From 3b6aec1903945a804766a42fae9ea9c1a52b197f Mon Sep 17 00:00:00 2001 From: tracylynne99 Date: Wed, 8 Mar 2023 14:52:05 -0600 Subject: [PATCH 1/4] Removed deprecated PiePlot3D --- .../java/org/jfree/chart/ChartFactory.java | 136 --- .../java/org/jfree/chart/plot/PiePlot3D.java | 1002 ----------------- .../org/jfree/chart/plot/PiePlot3DTest.java | 108 -- 3 files changed, 1246 deletions(-) delete mode 100644 src/main/java/org/jfree/chart/plot/PiePlot3D.java delete mode 100644 src/test/java/org/jfree/chart/plot/PiePlot3DTest.java diff --git a/src/main/java/org/jfree/chart/ChartFactory.java b/src/main/java/org/jfree/chart/ChartFactory.java index d5036e2e3..c3a842fa6 100644 --- a/src/main/java/org/jfree/chart/ChartFactory.java +++ b/src/main/java/org/jfree/chart/ChartFactory.java @@ -71,7 +71,6 @@ import org.jfree.chart.plot.Marker; import org.jfree.chart.plot.MultiplePiePlot; import org.jfree.chart.plot.PiePlot; -import org.jfree.chart.plot.PiePlot3D; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PolarPlot; import org.jfree.chart.plot.RingPlot; @@ -107,7 +106,6 @@ import org.jfree.chart.renderer.xy.XYStepRenderer; import org.jfree.chart.title.TextTitle; import org.jfree.chart.ui.Layer; -import org.jfree.chart.ui.RectangleEdge; import org.jfree.chart.ui.RectangleInsets; import org.jfree.chart.ui.TextAnchor; import org.jfree.chart.urls.PieURLGenerator; @@ -604,140 +602,6 @@ public static JFreeChart createMultiplePieChart(String title, } - /** - * Creates a 3D pie chart using the specified dataset. The chart object - * returned by this method uses a {@link PiePlot3D} instance as the - * plot. - * - * @param title the chart title ({@code null} permitted). - * @param dataset the dataset for the chart ({@code null} permitted). - * @param legend a flag specifying whether or not a legend is required. - * @param tooltips configure chart to generate tool tips? - * @param locale the locale ({@code null} not permitted). - * - * @return A pie chart. - * - * @deprecated For 3D pie charts, use Orson Charts (https://github.com/jfree/orson-charts). - */ - public static JFreeChart createPieChart3D(String title, PieDataset dataset, - boolean legend, boolean tooltips, Locale locale) { - - Args.nullNotPermitted(locale, "locale"); - PiePlot3D plot = new PiePlot3D(dataset); - plot.setInsets(new RectangleInsets(0.0, 5.0, 5.0, 5.0)); - if (tooltips) { - plot.setToolTipGenerator(new StandardPieToolTipGenerator(locale)); - } - JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, - plot, legend); - currentTheme.apply(chart); - return chart; - - } - - /** - * Creates a 3D pie chart using the specified dataset. The chart object - * returned by this method uses a {@link PiePlot3D} instance as the - * plot. - * - * @param title the chart title ({@code null} permitted). - * @param dataset the dataset for the chart ({@code null} permitted). - * - * @return A pie chart. - * - * @deprecated For 3D pie charts, use Orson Charts (https://github.com/jfree/orson-charts). - */ - public static JFreeChart createPieChart3D(String title, - PieDataset dataset) { - return createPieChart3D(title, dataset, true, true, false); - } - - /** - * Creates a 3D pie chart using the specified dataset. The chart object - * returned by this method uses a {@link PiePlot3D} instance as the - * plot. - * - * @param title the chart title ({@code null} permitted). - * @param dataset the dataset for the chart ({@code null} permitted). - * @param legend a flag specifying whether or not a legend is required. - * @param tooltips configure chart to generate tool tips? - * @param urls configure chart to generate URLs? - * - * @return A pie chart. - * @deprecated For 3D pie charts, use Orson Charts (https://github.com/jfree/orson-charts). - */ - public static JFreeChart createPieChart3D(String title, PieDataset dataset, - boolean legend, boolean tooltips, boolean urls) { - - PiePlot3D plot = new PiePlot3D(dataset); - plot.setInsets(new RectangleInsets(0.0, 5.0, 5.0, 5.0)); - if (tooltips) { - plot.setToolTipGenerator(new StandardPieToolTipGenerator()); - } - if (urls) { - plot.setURLGenerator(new StandardPieURLGenerator()); - } - JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, - plot, legend); - currentTheme.apply(chart); - return chart; - - } - - /** - * Creates a chart that displays multiple pie plots. The chart object - * returned by this method uses a {@link MultiplePiePlot} instance as the - * plot. - * - * @param title the chart title ({@code null} permitted). - * @param dataset the dataset ({@code null} permitted). - * @param order the order that the data is extracted (by row or by column) - * ({@code null} not permitted). - * @param legend include a legend? - * @param tooltips generate tooltips? - * @param urls generate URLs? - * - * @return A chart. - */ - public static JFreeChart createMultiplePieChart3D(String title, - CategoryDataset dataset, TableOrder order, boolean legend, - boolean tooltips, boolean urls) { - - Args.nullNotPermitted(order, "order"); - MultiplePiePlot plot = new MultiplePiePlot(dataset); - plot.setDataExtractOrder(order); - plot.setBackgroundPaint(null); - plot.setOutlineStroke(null); - - JFreeChart pieChart = new JFreeChart(new PiePlot3D(null)); - TextTitle seriesTitle = new TextTitle("Series Title", - new Font("SansSerif", Font.BOLD, 12)); - seriesTitle.setPosition(RectangleEdge.BOTTOM); - pieChart.setTitle(seriesTitle); - pieChart.removeLegend(); - pieChart.setBackgroundPaint(null); - plot.setPieChart(pieChart); - - if (tooltips) { - PieToolTipGenerator tooltipGenerator - = new StandardPieToolTipGenerator(); - PiePlot pp = (PiePlot) plot.getPieChart().getPlot(); - pp.setToolTipGenerator(tooltipGenerator); - } - - if (urls) { - PieURLGenerator urlGenerator = new StandardPieURLGenerator(); - PiePlot pp = (PiePlot) plot.getPieChart().getPlot(); - pp.setURLGenerator(urlGenerator); - } - - JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, - plot, legend); - currentTheme.apply(chart); - return chart; - - } - /** * Creates a bar chart with a vertical orientation. The chart object * returned by this method uses a {@link CategoryPlot} instance as the diff --git a/src/main/java/org/jfree/chart/plot/PiePlot3D.java b/src/main/java/org/jfree/chart/plot/PiePlot3D.java deleted file mode 100644 index cc8a1ff21..000000000 --- a/src/main/java/org/jfree/chart/plot/PiePlot3D.java +++ /dev/null @@ -1,1002 +0,0 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------- - * PiePlot3D.java - * -------------- - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Original Author: Tomer Peretz; - * Contributor(s): Richard Atkinson; - * David Gilbert; - * Xun Kang; - * Christian W. Zuckschwerdt; - * Arnaud Lelievre; - * Dave Crane; - * Martin Hoeller; - * DaveLaw (dave ATT davelaw DOTT de); - */ - -package org.jfree.chart.plot; - -import java.awt.AlphaComposite; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Polygon; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Arc2D; -import java.awt.geom.Area; -import java.awt.geom.Ellipse2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.entity.PieSectionEntity; -import org.jfree.chart.event.PlotChangeEvent; -import org.jfree.chart.labels.PieToolTipGenerator; -import org.jfree.chart.ui.RectangleInsets; -import org.jfree.chart.util.PaintAlpha; -import org.jfree.data.general.DatasetUtils; -import org.jfree.data.general.PieDataset; - -/** - * A plot that displays data in the form of a 3D pie chart, using data from - * any class that implements the {@link PieDataset} interface. - *

- * Although this class extends {@link PiePlot}, it does not currently support - * exploded sections. - * - * @deprecated For 3D pie charts, use Orson Charts (https://github.com/jfree/orson-charts). - */ -public class PiePlot3D extends PiePlot implements Serializable { - - /** For serialization. */ - private static final long serialVersionUID = 3408984188945161432L; - - /** The factor of the depth of the pie from the plot height */ - private double depthFactor = 0.12; - - /** - * A flag that controls whether or not the sides of the pie chart - * are rendered using a darker colour. - */ - private boolean darkerSides = false; // default preserves previous behaviour - - /** - * Creates a new instance with no dataset. - */ - public PiePlot3D() { - this(null); - } - - /** - * Creates a pie chart with a three dimensional effect using the specified - * dataset. - * - * @param dataset the dataset ({@code null} permitted). - */ - public PiePlot3D(PieDataset dataset) { - super(dataset); - setCircular(false, false); - } - - /** - * Returns the depth factor for the chart. - * - * @return The depth factor. - * - * @see #setDepthFactor(double) - */ - public double getDepthFactor() { - return this.depthFactor; - } - - /** - * Sets the pie depth as a percentage of the height of the plot area, and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param factor the depth factor (for example, 0.20 is twenty percent). - * - * @see #getDepthFactor() - */ - public void setDepthFactor(double factor) { - this.depthFactor = factor; - fireChangeEvent(); - } - - /** - * Returns a flag that controls whether or not the sides of the pie chart - * are rendered using a darker colour. - * - * @return A boolean. - * - * @see #setDarkerSides(boolean) - */ - public boolean getDarkerSides() { - return this.darkerSides; - } - - /** - * Sets a flag that controls whether or not the sides of the pie chart - * are rendered using a darker colour, and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param darker true to darken the sides, false to use the default - * behaviour. - * - * @see #getDarkerSides() - */ - public void setDarkerSides(boolean darker) { - this.darkerSides = darker; - fireChangeEvent(); - } - - /** - * Draws the plot on a Java 2D graphics device (such as the screen or a - * printer). This method is called by the - * {@link org.jfree.chart.JFreeChart} class, you don't normally need - * to call it yourself. - * - * @param g2 the graphics device. - * @param plotArea the area within which the plot should be drawn. - * @param anchor the anchor point. - * @param parentState the state from the parent plot, if there is one. - * @param info collects info about the drawing - * ({@code null} permitted). - */ - @Override - public void draw(Graphics2D g2, Rectangle2D plotArea, Point2D anchor, - PlotState parentState, PlotRenderingInfo info) { - - // adjust for insets... - RectangleInsets insets = getInsets(); - insets.trim(plotArea); - - Rectangle2D originalPlotArea = (Rectangle2D) plotArea.clone(); - if (info != null) { - info.setPlotArea(plotArea); - info.setDataArea(plotArea); - } - - drawBackground(g2, plotArea); - - Shape savedClip = g2.getClip(); - g2.clip(plotArea); - - Graphics2D savedG2 = g2; - BufferedImage dataImage = null; - if (getShadowGenerator() != null) { - dataImage = new BufferedImage((int) plotArea.getWidth(), - (int) plotArea.getHeight(), BufferedImage.TYPE_INT_ARGB); - g2 = dataImage.createGraphics(); - g2.translate(-plotArea.getX(), -plotArea.getY()); - g2.setRenderingHints(savedG2.getRenderingHints()); - originalPlotArea = (Rectangle2D) plotArea.clone(); - } - // adjust the plot area by the interior spacing value - double gapPercent = getInteriorGap(); - double labelPercent = 0.0; - if (getLabelGenerator() != null) { - labelPercent = getLabelGap() + getMaximumLabelWidth(); - } - double gapHorizontal = plotArea.getWidth() * (gapPercent - + labelPercent) * 2.0; - double gapVertical = plotArea.getHeight() * gapPercent * 2.0; - - if (DEBUG_DRAW_INTERIOR) { - double hGap = plotArea.getWidth() * getInteriorGap(); - double vGap = plotArea.getHeight() * getInteriorGap(); - double igx1 = plotArea.getX() + hGap; - double igx2 = plotArea.getMaxX() - hGap; - double igy1 = plotArea.getY() + vGap; - double igy2 = plotArea.getMaxY() - vGap; - g2.setPaint(Color.LIGHT_GRAY); - g2.draw(new Rectangle2D.Double(igx1, igy1, igx2 - igx1, - igy2 - igy1)); - } - - double linkX = plotArea.getX() + gapHorizontal / 2; - double linkY = plotArea.getY() + gapVertical / 2; - double linkW = plotArea.getWidth() - gapHorizontal; - double linkH = plotArea.getHeight() - gapVertical; - - // make the link area a square if the pie chart is to be circular... - if (isCircular()) { // is circular? - double min = Math.min(linkW, linkH) / 2; - linkX = (linkX + linkX + linkW) / 2 - min; - linkY = (linkY + linkY + linkH) / 2 - min; - linkW = 2 * min; - linkH = 2 * min; - } - - PiePlotState state = initialise(g2, plotArea, this, null, info); - - // the link area defines the dog leg points for the linking lines to - // the labels - Rectangle2D linkAreaXX = new Rectangle2D.Double(linkX, linkY, linkW, - linkH * (1 - this.depthFactor)); - state.setLinkArea(linkAreaXX); - - if (DEBUG_DRAW_LINK_AREA) { - g2.setPaint(Color.BLUE); - g2.draw(linkAreaXX); - g2.setPaint(Color.YELLOW); - g2.draw(new Ellipse2D.Double(linkAreaXX.getX(), linkAreaXX.getY(), - linkAreaXX.getWidth(), linkAreaXX.getHeight())); - } - - // the explode area defines the max circle/ellipse for the exploded pie - // sections. - // it is defined by shrinking the linkArea by the linkMargin factor. - double hh = linkW * getLabelLinkMargin(); - double vv = linkH * getLabelLinkMargin(); - Rectangle2D explodeArea = new Rectangle2D.Double(linkX + hh / 2.0, - linkY + vv / 2.0, linkW - hh, linkH - vv); - - state.setExplodedPieArea(explodeArea); - - // the pie area defines the circle/ellipse for regular pie sections. - // it is defined by shrinking the explodeArea by the explodeMargin - // factor. - double maximumExplodePercent = getMaximumExplodePercent(); - double percent = maximumExplodePercent / (1.0 + maximumExplodePercent); - - double h1 = explodeArea.getWidth() * percent; - double v1 = explodeArea.getHeight() * percent; - Rectangle2D pieArea = new Rectangle2D.Double(explodeArea.getX() - + h1 / 2.0, explodeArea.getY() + v1 / 2.0, - explodeArea.getWidth() - h1, explodeArea.getHeight() - v1); - - // the link area defines the dog-leg point for the linking lines to - // the labels - int depth = (int) (pieArea.getHeight() * this.depthFactor); - Rectangle2D linkArea = new Rectangle2D.Double(linkX, linkY, linkW, - linkH - depth); - state.setLinkArea(linkArea); - - state.setPieArea(pieArea); - state.setPieCenterX(pieArea.getCenterX()); - state.setPieCenterY(pieArea.getCenterY() - depth / 2.0); - state.setPieWRadius(pieArea.getWidth() / 2.0); - state.setPieHRadius((pieArea.getHeight() - depth) / 2.0); - - // get the data source - return if null; - PieDataset dataset = getDataset(); - if (DatasetUtils.isEmptyOrNull(getDataset())) { - drawNoDataMessage(g2, plotArea); - g2.setClip(savedClip); - drawOutline(g2, plotArea); - return; - } - - // if too any elements - if (dataset.getKeys().size() > plotArea.getWidth()) { - String text = localizationResources.getString("Too_many_elements"); - Font sfont = new Font("dialog", Font.BOLD, 10); - g2.setFont(sfont); - FontMetrics fm = g2.getFontMetrics(sfont); - int stringWidth = fm.stringWidth(text); - - g2.drawString(text, (int) (plotArea.getX() + (plotArea.getWidth() - - stringWidth) / 2), (int) (plotArea.getY() - + (plotArea.getHeight() / 2))); - return; - } - // if we are drawing a perfect circle, we need to readjust the top left - // coordinates of the drawing area for the arcs to arrive at this - // effect. - if (isCircular()) { - double min = Math.min(plotArea.getWidth(), - plotArea.getHeight()) / 2; - plotArea = new Rectangle2D.Double(plotArea.getCenterX() - min, - plotArea.getCenterY() - min, 2 * min, 2 * min); - } - // get a list of keys... - List sectionKeys = dataset.getKeys(); - - if (sectionKeys.isEmpty()) { - return; - } - - // establish the coordinates of the top left corner of the drawing area - double arcX = pieArea.getX(); - double arcY = pieArea.getY(); - - //g2.clip(clipArea); - Composite originalComposite = g2.getComposite(); - g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - getForegroundAlpha())); - - double totalValue = DatasetUtils.calculatePieDatasetTotal(dataset); - double runningTotal = 0; - if (depth < 0) { - return; // if depth is negative don't draw anything - } - - ArrayList arcList = new ArrayList(); - Arc2D.Double arc; - Paint paint; - Paint outlinePaint; - Stroke outlineStroke; - - Iterator iterator = sectionKeys.iterator(); - while (iterator.hasNext()) { - - Comparable currentKey = (Comparable) iterator.next(); - Number dataValue = dataset.getValue(currentKey); - if (dataValue == null) { - arcList.add(null); - continue; - } - double value = dataValue.doubleValue(); - if (value <= 0) { - arcList.add(null); - continue; - } - double startAngle = getStartAngle(); - double direction = getDirection().getFactor(); - double angle1 = startAngle + (direction * (runningTotal * 360)) - / totalValue; - double angle2 = startAngle + (direction * (runningTotal + value) - * 360) / totalValue; - if (Math.abs(angle2 - angle1) > getMinimumArcAngleToDraw()) { - arcList.add(new Arc2D.Double(arcX, arcY + depth, - pieArea.getWidth(), pieArea.getHeight() - depth, - angle1, angle2 - angle1, Arc2D.PIE)); - } - else { - arcList.add(null); - } - runningTotal += value; - } - - Shape oldClip = g2.getClip(); - - Ellipse2D top = new Ellipse2D.Double(pieArea.getX(), pieArea.getY(), - pieArea.getWidth(), pieArea.getHeight() - depth); - - Ellipse2D bottom = new Ellipse2D.Double(pieArea.getX(), pieArea.getY() - + depth, pieArea.getWidth(), pieArea.getHeight() - depth); - - Rectangle2D lower = new Rectangle2D.Double(top.getX(), - top.getCenterY(), pieArea.getWidth(), bottom.getMaxY() - - top.getCenterY()); - - Rectangle2D upper = new Rectangle2D.Double(pieArea.getX(), top.getY(), - pieArea.getWidth(), bottom.getCenterY() - top.getY()); - - Area a = new Area(top); - a.add(new Area(lower)); - Area b = new Area(bottom); - b.add(new Area(upper)); - Area pie = new Area(a); - pie.intersect(b); - - Area front = new Area(pie); - front.subtract(new Area(top)); - - Area back = new Area(pie); - back.subtract(new Area(bottom)); - - // draw the bottom circle - int[] xs; - int[] ys; - - int categoryCount = arcList.size(); - for (int categoryIndex = 0; categoryIndex < categoryCount; - categoryIndex++) { - arc = (Arc2D.Double) arcList.get(categoryIndex); - if (arc == null) { - continue; - } - Comparable key = getSectionKey(categoryIndex); - paint = lookupSectionPaint(key); - outlinePaint = lookupSectionOutlinePaint(key); - outlineStroke = lookupSectionOutlineStroke(key); - g2.setPaint(paint); - g2.fill(arc); - g2.setPaint(outlinePaint); - g2.setStroke(outlineStroke); - g2.draw(arc); - g2.setPaint(paint); - - Point2D p1 = arc.getStartPoint(); - - // draw the height - xs = new int[] {(int) arc.getCenterX(), (int) arc.getCenterX(), - (int) p1.getX(), (int) p1.getX()}; - ys = new int[] {(int) arc.getCenterY(), (int) arc.getCenterY() - - depth, (int) p1.getY() - depth, (int) p1.getY()}; - Polygon polygon = new Polygon(xs, ys, 4); - g2.setPaint(java.awt.Color.LIGHT_GRAY); - g2.fill(polygon); - g2.setPaint(outlinePaint); - g2.setStroke(outlineStroke); - g2.draw(polygon); - g2.setPaint(paint); - - } - - g2.setPaint(Color.GRAY); - g2.fill(back); - g2.fill(front); - - // cycle through once drawing only the sides at the back... - int cat = 0; - iterator = arcList.iterator(); - while (iterator.hasNext()) { - Arc2D segment = (Arc2D) iterator.next(); - if (segment != null) { - Comparable key = getSectionKey(cat); - paint = lookupSectionPaint(key); - outlinePaint = lookupSectionOutlinePaint(key); - outlineStroke = lookupSectionOutlineStroke(key); - drawSide(g2, pieArea, segment, front, back, paint, - outlinePaint, outlineStroke, false, true); - } - cat++; - } - - // cycle through again drawing only the sides at the front... - cat = 0; - iterator = arcList.iterator(); - while (iterator.hasNext()) { - Arc2D segment = (Arc2D) iterator.next(); - if (segment != null) { - Comparable key = getSectionKey(cat); - paint = lookupSectionPaint(key); - outlinePaint = lookupSectionOutlinePaint(key); - outlineStroke = lookupSectionOutlineStroke(key); - drawSide(g2, pieArea, segment, front, back, paint, - outlinePaint, outlineStroke, true, false); - } - cat++; - } - - g2.setClip(oldClip); - - // draw the sections at the top of the pie (and set up tooltips)... - Arc2D upperArc; - for (int sectionIndex = 0; sectionIndex < categoryCount; - sectionIndex++) { - arc = (Arc2D.Double) arcList.get(sectionIndex); - if (arc == null) { - continue; - } - upperArc = new Arc2D.Double(arcX, arcY, pieArea.getWidth(), - pieArea.getHeight() - depth, arc.getAngleStart(), - arc.getAngleExtent(), Arc2D.PIE); - - Comparable currentKey = (Comparable) sectionKeys.get(sectionIndex); - paint = lookupSectionPaint(currentKey, true); - outlinePaint = lookupSectionOutlinePaint(currentKey); - outlineStroke = lookupSectionOutlineStroke(currentKey); - g2.setPaint(paint); - g2.fill(upperArc); - g2.setStroke(outlineStroke); - g2.setPaint(outlinePaint); - g2.draw(upperArc); - - // add a tooltip for the section... - if (info != null) { - EntityCollection entities - = info.getOwner().getEntityCollection(); - if (entities != null) { - String tip = null; - PieToolTipGenerator tipster = getToolTipGenerator(); - if (tipster != null) { - // @mgs: using the method's return value was missing - tip = tipster.generateToolTip(dataset, currentKey); - } - String url = null; - if (getURLGenerator() != null) { - url = getURLGenerator().generateURL(dataset, currentKey, - getPieIndex()); - } - PieSectionEntity entity = new PieSectionEntity( - upperArc, dataset, getPieIndex(), sectionIndex, - currentKey, tip, url); - entities.add(entity); - } - } - } - - List keys = dataset.getKeys(); - Rectangle2D adjustedPlotArea = new Rectangle2D.Double( - originalPlotArea.getX(), originalPlotArea.getY(), - originalPlotArea.getWidth(), originalPlotArea.getHeight() - - depth); - if (getSimpleLabels()) { - drawSimpleLabels(g2, keys, totalValue, adjustedPlotArea, - linkArea, state); - } - else { - drawLabels(g2, keys, totalValue, adjustedPlotArea, linkArea, - state); - } - - if (getShadowGenerator() != null) { - BufferedImage shadowImage - = getShadowGenerator().createDropShadow(dataImage); - g2 = savedG2; - g2.drawImage(shadowImage, (int) plotArea.getX() - + getShadowGenerator().calculateOffsetX(), - (int) plotArea.getY() - + getShadowGenerator().calculateOffsetY(), null); - g2.drawImage(dataImage, (int) plotArea.getX(), - (int) plotArea.getY(), null); - } - - g2.setClip(savedClip); - g2.setComposite(originalComposite); - drawOutline(g2, originalPlotArea); - - } - - /** - * Draws the side of a pie section. - * - * @param g2 the graphics device. - * @param plotArea the plot area. - * @param arc the arc. - * @param front the front of the pie. - * @param back the back of the pie. - * @param paint the color. - * @param outlinePaint the outline paint. - * @param outlineStroke the outline stroke. - * @param drawFront draw the front? - * @param drawBack draw the back? - */ - protected void drawSide(Graphics2D g2, - Rectangle2D plotArea, - Arc2D arc, - Area front, - Area back, - Paint paint, - Paint outlinePaint, - Stroke outlineStroke, - boolean drawFront, - boolean drawBack) { - - if (getDarkerSides()) { - paint = PaintAlpha.darker(paint); - } - - double start = arc.getAngleStart(); - double extent = arc.getAngleExtent(); - double end = start + extent; - - g2.setStroke(outlineStroke); - - // for CLOCKWISE charts, the extent will be negative... - if (extent < 0.0) { - - if (isAngleAtFront(start)) { // start at front - - if (!isAngleAtBack(end)) { - - if (extent > -180.0) { // the segment is entirely at the - // front of the chart - if (drawFront) { - Area side = new Area(new Rectangle2D.Double( - arc.getEndPoint().getX(), plotArea.getY(), - arc.getStartPoint().getX() - - arc.getEndPoint().getX(), - plotArea.getHeight())); - side.intersect(front); - g2.setPaint(paint); - g2.fill(side); - g2.setPaint(outlinePaint); - g2.draw(side); - } - } - else { // the segment starts at the front, and wraps all - // the way around - // the back and finishes at the front again - Area side1 = new Area(new Rectangle2D.Double( - plotArea.getX(), plotArea.getY(), - arc.getStartPoint().getX() - plotArea.getX(), - plotArea.getHeight())); - side1.intersect(front); - - Area side2 = new Area(new Rectangle2D.Double( - arc.getEndPoint().getX(), plotArea.getY(), - plotArea.getMaxX() - arc.getEndPoint().getX(), - plotArea.getHeight())); - - side2.intersect(front); - g2.setPaint(paint); - if (drawFront) { - g2.fill(side1); - g2.fill(side2); - } - - if (drawBack) { - g2.fill(back); - } - - g2.setPaint(outlinePaint); - if (drawFront) { - g2.draw(side1); - g2.draw(side2); - } - - if (drawBack) { - g2.draw(back); - } - - } - } - else { // starts at the front, finishes at the back (going - // around the left side) - - if (drawBack) { - Area side2 = new Area(new Rectangle2D.Double( - plotArea.getX(), plotArea.getY(), - arc.getEndPoint().getX() - plotArea.getX(), - plotArea.getHeight())); - side2.intersect(back); - g2.setPaint(paint); - g2.fill(side2); - g2.setPaint(outlinePaint); - g2.draw(side2); - } - - if (drawFront) { - Area side1 = new Area(new Rectangle2D.Double( - plotArea.getX(), plotArea.getY(), - arc.getStartPoint().getX() - plotArea.getX(), - plotArea.getHeight())); - side1.intersect(front); - g2.setPaint(paint); - g2.fill(side1); - g2.setPaint(outlinePaint); - g2.draw(side1); - } - } - } - else { // the segment starts at the back (still extending - // CLOCKWISE) - - if (!isAngleAtFront(end)) { - if (extent > -180.0) { // whole segment stays at the back - if (drawBack) { - Area side = new Area(new Rectangle2D.Double( - arc.getStartPoint().getX(), plotArea.getY(), - arc.getEndPoint().getX() - - arc.getStartPoint().getX(), - plotArea.getHeight())); - side.intersect(back); - g2.setPaint(paint); - g2.fill(side); - g2.setPaint(outlinePaint); - g2.draw(side); - } - } - else { // starts at the back, wraps around front, and - // finishes at back again - Area side1 = new Area(new Rectangle2D.Double( - arc.getStartPoint().getX(), plotArea.getY(), - plotArea.getMaxX() - arc.getStartPoint().getX(), - plotArea.getHeight())); - side1.intersect(back); - - Area side2 = new Area(new Rectangle2D.Double( - plotArea.getX(), plotArea.getY(), - arc.getEndPoint().getX() - plotArea.getX(), - plotArea.getHeight())); - - side2.intersect(back); - - g2.setPaint(paint); - if (drawBack) { - g2.fill(side1); - g2.fill(side2); - } - - if (drawFront) { - g2.fill(front); - } - - g2.setPaint(outlinePaint); - if (drawBack) { - g2.draw(side1); - g2.draw(side2); - } - - if (drawFront) { - g2.draw(front); - } - - } - } - else { // starts at back, finishes at front (CLOCKWISE) - - if (drawBack) { - Area side1 = new Area(new Rectangle2D.Double( - arc.getStartPoint().getX(), plotArea.getY(), - plotArea.getMaxX() - arc.getStartPoint().getX(), - plotArea.getHeight())); - side1.intersect(back); - g2.setPaint(paint); - g2.fill(side1); - g2.setPaint(outlinePaint); - g2.draw(side1); - } - - if (drawFront) { - Area side2 = new Area(new Rectangle2D.Double( - arc.getEndPoint().getX(), plotArea.getY(), - plotArea.getMaxX() - arc.getEndPoint().getX(), - plotArea.getHeight())); - side2.intersect(front); - g2.setPaint(paint); - g2.fill(side2); - g2.setPaint(outlinePaint); - g2.draw(side2); - } - - } - } - } - else if (extent > 0.0) { // the pie sections are arranged ANTICLOCKWISE - - if (isAngleAtFront(start)) { // segment starts at the front - - if (!isAngleAtBack(end)) { // and finishes at the front - - if (extent < 180.0) { // segment only occupies the front - if (drawFront) { - Area side = new Area(new Rectangle2D.Double( - arc.getStartPoint().getX(), plotArea.getY(), - arc.getEndPoint().getX() - - arc.getStartPoint().getX(), - plotArea.getHeight())); - side.intersect(front); - g2.setPaint(paint); - g2.fill(side); - g2.setPaint(outlinePaint); - g2.draw(side); - } - } - else { // segments wraps right around the back... - Area side1 = new Area(new Rectangle2D.Double( - arc.getStartPoint().getX(), plotArea.getY(), - plotArea.getMaxX() - arc.getStartPoint().getX(), - plotArea.getHeight())); - side1.intersect(front); - - Area side2 = new Area(new Rectangle2D.Double( - plotArea.getX(), plotArea.getY(), - arc.getEndPoint().getX() - plotArea.getX(), - plotArea.getHeight())); - side2.intersect(front); - - g2.setPaint(paint); - if (drawFront) { - g2.fill(side1); - g2.fill(side2); - } - - if (drawBack) { - g2.fill(back); - } - - g2.setPaint(outlinePaint); - if (drawFront) { - g2.draw(side1); - g2.draw(side2); - } - - if (drawBack) { - g2.draw(back); - } - - } - } - else { // segments starts at front and finishes at back... - if (drawBack) { - Area side2 = new Area(new Rectangle2D.Double( - arc.getEndPoint().getX(), plotArea.getY(), - plotArea.getMaxX() - arc.getEndPoint().getX(), - plotArea.getHeight())); - side2.intersect(back); - g2.setPaint(paint); - g2.fill(side2); - g2.setPaint(outlinePaint); - g2.draw(side2); - } - - if (drawFront) { - Area side1 = new Area(new Rectangle2D.Double( - arc.getStartPoint().getX(), plotArea.getY(), - plotArea.getMaxX() - arc.getStartPoint().getX(), - plotArea.getHeight())); - side1.intersect(front); - g2.setPaint(paint); - g2.fill(side1); - g2.setPaint(outlinePaint); - g2.draw(side1); - } - } - } - else { // segment starts at back - - if (!isAngleAtFront(end)) { - if (extent < 180.0) { // and finishes at back - if (drawBack) { - Area side = new Area(new Rectangle2D.Double( - arc.getEndPoint().getX(), plotArea.getY(), - arc.getStartPoint().getX() - - arc.getEndPoint().getX(), - plotArea.getHeight())); - side.intersect(back); - g2.setPaint(paint); - g2.fill(side); - g2.setPaint(outlinePaint); - g2.draw(side); - } - } - else { // starts at back and wraps right around to the - // back again - Area side1 = new Area(new Rectangle2D.Double( - arc.getStartPoint().getX(), plotArea.getY(), - plotArea.getX() - arc.getStartPoint().getX(), - plotArea.getHeight())); - side1.intersect(back); - - Area side2 = new Area(new Rectangle2D.Double( - arc.getEndPoint().getX(), plotArea.getY(), - plotArea.getMaxX() - arc.getEndPoint().getX(), - plotArea.getHeight())); - side2.intersect(back); - - g2.setPaint(paint); - if (drawBack) { - g2.fill(side1); - g2.fill(side2); - } - - if (drawFront) { - g2.fill(front); - } - - g2.setPaint(outlinePaint); - if (drawBack) { - g2.draw(side1); - g2.draw(side2); - } - - if (drawFront) { - g2.draw(front); - } - - } - } - else { // starts at the back and finishes at the front - // (wrapping the left side) - if (drawBack) { - Area side1 = new Area(new Rectangle2D.Double( - plotArea.getX(), plotArea.getY(), - arc.getStartPoint().getX() - plotArea.getX(), - plotArea.getHeight())); - side1.intersect(back); - g2.setPaint(paint); - g2.fill(side1); - g2.setPaint(outlinePaint); - g2.draw(side1); - } - - if (drawFront) { - Area side2 = new Area(new Rectangle2D.Double( - plotArea.getX(), plotArea.getY(), - arc.getEndPoint().getX() - plotArea.getX(), - plotArea.getHeight())); - side2.intersect(front); - g2.setPaint(paint); - g2.fill(side2); - g2.setPaint(outlinePaint); - g2.draw(side2); - } - } - } - - } - - } - - /** - * Returns a short string describing the type of plot. - * - * @return Pie 3D Plot. - */ - @Override - public String getPlotType() { - return localizationResources.getString("Pie_3D_Plot"); - } - - /** - * A utility method that returns true if the angle represents a point at - * the front of the 3D pie chart. 0 - 180 degrees is the back, 180 - 360 - * is the front. - * - * @param angle the angle. - * - * @return A boolean. - */ - private boolean isAngleAtFront(double angle) { - return (Math.sin(Math.toRadians(angle)) < 0.0); - } - - /** - * A utility method that returns true if the angle represents a point at - * the back of the 3D pie chart. 0 - 180 degrees is the back, 180 - 360 - * is the front. - * - * @param angle the angle. - * - * @return {@code true} if the angle is at the back of the pie. - */ - private boolean isAngleAtBack(double angle) { - return (Math.sin(Math.toRadians(angle)) > 0.0); - } - - /** - * Tests this plot for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof PiePlot3D)) { - return false; - } - PiePlot3D that = (PiePlot3D) obj; - if (this.depthFactor != that.depthFactor) { - return false; - } - if (this.darkerSides != that.darkerSides) { - return false; - } - return super.equals(obj); - } - -} diff --git a/src/test/java/org/jfree/chart/plot/PiePlot3DTest.java b/src/test/java/org/jfree/chart/plot/PiePlot3DTest.java deleted file mode 100644 index a6e62b75b..000000000 --- a/src/test/java/org/jfree/chart/plot/PiePlot3DTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------ - * Pie3DPlotTest.java - * ------------------ - * (C) Copyright 2003-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.chart.plot; - -import java.awt.Graphics2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; - -import org.jfree.chart.ChartFactory; -import org.jfree.chart.JFreeChart; -import org.jfree.chart.TestUtils; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests for the {@link PiePlot3D} class. - */ -public class PiePlot3DTest { - - /** - * Some checks for the equals() method. - */ - @Test - public void testEquals() { - PiePlot3D p1 = new PiePlot3D(); - PiePlot3D p2 = new PiePlot3D(); - assertEquals(p1, p2); - assertEquals(p2, p1); - - p1.setDepthFactor(1.23); - assertNotEquals(p1, p2); - p2.setDepthFactor(1.23); - assertEquals(p1, p2); - - p1.setDarkerSides(true); - assertNotEquals(p1, p2); - p2.setDarkerSides(true); - assertEquals(p1, p2); - } - - /** - * Serialize an instance, restore it, and check for equality. - */ - @Test - public void testSerialization() { - PiePlot3D p1 = new PiePlot3D(null); - PiePlot3D p2 = (PiePlot3D) TestUtils.serialised(p1); - assertEquals(p1, p2); - } - - /** - * Draws a pie chart where the label generator returns null. - */ - @Test - public void testDrawWithNullDataset() { - JFreeChart chart = ChartFactory.createPieChart3D("Test", null, true, - false, false); - boolean success = false; - try { - BufferedImage image = new BufferedImage(200 , 100, - BufferedImage.TYPE_INT_RGB); - Graphics2D g2 = image.createGraphics(); - chart.draw(g2, new Rectangle2D.Double(0, 0, 200, 100), null, null); - g2.dispose(); - success = true; - } - catch (Exception e) { - success = false; - } - assertTrue(success); - } - -} From a7b581ebb03a8aadfa4d79469ff5e03d6c68e453 Mon Sep 17 00:00:00 2001 From: tracylynne99 Date: Wed, 8 Mar 2023 15:04:41 -0600 Subject: [PATCH 2/4] Removed deprecated PiePlot3D --- .../java/org/jfree/chart/JFreeChartTest.java | 990 +++++++++--------- .../java/org/jfree/chart/PieChart3DTest.java | 139 --- 2 files changed, 488 insertions(+), 641 deletions(-) delete mode 100644 src/test/java/org/jfree/chart/PieChart3DTest.java diff --git a/src/test/java/org/jfree/chart/JFreeChartTest.java b/src/test/java/org/jfree/chart/JFreeChartTest.java index 6dd208d02..a739cba2f 100644 --- a/src/test/java/org/jfree/chart/JFreeChartTest.java +++ b/src/test/java/org/jfree/chart/JFreeChartTest.java @@ -1,502 +1,488 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------- - * JFreeChartTest.java - * ------------------- - * (C) Copyright 2002-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): Tracy Hiltbrand; - * - */ - -package org.jfree.chart; - -import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.equalsverifier.Warning; -import org.jfree.chart.event.ChartChangeEvent; -import org.jfree.chart.event.ChartChangeListener; -import org.jfree.chart.plot.PiePlot; -import org.jfree.chart.plot.Plot; -import org.jfree.chart.plot.RingPlot; -import org.jfree.chart.title.LegendTitle; -import org.jfree.chart.title.TextTitle; -import org.jfree.chart.title.Title; -import org.jfree.chart.ui.Align; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.RectangleInsets; -import org.jfree.data.category.DefaultCategoryDataset; -import org.jfree.data.general.DefaultPieDataset; -import org.jfree.data.time.Day; -import org.jfree.data.time.RegularTimePeriod; -import org.jfree.data.time.TimeSeries; -import org.jfree.data.time.TimeSeriesCollection; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.swing.event.EventListenerList; -import java.awt.*; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests for the {@link JFreeChart} class. - */ -public class JFreeChartTest implements ChartChangeListener { - - /** - * Use EqualsVerifier to test that the contract between equals and hashCode - * is properly implemented. - */ - @Test - public void testEqualsHashCode() { - EqualsVerifier.forClass(JFreeChart.class) - .withPrefabValues(TextTitle.class, - new TextTitle("tee"), - new TextTitle("vee")) - .withPrefabValues(Plot.class, - TestUtils.createPlot(true), - TestUtils.createPlot(false)) - .withPrefabValues(RenderingHints.class, - new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF), - new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)) - .withPrefabValues(EventListenerList.class, - new EventListenerList(), - new EventListenerList()) - .suppress(Warning.STRICT_INHERITANCE) - .suppress(Warning.NONFINAL_FIELDS) - .suppress(Warning.TRANSIENT_FIELDS) - .verify(); - } - - /** A pie chart. */ - private JFreeChart pieChart; - - /** - * Common test setup. - */ - @BeforeEach - public void setUp() { - DefaultPieDataset data = new DefaultPieDataset<>(); - data.setValue("Java", 43.2); - data.setValue("Visual Basic", 0.0); - data.setValue("C/C++", 17.5); - this.pieChart = ChartFactory.createPieChart("Pie Chart", data); - } - - /** - * Check that the equals() method can distinguish all fields. - */ - @Test - public void testEquals() { - JFreeChart chart1 = new JFreeChart("Title", - new Font("SansSerif", Font.PLAIN, 12), new PiePlot(), true); - JFreeChart chart2 = new JFreeChart("Title", - new Font("SansSerif", Font.PLAIN, 12), new PiePlot(), true); - assertEquals(chart1, chart2); - assertEquals(chart2, chart1); - - // renderingHints - chart1.setRenderingHints(new RenderingHints( - RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON)); - assertNotEquals(chart1, chart2); - chart2.setRenderingHints(new RenderingHints( - RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON)); - assertEquals(chart1, chart2); - - // borderVisible - chart1.setBorderVisible(true); - assertNotEquals(chart1, chart2); - chart2.setBorderVisible(true); - assertEquals(chart1, chart2); - - // borderStroke - BasicStroke s = new BasicStroke(2.0f); - chart1.setBorderStroke(s); - assertNotEquals(chart1, chart2); - chart2.setBorderStroke(s); - assertEquals(chart1, chart2); - - // borderPaint - chart1.setBorderPaint(Color.RED); - assertNotEquals(chart1, chart2); - chart2.setBorderPaint(Color.RED); - assertEquals(chart1, chart2); - - // padding - chart1.setPadding(new RectangleInsets(1, 2, 3, 4)); - assertNotEquals(chart1, chart2); - chart2.setPadding(new RectangleInsets(1, 2, 3, 4)); - assertEquals(chart1, chart2); - - // title - chart1.setTitle("XYZ"); - assertNotEquals(chart1, chart2); - chart2.setTitle("XYZ"); - assertEquals(chart1, chart2); - - // subtitles - chart1.addSubtitle(new TextTitle("Subtitle")); - assertNotEquals(chart1, chart2); - chart2.addSubtitle(new TextTitle("Subtitle")); - assertEquals(chart1, chart2); - - // plot - chart1 = new JFreeChart("Title", - new Font("SansSerif", Font.PLAIN, 12), new RingPlot(), false); - chart2 = new JFreeChart("Title", - new Font("SansSerif", Font.PLAIN, 12), new PiePlot(), false); - assertNotEquals(chart1, chart2); - chart2 = new JFreeChart("Title", - new Font("SansSerif", Font.PLAIN, 12), new RingPlot(), false); - assertEquals(chart1, chart2); - - // backgroundPaint - chart1.setBackgroundPaint(new GradientPaint(1.0f, 2.0f, Color.RED, - 3.0f, 4.0f, Color.BLUE)); - assertNotEquals(chart1, chart2); - chart2.setBackgroundPaint(new GradientPaint(1.0f, 2.0f, Color.RED, - 3.0f, 4.0f, Color.BLUE)); - assertEquals(chart1, chart2); - -// // backgroundImage -// chart1.setBackgroundImage(JFreeChart.INFO.getLogo()); -// assertFalse(chart1.equals(chart2)); -// chart2.setBackgroundImage(JFreeChart.INFO.getLogo()); -// assertEquals(chart1, chart2); - - // backgroundImageAlignment - chart1.setBackgroundImageAlignment(Align.BOTTOM_LEFT); - assertNotEquals(chart1, chart2); - chart2.setBackgroundImageAlignment(Align.BOTTOM_LEFT); - assertEquals(chart1, chart2); - - // backgroundImageAlpha - chart1.setBackgroundImageAlpha(0.1f); - assertNotEquals(chart1, chart2); - chart2.setBackgroundImageAlpha(0.1f); - assertEquals(chart1, chart2); - } - - /** - * A test to make sure that the legend is being picked up in the - * equals() testing. - */ - @Test - public void testEquals2() { - JFreeChart chart1 = new JFreeChart("Title", - new Font("SansSerif", Font.PLAIN, 12), new PiePlot(), true); - JFreeChart chart2 = new JFreeChart("Title", - new Font("SansSerif", Font.PLAIN, 12), new PiePlot(), false); - assertNotEquals(chart1, chart2); - assertNotEquals(chart2, chart1); - } - - /** - * Checks the subtitle count - should be 1 (the legend). - */ - @Test - public void testSubtitleCount() { - int count = this.pieChart.getSubtitleCount(); - assertEquals(1, count); - } - - /** - * Some checks for the getSubtitle() method. - */ - @Test - public void testGetSubtitle() { - DefaultPieDataset dataset = new DefaultPieDataset<>(); - JFreeChart chart = ChartFactory.createPieChart("title", dataset); - Title t = chart.getSubtitle(0); - assertTrue(t instanceof LegendTitle); - - try { - chart.getSubtitle(-1); - fail("Should have thrown an IllegalArgumentException on negative number"); - } - catch (IllegalArgumentException e) { - assertEquals("Index out of range.", e.getMessage()); - } - - try { - chart.getSubtitle(1); - fail("Should have thrown an IllegalArgumentException on excesive number"); - } - catch (IllegalArgumentException e) { - assertEquals("Index out of range.", e.getMessage()); - } - - try { - chart.getSubtitle(2); - fail("Should have thrown an IllegalArgumentException on number being out of range"); - } - catch (IllegalArgumentException e) { - assertEquals("Index out of range.", e.getMessage()); - } - - } - - /** - * Serialize a pie chart, restore it, and check for equality. - */ - @Test - public void testSerialization1() { - DefaultPieDataset data = new DefaultPieDataset<>(); - data.setValue("Type 1", 54.5); - data.setValue("Type 2", 23.9); - data.setValue("Type 3", 45.8); - - JFreeChart c1 = ChartFactory.createPieChart("Test", data); - JFreeChart c2 = TestUtils.serialised(c1); - assertEquals(c1, c2); - LegendTitle lt2 = c2.getLegend(); - assertSame(lt2.getSources()[0], c2.getPlot()); - } - - /** - * Serialize a 3D pie chart, restore it, and check for equality. - */ - @Test - public void testSerialization2() { - DefaultPieDataset data = new DefaultPieDataset<>(); - data.setValue("Type 1", 54.5); - data.setValue("Type 2", 23.9); - data.setValue("Type 3", 45.8); - JFreeChart c1 = ChartFactory.createPieChart3D("Test", data); - JFreeChart c2 = TestUtils.serialised(c1); - assertEquals(c1, c2); - } - - /** - * Serialize a bar chart, restore it, and check for equality. - */ - @Test - public void testSerialization3() { - - // row keys... - String series1 = "First"; - String series2 = "Second"; - String series3 = "Third"; - - // column keys... - String category1 = "Category 1"; - String category2 = "Category 2"; - String category3 = "Category 3"; - String category4 = "Category 4"; - String category5 = "Category 5"; - String category6 = "Category 6"; - String category7 = "Category 7"; - String category8 = "Category 8"; - - // create the dataset... - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - - dataset.addValue(1.0, series1, category1); - dataset.addValue(4.0, series1, category2); - dataset.addValue(3.0, series1, category3); - dataset.addValue(5.0, series1, category4); - dataset.addValue(5.0, series1, category5); - dataset.addValue(7.0, series1, category6); - dataset.addValue(7.0, series1, category7); - dataset.addValue(8.0, series1, category8); - - dataset.addValue(5.0, series2, category1); - dataset.addValue(7.0, series2, category2); - dataset.addValue(6.0, series2, category3); - dataset.addValue(8.0, series2, category4); - dataset.addValue(4.0, series2, category5); - dataset.addValue(4.0, series2, category6); - dataset.addValue(2.0, series2, category7); - dataset.addValue(1.0, series2, category8); - - dataset.addValue(4.0, series3, category1); - dataset.addValue(3.0, series3, category2); - dataset.addValue(2.0, series3, category3); - dataset.addValue(3.0, series3, category4); - dataset.addValue(6.0, series3, category5); - dataset.addValue(3.0, series3, category6); - dataset.addValue(4.0, series3, category7); - dataset.addValue(3.0, series3, category8); - - // create the chart... - JFreeChart c1 = ChartFactory.createBarChart("Vertical Bar Chart", - "Category", "Value", dataset); - JFreeChart c2 = TestUtils.serialised(c1); - assertEquals(c1, c2); - } - - /** - * Serialize a time seroes chart, restore it, and check for equality. - */ - @Test - public void testSerialization4() { - - RegularTimePeriod t = new Day(); - TimeSeries series = new TimeSeries("Series 1"); - series.add(t, 36.4); - t = t.next(); - series.add(t, 63.5); - TimeSeriesCollection dataset = new TimeSeriesCollection(); - dataset.addSeries(series); - - JFreeChart c1 = ChartFactory.createTimeSeriesChart("Test", "Date", - "Value", dataset); - JFreeChart c2 = TestUtils.serialised(c1); - assertEquals(c1, c2); - } - - /** - * Some checks for the addSubtitle() methods. - */ - @Test - public void testAddSubtitle() { - DefaultPieDataset dataset = new DefaultPieDataset<>(); - JFreeChart chart = ChartFactory.createPieChart("title", dataset); - - TextTitle t0 = new TextTitle("T0"); - chart.addSubtitle(0, t0); - assertEquals(t0, chart.getSubtitle(0)); - - TextTitle t1 = new TextTitle("T1"); - chart.addSubtitle(t1); - assertEquals(t1, chart.getSubtitle(2)); // subtitle 1 is the legend - - try { - chart.addSubtitle(null); - fail("Should have thrown an IllegalArgumentException on index out of range"); - } - catch (IllegalArgumentException e) { - assertEquals("Null 'subtitle' argument.", e.getMessage()); - } - - try { - chart.addSubtitle(-1, t0); - fail("Should have thrown an IllegalArgumentException on index out of range"); - } - catch (IllegalArgumentException e) { - assertEquals("The 'index' argument is out of range.", e.getMessage()); - } - - try { - chart.addSubtitle(4, t0); - fail("Should have thrown an IllegalArgumentException on index out of range"); - } - catch (IllegalArgumentException e) { - assertEquals("The 'index' argument is out of range.", e.getMessage()); - } - - } - - /** - * Some checks for the getSubtitles() method. - */ - @Test - public void testGetSubtitles() { - DefaultPieDataset dataset = new DefaultPieDataset<>(); - JFreeChart chart = ChartFactory.createPieChart("title", dataset); - List subtitles = chart.getSubtitles(); - - assertEquals(1, chart.getSubtitleCount()); - - // adding something to the returned list should NOT change the chart - subtitles.add(new TextTitle("T")); - assertEquals(1, chart.getSubtitleCount()); - } - - /** - * Some checks for the default legend firing change events. - */ - @Test - public void testLegendEvents() { - DefaultPieDataset dataset = new DefaultPieDataset<>(); - JFreeChart chart = ChartFactory.createPieChart("title", dataset); - chart.addChangeListener(this); - this.lastChartChangeEvent = null; - LegendTitle legend = chart.getLegend(); - legend.setPosition(RectangleEdge.TOP); - assertNotNull(this.lastChartChangeEvent); - } - - /** - * Some checks for title changes and event notification. - */ - @Test - public void testTitleChangeEvent() { - DefaultPieDataset dataset = new DefaultPieDataset(); - JFreeChart chart = ChartFactory.createPieChart("title", dataset); - chart.addChangeListener(this); - this.lastChartChangeEvent = null; - TextTitle t = chart.getTitle(); - t.setFont(new Font("Dialog", Font.BOLD, 9)); - assertNotNull(this.lastChartChangeEvent); - this.lastChartChangeEvent = null; - - // now create a new title and replace the existing title, several - // things should happen: - // (1) Adding the new title should trigger an immediate - // ChartChangeEvent; - // (2) Modifying the new title should trigger a ChartChangeEvent; - // (3) Modifying the old title should NOT trigger a ChartChangeEvent - TextTitle t2 = new TextTitle("T2"); - chart.setTitle(t2); - assertNotNull(this.lastChartChangeEvent); - this.lastChartChangeEvent = null; - - t2.setFont(new Font("Dialog", Font.BOLD, 9)); - assertNotNull(this.lastChartChangeEvent); - this.lastChartChangeEvent = null; - - t.setFont(new Font("Dialog", Font.BOLD, 9)); - assertNull(this.lastChartChangeEvent); - this.lastChartChangeEvent = null; - } - - @Test - public void testBug942() throws Exception { - final String title = "Pie Chart Demo 1\n\n\ntestnew line"; - assertEquals(title, ChartFactory.createPieChart(title, - new DefaultPieDataset()).getTitle().getText()); - } - - /** The last ChartChangeEvent received. */ - private ChartChangeEvent lastChartChangeEvent; - - /** - * Records the last chart change event. - * - * @param event the event. - */ - @Override - public void chartChanged(ChartChangeEvent event) { - this.lastChartChangeEvent = event; - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------- + * JFreeChartTest.java + * ------------------- + * (C) Copyright 2002-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): Tracy Hiltbrand; + * + */ + +package org.jfree.chart; + +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; +import org.jfree.chart.event.ChartChangeEvent; +import org.jfree.chart.event.ChartChangeListener; +import org.jfree.chart.plot.PiePlot; +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.RingPlot; +import org.jfree.chart.title.LegendTitle; +import org.jfree.chart.title.TextTitle; +import org.jfree.chart.title.Title; +import org.jfree.chart.ui.Align; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.data.category.DefaultCategoryDataset; +import org.jfree.data.general.DefaultPieDataset; +import org.jfree.data.time.Day; +import org.jfree.data.time.RegularTimePeriod; +import org.jfree.data.time.TimeSeries; +import org.jfree.data.time.TimeSeriesCollection; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.swing.event.EventListenerList; +import java.awt.*; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the {@link JFreeChart} class. + */ +public class JFreeChartTest implements ChartChangeListener { + + /** + * Use EqualsVerifier to test that the contract between equals and hashCode + * is properly implemented. + */ + @Test + public void testEqualsHashCode() { + EqualsVerifier.forClass(JFreeChart.class) + .withPrefabValues(TextTitle.class, + new TextTitle("tee"), + new TextTitle("vee")) + .withPrefabValues(Plot.class, + TestUtils.createPlot(true), + TestUtils.createPlot(false)) + .withPrefabValues(RenderingHints.class, + new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF), + new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)) + .withPrefabValues(EventListenerList.class, + new EventListenerList(), + new EventListenerList()) + .suppress(Warning.STRICT_INHERITANCE) + .suppress(Warning.NONFINAL_FIELDS) + .suppress(Warning.TRANSIENT_FIELDS) + .verify(); + } + + /** A pie chart. */ + private JFreeChart pieChart; + + /** + * Common test setup. + */ + @BeforeEach + public void setUp() { + DefaultPieDataset data = new DefaultPieDataset<>(); + data.setValue("Java", 43.2); + data.setValue("Visual Basic", 0.0); + data.setValue("C/C++", 17.5); + this.pieChart = ChartFactory.createPieChart("Pie Chart", data); + } + + /** + * Check that the equals() method can distinguish all fields. + */ + @Test + public void testEquals() { + JFreeChart chart1 = new JFreeChart("Title", + new Font("SansSerif", Font.PLAIN, 12), new PiePlot(), true); + JFreeChart chart2 = new JFreeChart("Title", + new Font("SansSerif", Font.PLAIN, 12), new PiePlot(), true); + assertEquals(chart1, chart2); + assertEquals(chart2, chart1); + + // renderingHints + chart1.setRenderingHints(new RenderingHints( + RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON)); + assertNotEquals(chart1, chart2); + chart2.setRenderingHints(new RenderingHints( + RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON)); + assertEquals(chart1, chart2); + + // borderVisible + chart1.setBorderVisible(true); + assertNotEquals(chart1, chart2); + chart2.setBorderVisible(true); + assertEquals(chart1, chart2); + + // borderStroke + BasicStroke s = new BasicStroke(2.0f); + chart1.setBorderStroke(s); + assertNotEquals(chart1, chart2); + chart2.setBorderStroke(s); + assertEquals(chart1, chart2); + + // borderPaint + chart1.setBorderPaint(Color.RED); + assertNotEquals(chart1, chart2); + chart2.setBorderPaint(Color.RED); + assertEquals(chart1, chart2); + + // padding + chart1.setPadding(new RectangleInsets(1, 2, 3, 4)); + assertNotEquals(chart1, chart2); + chart2.setPadding(new RectangleInsets(1, 2, 3, 4)); + assertEquals(chart1, chart2); + + // title + chart1.setTitle("XYZ"); + assertNotEquals(chart1, chart2); + chart2.setTitle("XYZ"); + assertEquals(chart1, chart2); + + // subtitles + chart1.addSubtitle(new TextTitle("Subtitle")); + assertNotEquals(chart1, chart2); + chart2.addSubtitle(new TextTitle("Subtitle")); + assertEquals(chart1, chart2); + + // plot + chart1 = new JFreeChart("Title", + new Font("SansSerif", Font.PLAIN, 12), new RingPlot(), false); + chart2 = new JFreeChart("Title", + new Font("SansSerif", Font.PLAIN, 12), new PiePlot(), false); + assertNotEquals(chart1, chart2); + chart2 = new JFreeChart("Title", + new Font("SansSerif", Font.PLAIN, 12), new RingPlot(), false); + assertEquals(chart1, chart2); + + // backgroundPaint + chart1.setBackgroundPaint(new GradientPaint(1.0f, 2.0f, Color.RED, + 3.0f, 4.0f, Color.BLUE)); + assertNotEquals(chart1, chart2); + chart2.setBackgroundPaint(new GradientPaint(1.0f, 2.0f, Color.RED, + 3.0f, 4.0f, Color.BLUE)); + assertEquals(chart1, chart2); + +// // backgroundImage +// chart1.setBackgroundImage(JFreeChart.INFO.getLogo()); +// assertFalse(chart1.equals(chart2)); +// chart2.setBackgroundImage(JFreeChart.INFO.getLogo()); +// assertEquals(chart1, chart2); + + // backgroundImageAlignment + chart1.setBackgroundImageAlignment(Align.BOTTOM_LEFT); + assertNotEquals(chart1, chart2); + chart2.setBackgroundImageAlignment(Align.BOTTOM_LEFT); + assertEquals(chart1, chart2); + + // backgroundImageAlpha + chart1.setBackgroundImageAlpha(0.1f); + assertNotEquals(chart1, chart2); + chart2.setBackgroundImageAlpha(0.1f); + assertEquals(chart1, chart2); + } + + /** + * A test to make sure that the legend is being picked up in the + * equals() testing. + */ + @Test + public void testEquals2() { + JFreeChart chart1 = new JFreeChart("Title", + new Font("SansSerif", Font.PLAIN, 12), new PiePlot(), true); + JFreeChart chart2 = new JFreeChart("Title", + new Font("SansSerif", Font.PLAIN, 12), new PiePlot(), false); + assertNotEquals(chart1, chart2); + assertNotEquals(chart2, chart1); + } + + /** + * Checks the subtitle count - should be 1 (the legend). + */ + @Test + public void testSubtitleCount() { + int count = this.pieChart.getSubtitleCount(); + assertEquals(1, count); + } + + /** + * Some checks for the getSubtitle() method. + */ + @Test + public void testGetSubtitle() { + DefaultPieDataset dataset = new DefaultPieDataset<>(); + JFreeChart chart = ChartFactory.createPieChart("title", dataset); + Title t = chart.getSubtitle(0); + assertTrue(t instanceof LegendTitle); + + try { + chart.getSubtitle(-1); + fail("Should have thrown an IllegalArgumentException on negative number"); + } + catch (IllegalArgumentException e) { + assertEquals("Index out of range.", e.getMessage()); + } + + try { + chart.getSubtitle(1); + fail("Should have thrown an IllegalArgumentException on excesive number"); + } + catch (IllegalArgumentException e) { + assertEquals("Index out of range.", e.getMessage()); + } + + try { + chart.getSubtitle(2); + fail("Should have thrown an IllegalArgumentException on number being out of range"); + } + catch (IllegalArgumentException e) { + assertEquals("Index out of range.", e.getMessage()); + } + + } + + /** + * Serialize a pie chart, restore it, and check for equality. + */ + @Test + public void testSerialization1() { + DefaultPieDataset data = new DefaultPieDataset<>(); + data.setValue("Type 1", 54.5); + data.setValue("Type 2", 23.9); + data.setValue("Type 3", 45.8); + + JFreeChart c1 = ChartFactory.createPieChart("Test", data); + JFreeChart c2 = TestUtils.serialised(c1); + assertEquals(c1, c2); + LegendTitle lt2 = c2.getLegend(); + assertSame(lt2.getSources()[0], c2.getPlot()); + } + + /** + * Serialize a bar chart, restore it, and check for equality. + */ + @Test + public void testSerialization3() { + + // row keys... + String series1 = "First"; + String series2 = "Second"; + String series3 = "Third"; + + // column keys... + String category1 = "Category 1"; + String category2 = "Category 2"; + String category3 = "Category 3"; + String category4 = "Category 4"; + String category5 = "Category 5"; + String category6 = "Category 6"; + String category7 = "Category 7"; + String category8 = "Category 8"; + + // create the dataset... + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + + dataset.addValue(1.0, series1, category1); + dataset.addValue(4.0, series1, category2); + dataset.addValue(3.0, series1, category3); + dataset.addValue(5.0, series1, category4); + dataset.addValue(5.0, series1, category5); + dataset.addValue(7.0, series1, category6); + dataset.addValue(7.0, series1, category7); + dataset.addValue(8.0, series1, category8); + + dataset.addValue(5.0, series2, category1); + dataset.addValue(7.0, series2, category2); + dataset.addValue(6.0, series2, category3); + dataset.addValue(8.0, series2, category4); + dataset.addValue(4.0, series2, category5); + dataset.addValue(4.0, series2, category6); + dataset.addValue(2.0, series2, category7); + dataset.addValue(1.0, series2, category8); + + dataset.addValue(4.0, series3, category1); + dataset.addValue(3.0, series3, category2); + dataset.addValue(2.0, series3, category3); + dataset.addValue(3.0, series3, category4); + dataset.addValue(6.0, series3, category5); + dataset.addValue(3.0, series3, category6); + dataset.addValue(4.0, series3, category7); + dataset.addValue(3.0, series3, category8); + + // create the chart... + JFreeChart c1 = ChartFactory.createBarChart("Vertical Bar Chart", + "Category", "Value", dataset); + JFreeChart c2 = TestUtils.serialised(c1); + assertEquals(c1, c2); + } + + /** + * Serialize a time seroes chart, restore it, and check for equality. + */ + @Test + public void testSerialization4() { + + RegularTimePeriod t = new Day(); + TimeSeries series = new TimeSeries("Series 1"); + series.add(t, 36.4); + t = t.next(); + series.add(t, 63.5); + TimeSeriesCollection dataset = new TimeSeriesCollection(); + dataset.addSeries(series); + + JFreeChart c1 = ChartFactory.createTimeSeriesChart("Test", "Date", + "Value", dataset); + JFreeChart c2 = TestUtils.serialised(c1); + assertEquals(c1, c2); + } + + /** + * Some checks for the addSubtitle() methods. + */ + @Test + public void testAddSubtitle() { + DefaultPieDataset dataset = new DefaultPieDataset<>(); + JFreeChart chart = ChartFactory.createPieChart("title", dataset); + + TextTitle t0 = new TextTitle("T0"); + chart.addSubtitle(0, t0); + assertEquals(t0, chart.getSubtitle(0)); + + TextTitle t1 = new TextTitle("T1"); + chart.addSubtitle(t1); + assertEquals(t1, chart.getSubtitle(2)); // subtitle 1 is the legend + + try { + chart.addSubtitle(null); + fail("Should have thrown an IllegalArgumentException on index out of range"); + } + catch (IllegalArgumentException e) { + assertEquals("Null 'subtitle' argument.", e.getMessage()); + } + + try { + chart.addSubtitle(-1, t0); + fail("Should have thrown an IllegalArgumentException on index out of range"); + } + catch (IllegalArgumentException e) { + assertEquals("The 'index' argument is out of range.", e.getMessage()); + } + + try { + chart.addSubtitle(4, t0); + fail("Should have thrown an IllegalArgumentException on index out of range"); + } + catch (IllegalArgumentException e) { + assertEquals("The 'index' argument is out of range.", e.getMessage()); + } + + } + + /** + * Some checks for the getSubtitles() method. + */ + @Test + public void testGetSubtitles() { + DefaultPieDataset dataset = new DefaultPieDataset<>(); + JFreeChart chart = ChartFactory.createPieChart("title", dataset); + List subtitles = chart.getSubtitles(); + + assertEquals(1, chart.getSubtitleCount()); + + // adding something to the returned list should NOT change the chart + subtitles.add(new TextTitle("T")); + assertEquals(1, chart.getSubtitleCount()); + } + + /** + * Some checks for the default legend firing change events. + */ + @Test + public void testLegendEvents() { + DefaultPieDataset dataset = new DefaultPieDataset<>(); + JFreeChart chart = ChartFactory.createPieChart("title", dataset); + chart.addChangeListener(this); + this.lastChartChangeEvent = null; + LegendTitle legend = chart.getLegend(); + legend.setPosition(RectangleEdge.TOP); + assertNotNull(this.lastChartChangeEvent); + } + + /** + * Some checks for title changes and event notification. + */ + @Test + public void testTitleChangeEvent() { + DefaultPieDataset dataset = new DefaultPieDataset(); + JFreeChart chart = ChartFactory.createPieChart("title", dataset); + chart.addChangeListener(this); + this.lastChartChangeEvent = null; + TextTitle t = chart.getTitle(); + t.setFont(new Font("Dialog", Font.BOLD, 9)); + assertNotNull(this.lastChartChangeEvent); + this.lastChartChangeEvent = null; + + // now create a new title and replace the existing title, several + // things should happen: + // (1) Adding the new title should trigger an immediate + // ChartChangeEvent; + // (2) Modifying the new title should trigger a ChartChangeEvent; + // (3) Modifying the old title should NOT trigger a ChartChangeEvent + TextTitle t2 = new TextTitle("T2"); + chart.setTitle(t2); + assertNotNull(this.lastChartChangeEvent); + this.lastChartChangeEvent = null; + + t2.setFont(new Font("Dialog", Font.BOLD, 9)); + assertNotNull(this.lastChartChangeEvent); + this.lastChartChangeEvent = null; + + t.setFont(new Font("Dialog", Font.BOLD, 9)); + assertNull(this.lastChartChangeEvent); + this.lastChartChangeEvent = null; + } + + @Test + public void testBug942() throws Exception { + final String title = "Pie Chart Demo 1\n\n\ntestnew line"; + assertEquals(title, ChartFactory.createPieChart(title, + new DefaultPieDataset()).getTitle().getText()); + } + + /** The last ChartChangeEvent received. */ + private ChartChangeEvent lastChartChangeEvent; + + /** + * Records the last chart change event. + * + * @param event the event. + */ + @Override + public void chartChanged(ChartChangeEvent event) { + this.lastChartChangeEvent = event; + } + +} diff --git a/src/test/java/org/jfree/chart/PieChart3DTest.java b/src/test/java/org/jfree/chart/PieChart3DTest.java deleted file mode 100644 index 041efdd26..000000000 --- a/src/test/java/org/jfree/chart/PieChart3DTest.java +++ /dev/null @@ -1,139 +0,0 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------- - * PieChart3DTest.java - * ------------------- - * (C) Copyright 2004-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.chart; - -import java.awt.Graphics2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; - -import org.jfree.chart.event.ChartChangeEvent; -import org.jfree.chart.event.ChartChangeListener; -import org.jfree.chart.plot.PiePlot; -import org.jfree.data.general.DefaultPieDataset; -import org.jfree.data.general.PieDataset; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests for a pie chart with a 3D effect. - */ -public class PieChart3DTest { - - /** A chart. */ - private JFreeChart pieChart; - - /** - * Common test setup. - */ - @BeforeEach - public void setUp() { - // create a dataset... - DefaultPieDataset dataset = new DefaultPieDataset(); - dataset.setValue("Java", 43.2); - dataset.setValue("Visual Basic", 0.0); - dataset.setValue("C/C++", 17.5); - this.pieChart = createPieChart3D(dataset); - } - - /** - * Using a regular pie chart, we replace the dataset with null. Expect to - * receive notification of a chart change event, and (of course) the - * dataset should be null. - */ - @Test - public void testReplaceDatasetOnPieChart() { - LocalListener l = new LocalListener(); - this.pieChart.addChangeListener(l); - PiePlot plot = (PiePlot) this.pieChart.getPlot(); - plot.setDataset(null); - assertTrue(l.flag); - assertNull(plot.getDataset()); - } - - /** - * Tests that no exceptions are thrown when there is a {@code null} - * value in the dataset. - */ - @Test - public void testNullValueInDataset() { - DefaultPieDataset dataset = new DefaultPieDataset(); - dataset.setValue("Section 1", 10.0); - dataset.setValue("Section 2", 11.0); - dataset.setValue("Section 3", null); - JFreeChart chart = createPieChart3D(dataset); - BufferedImage image = new BufferedImage(200 , 100, - BufferedImage.TYPE_INT_RGB); - Graphics2D g2 = image.createGraphics(); - chart.draw(g2, new Rectangle2D.Double(0, 0, 200, 100), null, null); - g2.dispose(); - //FIXME we should really assert a value here - } - - /** - * Creates a pie chart. - * - * @param dataset the dataset. - * - * @return The pie chart. - */ - private static JFreeChart createPieChart3D(PieDataset dataset) { - return ChartFactory.createPieChart3D("Pie Chart", dataset); - } - - /** - * A chart change listener. - */ - static class LocalListener implements ChartChangeListener { - - /** A flag. */ - private boolean flag; - - /** - * Event handler. - * - * @param event the event. - */ - @Override - public void chartChanged(ChartChangeEvent event) { - this.flag = true; - } - - } - -} From e5d8abdfd56f24d433f0931f2255c0619626c1d1 Mon Sep 17 00:00:00 2001 From: tracylynne99 Date: Mon, 20 Mar 2023 11:31:47 -0500 Subject: [PATCH 3/4] Removed unused imports, changed javadocs to reference the function that gets called directly (ie: fireXyzChange) instead of the event that gets pushed when the fireXyzChange method is called. --- .../jfree/chart/annotations/Annotation.java | 7 +- .../annotations/CategoryLineAnnotation.java | 25 +- .../CategoryPointerAnnotation.java | 1107 +- .../annotations/CategoryTextAnnotation.java | 11 +- .../chart/annotations/TextAnnotation.java | 737 +- .../chart/annotations/XYNoteAnnotation.java | 919 +- .../annotations/XYPointerAnnotation.java | 1145 +- .../chart/annotations/XYTextAnnotation.java | 1319 +- .../chart/annotations/XYTitleAnnotation.java | 7 +- .../org/jfree/chart/axis/CategoryAxis.java | 53 +- .../java/org/jfree/chart/axis/DateAxis.java | 50 +- .../java/org/jfree/chart/axis/LogAxis.java | 2074 ++- .../java/org/jfree/chart/axis/PeriodAxis.java | 40 +- .../java/org/jfree/chart/axis/ValueAxis.java | 115 +- .../org/jfree/chart/block/BlockFrame.java | 3 +- .../org/jfree/chart/editor/ChartEditor.java | 4 +- .../chart/entity/CategoryLabelEntity.java | 289 +- .../org/jfree/chart/entity/FlowEntity.java | 266 +- .../org/jfree/chart/entity/NodeEntity.java | 193 +- .../chart/event/AnnotationChangeListener.java | 110 +- .../chart/event/MarkerChangeListener.java | 4 +- .../jfree/chart/event/OverlayChangeEvent.java | 111 +- .../chart/event/OverlayChangeListener.java | 107 +- .../imagemap/URLTagFragmentGenerator.java | 11 +- .../labels/BoxAndWhiskerToolTipGenerator.java | 3 +- .../BoxAndWhiskerXYToolTipGenerator.java | 3 +- .../labels/BubbleXYItemLabelGenerator.java | 6 +- .../labels/IntervalXYItemLabelGenerator.java | 3 +- .../labels/IntervalXYToolTipGenerator.java | 515 +- .../labels/PieSectionLabelGenerator.java | 17 - .../StandardPieSectionLabelGenerator.java | 24 +- .../jfree/chart/panel/AbstractOverlay.java | 222 +- .../jfree/chart/panel/CrosshairOverlay.java | 1189 +- .../chart/plot/CategoryCrosshairState.java | 7 +- .../org/jfree/chart/plot/CategoryPlot.java | 9846 +++++++------- .../org/jfree/chart/plot/CompassPlot.java | 13 +- .../org/jfree/chart/plot/FastScatterPlot.java | 2149 ++-- .../java/org/jfree/chart/plot/MeterPlot.java | 75 +- .../org/jfree/chart/plot/MultiplePiePlot.java | 19 +- .../java/org/jfree/chart/plot/Pannable.java | 4 +- .../java/org/jfree/chart/plot/PiePlot.java | 161 +- src/main/java/org/jfree/chart/plot/Plot.java | 3034 +++-- .../java/org/jfree/chart/plot/PolarPlot.java | 4092 +++--- .../org/jfree/chart/plot/SpiderWebPlot.java | 156 +- .../org/jfree/chart/plot/ThermometerPlot.java | 88 +- .../org/jfree/chart/plot/WaferMapPlot.java | 11 +- .../jfree/chart/plot/XYCrosshairState.java | 6 +- .../java/org/jfree/chart/plot/XYPlot.java | 10708 ++++++++-------- .../java/org/jfree/chart/plot/Zoomable.java | 4 +- .../org/jfree/chart/plot/dial/DialFrame.java | 127 +- .../org/jfree/chart/plot/dial/DialLayer.java | 223 +- .../org/jfree/chart/plot/dial/DialPlot.java | 1632 ++- .../chart/renderer/AbstractRenderer.java | 7 +- .../renderer/DefaultPolarItemRenderer.java | 1846 ++- .../org/jfree/chart/renderer/PaintScale.java | 4 +- .../chart/renderer/PolarItemRenderer.java | 409 +- .../AbstractCategoryItemRenderer.java | 61 +- .../chart/renderer/category/AreaRenderer.java | 3 +- .../chart/renderer/category/BarRenderer.java | 40 +- .../category/BoxAndWhiskerRenderer.java | 2230 ++-- .../category/CategoryItemRenderer.java | 243 +- .../category/CategoryStepRenderer.java | 9 +- .../category/DefaultCategoryItemRenderer.java | 6 +- .../renderer/category/GanttRenderer.java | 13 +- .../category/GroupedStackedBarRenderer.java | 11 +- .../renderer/category/LevelRenderer.java | 21 +- .../category/LineAndShapeRenderer.java | 109 +- .../category/MinMaxCategoryRenderer.java | 19 +- .../renderer/category/ScatterRenderer.java | 25 +- .../category/StackedAreaRenderer.java | 5 +- .../renderer/category/StackedBarRenderer.java | 5 +- .../category/StatisticalBarRenderer.java | 21 +- .../StatisticalLineAndShapeRenderer.java | 7 +- .../category/WaterfallBarRenderer.java | 15 +- .../renderer/xy/AbstractXYItemRenderer.java | 3332 +++-- .../renderer/xy/CandlestickRenderer.java | 30 +- .../chart/renderer/xy/DeviationRenderer.java | 5 +- .../chart/renderer/xy/HighLowRenderer.java | 26 +- .../renderer/xy/StackedXYAreaRenderer.java | 7 +- .../renderer/xy/StackedXYAreaRenderer2.java | 4 +- .../renderer/xy/StackedXYBarRenderer.java | 835 +- .../renderer/xy/StandardXYItemRenderer.java | 25 +- .../chart/renderer/xy/XYAreaRenderer.java | 1426 +- .../chart/renderer/xy/XYAreaRenderer2.java | 841 +- .../chart/renderer/xy/XYBarRenderer.java | 45 +- .../chart/renderer/xy/XYBlockRenderer.java | 10 +- .../renderer/xy/XYBoxAndWhiskerRenderer.java | 15 +- .../chart/renderer/xy/XYDotRenderer.java | 11 +- .../chart/renderer/xy/XYErrorRenderer.java | 25 +- .../chart/renderer/xy/XYItemRenderer.java | 437 +- .../renderer/xy/XYItemRendererState.java | 5 +- .../renderer/xy/XYLineAndShapeRenderer.java | 43 +- .../chart/renderer/xy/XYSplineRenderer.java | 984 +- .../chart/renderer/xy/XYStepAreaRenderer.java | 20 +- .../chart/renderer/xy/XYStepRenderer.java | 674 +- .../chart/renderer/xy/YIntervalRenderer.java | 8 +- .../chart/title/LegendItemBlockContainer.java | 3 +- .../java/org/jfree/chart/title/TextTitle.java | 26 +- .../chart/urls/CustomPieURLGenerator.java | 3 +- .../jfree/data/ComparableObjectSeries.java | 76 +- src/main/java/org/jfree/data/DataUtils.java | 3 +- .../data/category/DefaultCategoryDataset.java | 32 +- .../data/category/SlidingCategoryDataset.java | 7 +- .../gantt/SlidingGanttCategoryDataset.java | 7 +- .../org/jfree/data/gantt/XYTaskDataset.java | 6 +- .../jfree/data/general/DefaultPieDataset.java | 3 +- .../org/jfree/data/general/SeriesDataset.java | 16 +- .../jfree/data/jdbc/JDBCCategoryDataset.java | 4 +- .../org/jfree/data/jdbc/JDBCPieDataset.java | 8 +- .../org/jfree/data/jdbc/JDBCXYDataset.java | 7 - .../java/org/jfree/data/json/JSONUtils.java | 388 +- .../DefaultBoxAndWhiskerCategoryDataset.java | 19 +- .../DefaultBoxAndWhiskerXYDataset.java | 4 +- .../DefaultMultiValueCategoryDataset.java | 816 +- .../DefaultStatisticalCategoryDataset.java | 19 +- .../data/statistics/HistogramDataset.java | 6 +- .../org/jfree/data/time/TimePeriodValues.java | 19 +- .../java/org/jfree/data/time/TimeSeries.java | 2628 ++-- .../data/time/ohlc/OHLCSeriesCollection.java | 5 +- .../jfree/data/xy/CategoryTableXYDataset.java | 825 +- .../jfree/data/xy/DefaultTableXYDataset.java | 18 +- .../org/jfree/data/xy/IntervalXYDelegate.java | 4 +- .../java/org/jfree/data/xy/VectorSeries.java | 11 +- .../jfree/data/xy/VectorSeriesCollection.java | 11 +- .../org/jfree/data/xy/XIntervalSeries.java | 29 +- .../data/xy/XIntervalSeriesCollection.java | 17 +- .../org/jfree/data/xy/XYIntervalSeries.java | 25 +- .../data/xy/XYIntervalSeriesCollection.java | 17 +- src/main/java/org/jfree/data/xy/XYSeries.java | 204 +- .../org/jfree/data/xy/XYSeriesCollection.java | 1503 +-- .../org/jfree/data/xy/YIntervalSeries.java | 29 +- .../data/xy/YIntervalSeriesCollection.java | 20 +- .../jfree/data/general/DatasetUtilsTest.java | 2930 +++-- .../general/TestIntervalCategoryDataset.java | 856 +- .../java/org/jfree/data/xy/XYSeriesTest.java | 1616 +-- 135 files changed, 34292 insertions(+), 34889 deletions(-) diff --git a/src/main/java/org/jfree/chart/annotations/Annotation.java b/src/main/java/org/jfree/chart/annotations/Annotation.java index 59b32d538..ab7203e46 100644 --- a/src/main/java/org/jfree/chart/annotations/Annotation.java +++ b/src/main/java/org/jfree/chart/annotations/Annotation.java @@ -36,13 +36,12 @@ package org.jfree.chart.annotations; -import org.jfree.chart.event.AnnotationChangeEvent; import org.jfree.chart.event.AnnotationChangeListener; /** - * The base interface for annotations. All annotations should support the - * {@link AnnotationChangeEvent} mechanism by allowing listeners to register - * and receive notification of any changes to the annotation. + * The base interface for annotations. All annotations should support + * {@link AnnotationChangeListener} via the addChangeListener and + * removeChangeListener methods. */ public interface Annotation { diff --git a/src/main/java/org/jfree/chart/annotations/CategoryLineAnnotation.java b/src/main/java/org/jfree/chart/annotations/CategoryLineAnnotation.java index 3063821fe..819cecf99 100644 --- a/src/main/java/org/jfree/chart/annotations/CategoryLineAnnotation.java +++ b/src/main/java/org/jfree/chart/annotations/CategoryLineAnnotation.java @@ -53,7 +53,6 @@ import org.jfree.chart.axis.CategoryAnchor; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.AnnotationChangeEvent; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.PlotOrientation; @@ -133,8 +132,8 @@ public Comparable getCategory1() { } /** - * Sets the category for the start of the line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the category for the start of the line and calls + * {@link #fireAnnotationChanged()}. * * @param category the category ({@code null} not permitted). * @@ -158,8 +157,8 @@ public double getValue1() { } /** - * Sets the y-value for the start of the line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the y-value for the start of the line and calls + * {@link #fireAnnotationChanged()}. * * @param value the value (must be finite). * @@ -183,8 +182,8 @@ public Comparable getCategory2() { } /** - * Sets the category for the end of the line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the category for the end of the line and calls + * {@link #fireAnnotationChanged()}. * * @param category the category ({@code null} not permitted). * @@ -208,8 +207,8 @@ public double getValue2() { } /** - * Sets the y-value for the end of the line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the y-value for the end of the line and calls + * {@link #fireAnnotationChanged()}. * * @param value the value (must be finite). * @@ -233,8 +232,8 @@ public Paint getPaint() { } /** - * Sets the paint used to draw the connecting line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the paint used to draw the connecting line and calls + * {@link #fireAnnotationChanged()}. * * @param paint the paint ({@code null} not permitted). * @@ -258,8 +257,8 @@ public Stroke getStroke() { } /** - * Sets the stroke used to draw the connecting line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the stroke used to draw the connecting line and calls + * {@link #fireAnnotationChanged()}. * * @param stroke the stroke ({@code null} not permitted). * diff --git a/src/main/java/org/jfree/chart/annotations/CategoryPointerAnnotation.java b/src/main/java/org/jfree/chart/annotations/CategoryPointerAnnotation.java index e20c97adf..974e36432 100644 --- a/src/main/java/org/jfree/chart/annotations/CategoryPointerAnnotation.java +++ b/src/main/java/org/jfree/chart/annotations/CategoryPointerAnnotation.java @@ -1,557 +1,550 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------------------ - * CategoryPointerAnnotation.java - * ------------------------------ - * (C) Copyright 2006-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): Peter Kolb (patch 2809117); - * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); - * - */ - -package org.jfree.chart.annotations; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Stroke; -import java.awt.geom.GeneralPath; -import java.awt.geom.Line2D; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Objects; - -import org.jfree.chart.HashUtils; -import org.jfree.chart.axis.CategoryAxis; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.AnnotationChangeEvent; -import org.jfree.chart.plot.CategoryPlot; -import org.jfree.chart.plot.Plot; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.text.TextUtils; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PaintUtils; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.SerialUtils; -import org.jfree.data.category.CategoryDataset; - -/** - * An arrow and label that can be placed on a {@link CategoryPlot}. The arrow - * is drawn at a user-definable angle so that it points towards the (category, - * value) location for the annotation. - *

- * The arrow length (and its offset from the (category, value) location) is - * controlled by the tip radius and the base radius attributes. Imagine two - * circles around the (category, value) coordinate: the inner circle defined by - * the tip radius, and the outer circle defined by the base radius. Now, draw - * the arrow starting at some point on the outer circle (the point is - * determined by the angle), with the arrow tip being drawn at a corresponding - * point on the inner circle. - */ -public class CategoryPointerAnnotation extends CategoryTextAnnotation - implements Cloneable, PublicCloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = -4031161445009858551L; - - /** The default tip radius (in Java2D units). */ - public static final double DEFAULT_TIP_RADIUS = 10.0; - - /** The default base radius (in Java2D units). */ - public static final double DEFAULT_BASE_RADIUS = 30.0; - - /** The default label offset (in Java2D units). */ - public static final double DEFAULT_LABEL_OFFSET = 3.0; - - /** The default arrow length (in Java2D units). */ - public static final double DEFAULT_ARROW_LENGTH = 5.0; - - /** The default arrow width (in Java2D units). */ - public static final double DEFAULT_ARROW_WIDTH = 3.0; - - /** The angle of the arrow's line (in radians). */ - private double angle; - - /** - * The radius from the (x, y) point to the tip of the arrow (in Java2D - * units). - */ - private double tipRadius; - - /** - * The radius from the (x, y) point to the start of the arrow line (in - * Java2D units). - */ - private double baseRadius; - - /** The length of the arrow head (in Java2D units). */ - private double arrowLength; - - /** The arrow width (in Java2D units, per side). */ - private double arrowWidth; - - /** The arrow stroke. */ - private transient Stroke arrowStroke; - - /** The arrow paint. */ - private transient Paint arrowPaint; - - /** The radius from the base point to the anchor point for the label. */ - private double labelOffset; - - /** - * Creates a new label and arrow annotation. - * - * @param label the label ({@code null} permitted). - * @param key the category key. - * @param value the y-value (measured against the chart's range axis). - * @param angle the angle of the arrow's line (in radians). - */ - public CategoryPointerAnnotation(String label, Comparable key, double value, - double angle) { - - super(label, key, value); - this.angle = angle; - this.tipRadius = DEFAULT_TIP_RADIUS; - this.baseRadius = DEFAULT_BASE_RADIUS; - this.arrowLength = DEFAULT_ARROW_LENGTH; - this.arrowWidth = DEFAULT_ARROW_WIDTH; - this.labelOffset = DEFAULT_LABEL_OFFSET; - this.arrowStroke = new BasicStroke(1.0f); - this.arrowPaint = Color.BLACK; - - } - - /** - * Returns the angle of the arrow. - * - * @return The angle (in radians). - * - * @see #setAngle(double) - */ - public double getAngle() { - return this.angle; - } - - /** - * Sets the angle of the arrow and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param angle the angle (in radians). - * - * @see #getAngle() - */ - public void setAngle(double angle) { - this.angle = angle; - fireAnnotationChanged(); - } - - /** - * Returns the tip radius. - * - * @return The tip radius (in Java2D units). - * - * @see #setTipRadius(double) - */ - public double getTipRadius() { - return this.tipRadius; - } - - /** - * Sets the tip radius and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param radius the radius (in Java2D units). - * - * @see #getTipRadius() - */ - public void setTipRadius(double radius) { - this.tipRadius = radius; - fireAnnotationChanged(); - } - - /** - * Returns the base radius. - * - * @return The base radius (in Java2D units). - * - * @see #setBaseRadius(double) - */ - public double getBaseRadius() { - return this.baseRadius; - } - - /** - * Sets the base radius and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param radius the radius (in Java2D units). - * - * @see #getBaseRadius() - */ - public void setBaseRadius(double radius) { - this.baseRadius = radius; - fireAnnotationChanged(); - } - - /** - * Returns the label offset. - * - * @return The label offset (in Java2D units). - * - * @see #setLabelOffset(double) - */ - public double getLabelOffset() { - return this.labelOffset; - } - - /** - * Sets the label offset (from the arrow base, continuing in a straight - * line, in Java2D units) and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param offset the offset (in Java2D units). - * - * @see #getLabelOffset() - */ - public void setLabelOffset(double offset) { - this.labelOffset = offset; - fireAnnotationChanged(); - } - - /** - * Returns the arrow length. - * - * @return The arrow length. - * - * @see #setArrowLength(double) - */ - public double getArrowLength() { - return this.arrowLength; - } - - /** - * Sets the arrow length and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param length the length. - * - * @see #getArrowLength() - */ - public void setArrowLength(double length) { - this.arrowLength = length; - fireAnnotationChanged(); - } - - /** - * Returns the arrow width. - * - * @return The arrow width (in Java2D units). - * - * @see #setArrowWidth(double) - */ - public double getArrowWidth() { - return this.arrowWidth; - } - - /** - * Sets the arrow width and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param width the width (in Java2D units). - * - * @see #getArrowWidth() - */ - public void setArrowWidth(double width) { - this.arrowWidth = width; - fireAnnotationChanged(); - } - - /** - * Returns the stroke used to draw the arrow line. - * - * @return The arrow stroke (never {@code null}). - * - * @see #setArrowStroke(Stroke) - */ - public Stroke getArrowStroke() { - return this.arrowStroke; - } - - /** - * Sets the stroke used to draw the arrow line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getArrowStroke() - */ - public void setArrowStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.arrowStroke = stroke; - fireAnnotationChanged(); - } - - /** - * Returns the paint used for the arrow. - * - * @return The arrow paint (never {@code null}). - * - * @see #setArrowPaint(Paint) - */ - public Paint getArrowPaint() { - return this.arrowPaint; - } - - /** - * Sets the paint used for the arrow and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param paint the arrow paint ({@code null} not permitted). - * - * @see #getArrowPaint() - */ - public void setArrowPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.arrowPaint = paint; - fireAnnotationChanged(); - } - - /** - * Draws the annotation. - * - * @param g2 the graphics device. - * @param plot the plot. - * @param dataArea the data area. - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - */ - @Override - public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea, - CategoryAxis domainAxis, ValueAxis rangeAxis) { - - PlotOrientation orientation = plot.getOrientation(); - RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( - plot.getDomainAxisLocation(), orientation); - RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( - plot.getRangeAxisLocation(), orientation); - CategoryDataset dataset = plot.getDataset(); - int catIndex = dataset.getColumnIndex(getCategory()); - int catCount = dataset.getColumnCount(); - double j2DX = domainAxis.getCategoryMiddle(catIndex, catCount, - dataArea, domainEdge); - double j2DY = rangeAxis.valueToJava2D(getValue(), dataArea, rangeEdge); - if (orientation == PlotOrientation.HORIZONTAL) { - double temp = j2DX; - j2DX = j2DY; - j2DY = temp; - } - double startX = j2DX + Math.cos(this.angle) * this.baseRadius; - double startY = j2DY + Math.sin(this.angle) * this.baseRadius; - - double endX = j2DX + Math.cos(this.angle) * this.tipRadius; - double endY = j2DY + Math.sin(this.angle) * this.tipRadius; - - double arrowBaseX = endX + Math.cos(this.angle) * this.arrowLength; - double arrowBaseY = endY + Math.sin(this.angle) * this.arrowLength; - - double arrowLeftX = arrowBaseX - + Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth; - double arrowLeftY = arrowBaseY - + Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth; - - double arrowRightX = arrowBaseX - - Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth; - double arrowRightY = arrowBaseY - - Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth; - - GeneralPath arrow = new GeneralPath(); - arrow.moveTo((float) endX, (float) endY); - arrow.lineTo((float) arrowLeftX, (float) arrowLeftY); - arrow.lineTo((float) arrowRightX, (float) arrowRightY); - arrow.closePath(); - - g2.setStroke(this.arrowStroke); - g2.setPaint(this.arrowPaint); - Line2D line = new Line2D.Double(startX, startY, arrowBaseX, arrowBaseY); - g2.draw(line); - g2.fill(arrow); - - // draw the label - g2.setFont(getFont()); - g2.setPaint(getPaint()); - double labelX = j2DX - + Math.cos(this.angle) * (this.baseRadius + this.labelOffset); - double labelY = j2DY - + Math.sin(this.angle) * (this.baseRadius + this.labelOffset); - /* Rectangle2D hotspot = */ TextUtils.drawAlignedString(getText(), - g2, (float) labelX, (float) labelY, getTextAnchor()); - // TODO: implement the entity for the annotation - - } - - /** - * Tests this annotation for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return {@code true} or {@code false}. - */ - @Override - public boolean equals(Object obj) { - - if (obj == this) { - return true; - } - if (!(obj instanceof CategoryPointerAnnotation)) { - return false; - } - CategoryPointerAnnotation that = (CategoryPointerAnnotation) obj; - if (Double.doubleToLongBits(this.angle) != - Double.doubleToLongBits(that.angle)) { - return false; - } - if (Double.doubleToLongBits(this.tipRadius) != - Double.doubleToLongBits(that.tipRadius)) { - return false; - } - if (Double.doubleToLongBits(this.baseRadius) != - Double.doubleToLongBits(that.baseRadius)) { - return false; - } - if (Double.doubleToLongBits(this.arrowLength) != - Double.doubleToLongBits(that.arrowLength)) { - return false; - } - if (Double.doubleToLongBits(this.arrowWidth) != - Double.doubleToLongBits(that.arrowWidth)) { - return false; - } - if (!PaintUtils.equal(this.arrowPaint, that.arrowPaint)) { - return false; - } - if (!Objects.equals(this.arrowStroke, that.arrowStroke)) { - return false; - } - if (Double.doubleToLongBits(this.labelOffset) != - Double.doubleToLongBits(that.labelOffset)) { - return false; - } - // fix the "equals not symmetric" problem - if (!that.canEqual(this)) { - return false; - } - return super.equals(obj); - } - - /** - * Ensures symmetry between super/subclass implementations of equals. For - * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. - * - * @param other Object - * - * @return true ONLY if the parameter is THIS class type - */ - @Override - public boolean canEqual(Object other) { - // fix the "equals not symmetric" problem - return (other instanceof CategoryPointerAnnotation); - } - - /** - * Returns a hash code for this instance. - * - * @return A hash code. - */ - @Override - public int hashCode() { - int result = super.hashCode(); // equals calls superclass, hashCode must also - long temp = Double.doubleToLongBits(this.angle); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(this.tipRadius); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(this.baseRadius); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(this.arrowLength); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(this.arrowWidth); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - result = 37 * result + HashUtils.hashCodeForPaint(this.arrowPaint); - result = 37 * result + Objects.hashCode(this.arrowStroke); - temp = Double.doubleToLongBits(this.labelOffset); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Returns a clone of the annotation. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the annotation can't be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writePaint(this.arrowPaint, stream); - SerialUtils.writeStroke(this.arrowStroke, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - this.arrowPaint = SerialUtils.readPaint(stream); - this.arrowStroke = SerialUtils.readStroke(stream); - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------------------ + * CategoryPointerAnnotation.java + * ------------------------------ + * (C) Copyright 2006-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): Peter Kolb (patch 2809117); + * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); + * + */ + +package org.jfree.chart.annotations; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Stroke; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Objects; + +import org.jfree.chart.HashUtils; +import org.jfree.chart.axis.CategoryAxis; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.CategoryPlot; +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.text.TextUtils; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PaintUtils; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.SerialUtils; +import org.jfree.data.category.CategoryDataset; + +/** + * An arrow and label that can be placed on a {@link CategoryPlot}. The arrow + * is drawn at a user-definable angle so that it points towards the (category, + * value) location for the annotation. + *

+ * The arrow length (and its offset from the (category, value) location) is + * controlled by the tip radius and the base radius attributes. Imagine two + * circles around the (category, value) coordinate: the inner circle defined by + * the tip radius, and the outer circle defined by the base radius. Now, draw + * the arrow starting at some point on the outer circle (the point is + * determined by the angle), with the arrow tip being drawn at a corresponding + * point on the inner circle. + */ +public class CategoryPointerAnnotation extends CategoryTextAnnotation + implements Cloneable, PublicCloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = -4031161445009858551L; + + /** The default tip radius (in Java2D units). */ + public static final double DEFAULT_TIP_RADIUS = 10.0; + + /** The default base radius (in Java2D units). */ + public static final double DEFAULT_BASE_RADIUS = 30.0; + + /** The default label offset (in Java2D units). */ + public static final double DEFAULT_LABEL_OFFSET = 3.0; + + /** The default arrow length (in Java2D units). */ + public static final double DEFAULT_ARROW_LENGTH = 5.0; + + /** The default arrow width (in Java2D units). */ + public static final double DEFAULT_ARROW_WIDTH = 3.0; + + /** The angle of the arrow's line (in radians). */ + private double angle; + + /** + * The radius from the (x, y) point to the tip of the arrow (in Java2D + * units). + */ + private double tipRadius; + + /** + * The radius from the (x, y) point to the start of the arrow line (in + * Java2D units). + */ + private double baseRadius; + + /** The length of the arrow head (in Java2D units). */ + private double arrowLength; + + /** The arrow width (in Java2D units, per side). */ + private double arrowWidth; + + /** The arrow stroke. */ + private transient Stroke arrowStroke; + + /** The arrow paint. */ + private transient Paint arrowPaint; + + /** The radius from the base point to the anchor point for the label. */ + private double labelOffset; + + /** + * Creates a new label and arrow annotation. + * + * @param label the label ({@code null} permitted). + * @param key the category key. + * @param value the y-value (measured against the chart's range axis). + * @param angle the angle of the arrow's line (in radians). + */ + public CategoryPointerAnnotation(String label, Comparable key, double value, + double angle) { + + super(label, key, value); + this.angle = angle; + this.tipRadius = DEFAULT_TIP_RADIUS; + this.baseRadius = DEFAULT_BASE_RADIUS; + this.arrowLength = DEFAULT_ARROW_LENGTH; + this.arrowWidth = DEFAULT_ARROW_WIDTH; + this.labelOffset = DEFAULT_LABEL_OFFSET; + this.arrowStroke = new BasicStroke(1.0f); + this.arrowPaint = Color.BLACK; + + } + + /** + * Returns the angle of the arrow. + * + * @return The angle (in radians). + * + * @see #setAngle(double) + */ + public double getAngle() { + return this.angle; + } + + /** + * Sets the angle of the arrow and calls {@link #fireAnnotationChanged}. + * + * @param angle the angle (in radians). + * + * @see #getAngle() + */ + public void setAngle(double angle) { + this.angle = angle; + fireAnnotationChanged(); + } + + /** + * Returns the tip radius. + * + * @return The tip radius (in Java2D units). + * + * @see #setTipRadius(double) + */ + public double getTipRadius() { + return this.tipRadius; + } + + /** + * Sets the tip radius and calls {@link #fireAnnotationChanged}. + * + * @param radius the radius (in Java2D units). + * + * @see #getTipRadius() + */ + public void setTipRadius(double radius) { + this.tipRadius = radius; + fireAnnotationChanged(); + } + + /** + * Returns the base radius. + * + * @return The base radius (in Java2D units). + * + * @see #setBaseRadius(double) + */ + public double getBaseRadius() { + return this.baseRadius; + } + + /** + * Sets the base radius and calls {@link #fireAnnotationChanged}. + * + * @param radius the radius (in Java2D units). + * + * @see #getBaseRadius() + */ + public void setBaseRadius(double radius) { + this.baseRadius = radius; + fireAnnotationChanged(); + } + + /** + * Returns the label offset. + * + * @return The label offset (in Java2D units). + * + * @see #setLabelOffset(double) + */ + public double getLabelOffset() { + return this.labelOffset; + } + + /** + * Sets the label offset (from the arrow base, continuing in a straight + * line, in Java2D units) and calls {@link #fireAnnotationChanged}. + * + * @param offset the offset (in Java2D units). + * + * @see #getLabelOffset() + */ + public void setLabelOffset(double offset) { + this.labelOffset = offset; + fireAnnotationChanged(); + } + + /** + * Returns the arrow length. + * + * @return The arrow length. + * + * @see #setArrowLength(double) + */ + public double getArrowLength() { + return this.arrowLength; + } + + /** + * Sets the arrow length and calls {@link #fireAnnotationChanged}. + * + * @param length the length. + * + * @see #getArrowLength() + */ + public void setArrowLength(double length) { + this.arrowLength = length; + fireAnnotationChanged(); + } + + /** + * Returns the arrow width. + * + * @return The arrow width (in Java2D units). + * + * @see #setArrowWidth(double) + */ + public double getArrowWidth() { + return this.arrowWidth; + } + + /** + * Sets the arrow width and calls {@link #fireAnnotationChanged}. + * + * @param width the width (in Java2D units). + * + * @see #getArrowWidth() + */ + public void setArrowWidth(double width) { + this.arrowWidth = width; + fireAnnotationChanged(); + } + + /** + * Returns the stroke used to draw the arrow line. + * + * @return The arrow stroke (never {@code null}). + * + * @see #setArrowStroke(Stroke) + */ + public Stroke getArrowStroke() { + return this.arrowStroke; + } + + /** + * Sets the stroke used to draw the arrow line and calls + * {@link #fireAnnotationChanged}. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getArrowStroke() + */ + public void setArrowStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.arrowStroke = stroke; + fireAnnotationChanged(); + } + + /** + * Returns the paint used for the arrow. + * + * @return The arrow paint (never {@code null}). + * + * @see #setArrowPaint(Paint) + */ + public Paint getArrowPaint() { + return this.arrowPaint; + } + + /** + * Sets the paint used for the arrow and calls + * {@link #fireAnnotationChanged}. + * + * @param paint the arrow paint ({@code null} not permitted). + * + * @see #getArrowPaint() + */ + public void setArrowPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.arrowPaint = paint; + fireAnnotationChanged(); + } + + /** + * Draws the annotation. + * + * @param g2 the graphics device. + * @param plot the plot. + * @param dataArea the data area. + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + */ + @Override + public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea, + CategoryAxis domainAxis, ValueAxis rangeAxis) { + + PlotOrientation orientation = plot.getOrientation(); + RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( + plot.getDomainAxisLocation(), orientation); + RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( + plot.getRangeAxisLocation(), orientation); + CategoryDataset dataset = plot.getDataset(); + int catIndex = dataset.getColumnIndex(getCategory()); + int catCount = dataset.getColumnCount(); + double j2DX = domainAxis.getCategoryMiddle(catIndex, catCount, + dataArea, domainEdge); + double j2DY = rangeAxis.valueToJava2D(getValue(), dataArea, rangeEdge); + if (orientation == PlotOrientation.HORIZONTAL) { + double temp = j2DX; + j2DX = j2DY; + j2DY = temp; + } + double startX = j2DX + Math.cos(this.angle) * this.baseRadius; + double startY = j2DY + Math.sin(this.angle) * this.baseRadius; + + double endX = j2DX + Math.cos(this.angle) * this.tipRadius; + double endY = j2DY + Math.sin(this.angle) * this.tipRadius; + + double arrowBaseX = endX + Math.cos(this.angle) * this.arrowLength; + double arrowBaseY = endY + Math.sin(this.angle) * this.arrowLength; + + double arrowLeftX = arrowBaseX + + Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth; + double arrowLeftY = arrowBaseY + + Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth; + + double arrowRightX = arrowBaseX + - Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth; + double arrowRightY = arrowBaseY + - Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth; + + GeneralPath arrow = new GeneralPath(); + arrow.moveTo((float) endX, (float) endY); + arrow.lineTo((float) arrowLeftX, (float) arrowLeftY); + arrow.lineTo((float) arrowRightX, (float) arrowRightY); + arrow.closePath(); + + g2.setStroke(this.arrowStroke); + g2.setPaint(this.arrowPaint); + Line2D line = new Line2D.Double(startX, startY, arrowBaseX, arrowBaseY); + g2.draw(line); + g2.fill(arrow); + + // draw the label + g2.setFont(getFont()); + g2.setPaint(getPaint()); + double labelX = j2DX + + Math.cos(this.angle) * (this.baseRadius + this.labelOffset); + double labelY = j2DY + + Math.sin(this.angle) * (this.baseRadius + this.labelOffset); + /* Rectangle2D hotspot = */ TextUtils.drawAlignedString(getText(), + g2, (float) labelX, (float) labelY, getTextAnchor()); + // TODO: implement the entity for the annotation + + } + + /** + * Tests this annotation for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return {@code true} or {@code false}. + */ + @Override + public boolean equals(Object obj) { + + if (obj == this) { + return true; + } + if (!(obj instanceof CategoryPointerAnnotation)) { + return false; + } + CategoryPointerAnnotation that = (CategoryPointerAnnotation) obj; + if (Double.doubleToLongBits(this.angle) != + Double.doubleToLongBits(that.angle)) { + return false; + } + if (Double.doubleToLongBits(this.tipRadius) != + Double.doubleToLongBits(that.tipRadius)) { + return false; + } + if (Double.doubleToLongBits(this.baseRadius) != + Double.doubleToLongBits(that.baseRadius)) { + return false; + } + if (Double.doubleToLongBits(this.arrowLength) != + Double.doubleToLongBits(that.arrowLength)) { + return false; + } + if (Double.doubleToLongBits(this.arrowWidth) != + Double.doubleToLongBits(that.arrowWidth)) { + return false; + } + if (!PaintUtils.equal(this.arrowPaint, that.arrowPaint)) { + return false; + } + if (!Objects.equals(this.arrowStroke, that.arrowStroke)) { + return false; + } + if (Double.doubleToLongBits(this.labelOffset) != + Double.doubleToLongBits(that.labelOffset)) { + return false; + } + // fix the "equals not symmetric" problem + if (!that.canEqual(this)) { + return false; + } + return super.equals(obj); + } + + /** + * Ensures symmetry between super/subclass implementations of equals. For + * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. + * + * @param other Object + * + * @return true ONLY if the parameter is THIS class type + */ + @Override + public boolean canEqual(Object other) { + // fix the "equals not symmetric" problem + return (other instanceof CategoryPointerAnnotation); + } + + /** + * Returns a hash code for this instance. + * + * @return A hash code. + */ + @Override + public int hashCode() { + int result = super.hashCode(); // equals calls superclass, hashCode must also + long temp = Double.doubleToLongBits(this.angle); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(this.tipRadius); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(this.baseRadius); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(this.arrowLength); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(this.arrowWidth); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + result = 37 * result + HashUtils.hashCodeForPaint(this.arrowPaint); + result = 37 * result + Objects.hashCode(this.arrowStroke); + temp = Double.doubleToLongBits(this.labelOffset); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + /** + * Returns a clone of the annotation. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the annotation can't be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writePaint(this.arrowPaint, stream); + SerialUtils.writeStroke(this.arrowStroke, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + this.arrowPaint = SerialUtils.readPaint(stream); + this.arrowStroke = SerialUtils.readStroke(stream); + } + +} diff --git a/src/main/java/org/jfree/chart/annotations/CategoryTextAnnotation.java b/src/main/java/org/jfree/chart/annotations/CategoryTextAnnotation.java index 8a7fe862e..0ed49a82c 100644 --- a/src/main/java/org/jfree/chart/annotations/CategoryTextAnnotation.java +++ b/src/main/java/org/jfree/chart/annotations/CategoryTextAnnotation.java @@ -45,7 +45,6 @@ import org.jfree.chart.axis.CategoryAnchor; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.AnnotationChangeEvent; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.PlotOrientation; @@ -102,8 +101,8 @@ public Comparable getCategory() { } /** - * Sets the category that the annotation attaches to and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the category that the annotation attaches to and calls + * {@link #fireAnnotationChanged()}.. * * @param category the category ({@code null} not permitted). * @@ -127,8 +126,7 @@ public CategoryAnchor getCategoryAnchor() { } /** - * Sets the category anchor point and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the category anchor point and calls {@link #fireAnnotationChanged()}. * * @param anchor the anchor point ({@code null} not permitted). * @@ -152,8 +150,7 @@ public double getValue() { } /** - * Sets the value and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the value and calls {@link #fireAnnotationChanged()}. * * @param value the value. * diff --git a/src/main/java/org/jfree/chart/annotations/TextAnnotation.java b/src/main/java/org/jfree/chart/annotations/TextAnnotation.java index da0b93cc2..1a3e8d8f3 100644 --- a/src/main/java/org/jfree/chart/annotations/TextAnnotation.java +++ b/src/main/java/org/jfree/chart/annotations/TextAnnotation.java @@ -1,369 +1,368 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------- - * TextAnnotation.java - * ------------------- - * (C) Copyright 2002-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): Peter Kolb (patch 2809117); - * Martin Hoeller; - * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); - * - */ - -package org.jfree.chart.annotations; - -import java.awt.Color; -import java.awt.Font; -import java.awt.Paint; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Objects; - -import org.jfree.chart.HashUtils; -import org.jfree.chart.event.AnnotationChangeEvent; -import org.jfree.chart.ui.TextAnchor; -import org.jfree.chart.util.PaintUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.SerialUtils; - -/** - * A base class for text annotations. This class records the content but not - * the location of the annotation. - */ -public class TextAnnotation extends AbstractAnnotation implements Serializable { - - /** For serialization. */ - private static final long serialVersionUID = 7008912287533127432L; - - /** The default font. */ - public static final Font DEFAULT_FONT - = new Font("SansSerif", Font.PLAIN, 10); - - /** The default paint. */ - public static final Paint DEFAULT_PAINT = Color.BLACK; - - /** The default text anchor. */ - public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER; - - /** The default rotation anchor. */ - public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER; - - /** The default rotation angle. */ - public static final double DEFAULT_ROTATION_ANGLE = 0.0; - - /** The text. */ - private String text; - - /** The font. */ - private Font font; - - /** The paint. */ - private transient Paint paint; - - /** The text anchor. */ - private TextAnchor textAnchor; - - /** The rotation anchor. */ - private TextAnchor rotationAnchor; - - /** The rotation angle. */ - private double rotationAngle; - - /** - * Creates a text annotation with default settings. - * - * @param text the text ({@code null} not permitted). - */ - protected TextAnnotation(String text) { - super(); - Args.nullNotPermitted(text, "text"); - this.text = text; - this.font = DEFAULT_FONT; - this.paint = DEFAULT_PAINT; - this.textAnchor = DEFAULT_TEXT_ANCHOR; - this.rotationAnchor = DEFAULT_ROTATION_ANCHOR; - this.rotationAngle = DEFAULT_ROTATION_ANGLE; - } - - /** - * Returns the text for the annotation. - * - * @return The text (never {@code null}). - * - * @see #setText(String) - */ - public String getText() { - return this.text; - } - - /** - * Sets the text for the annotation and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param text the text ({@code null} not permitted). - * - * @see #getText() - */ - public void setText(String text) { - Args.nullNotPermitted(text, "text"); - this.text = text; - fireAnnotationChanged(); - } - - /** - * Returns the font for the annotation. - * - * @return The font (never {@code null}). - * - * @see #setFont(Font) - */ - public Font getFont() { - return this.font; - } - - /** - * Sets the font for the annotation and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param font the font ({@code null} not permitted). - * - * @see #getFont() - */ - public void setFont(Font font) { - Args.nullNotPermitted(font, "font"); - this.font = font; - fireAnnotationChanged(); - } - - /** - * Returns the paint for the annotation. - * - * @return The paint (never {@code null}). - * - * @see #setPaint(Paint) - */ - public Paint getPaint() { - return this.paint; - } - - /** - * Sets the paint for the annotation and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getPaint() - */ - public void setPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.paint = paint; - fireAnnotationChanged(); - } - - /** - * Returns the text anchor. - * - * @return The text anchor. - * - * @see #setTextAnchor(TextAnchor) - */ - public TextAnchor getTextAnchor() { - return this.textAnchor; - } - - /** - * Sets the text anchor (the point on the text bounding rectangle that is - * aligned to the (x, y) coordinate of the annotation) and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param anchor the anchor point ({@code null} not permitted). - * - * @see #getTextAnchor() - */ - public void setTextAnchor(TextAnchor anchor) { - Args.nullNotPermitted(anchor, "anchor"); - this.textAnchor = anchor; - fireAnnotationChanged(); - } - - /** - * Returns the rotation anchor. - * - * @return The rotation anchor point (never {@code null}). - * - * @see #setRotationAnchor(TextAnchor) - */ - public TextAnchor getRotationAnchor() { - return this.rotationAnchor; - } - - /** - * Sets the rotation anchor point and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param anchor the anchor ({@code null} not permitted). - * - * @see #getRotationAnchor() - */ - public void setRotationAnchor(TextAnchor anchor) { - Args.nullNotPermitted(anchor, "anchor"); - this.rotationAnchor = anchor; - fireAnnotationChanged(); - } - - /** - * Returns the rotation angle in radians. - * - * @return The rotation angle. - * - * @see #setRotationAngle(double) - */ - public double getRotationAngle() { - return this.rotationAngle; - } - - /** - * Sets the rotation angle and sends an {@link AnnotationChangeEvent} to - * all registered listeners. The angle is measured clockwise in radians. - * - * @param angle the angle (in radians). - * - * @see #getRotationAngle() - */ - public void setRotationAngle(double angle) { - this.rotationAngle = angle; - fireAnnotationChanged(); - } - - /** - * Tests this object for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return {@code true} or {@code false}. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - // now try to reject equality... - if (!(obj instanceof TextAnnotation)) { - return false; - } - TextAnnotation that = (TextAnnotation) obj; - if (!Objects.equals(this.text, that.getText())) { - return false; - } - if (!Objects.equals(this.font, that.getFont())) { - return false; - } - if (!PaintUtils.equal(this.paint, that.getPaint())) { - return false; - } - if (!Objects.equals(this.textAnchor, that.getTextAnchor())) { - return false; - } - if (!Objects.equals(this.rotationAnchor, that.getRotationAnchor())) { - return false; - } - if (Double.doubleToLongBits(this.rotationAngle) != - Double.doubleToLongBits(that.rotationAngle)) { - return false; - } - - // fix the "equals not symmetric" problem - if (!that.canEqual(this)) { - return false; - } - - return super.equals(obj); - } - - /** - * Ensures symmetry between super/subclass implementations of equals. For - * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. - * - * @param other Object - * - * @return true ONLY if the parameter is THIS class type - */ - @Override - public boolean canEqual(Object other) { - // fix the "equals not symmetric" problem - return (other instanceof TextAnnotation); - } - - /** - * Returns a hash code for this instance. - * - * @return A hash code. - */ - @Override - public int hashCode() { - int result = super.hashCode(); // equals calls superclass, hashCode must also - result = 37 * result + Objects.hashCode(this.font); - result = 37 * result + HashUtils.hashCodeForPaint(this.paint); - result = 37 * result + Objects.hashCode(this.rotationAnchor); - long temp = Double.doubleToLongBits(this.rotationAngle); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - result = 37 * result + Objects.hashCode(this.text); - result = 37 * result + Objects.hashCode(this.textAnchor); - return result; - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writePaint(this.paint, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - this.paint = SerialUtils.readPaint(stream); - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------- + * TextAnnotation.java + * ------------------- + * (C) Copyright 2002-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): Peter Kolb (patch 2809117); + * Martin Hoeller; + * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); + * + */ + +package org.jfree.chart.annotations; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Paint; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Objects; + +import org.jfree.chart.HashUtils; +import org.jfree.chart.ui.TextAnchor; +import org.jfree.chart.util.PaintUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.SerialUtils; + +/** + * A base class for text annotations. This class records the content but not + * the location of the annotation. + */ +public class TextAnnotation extends AbstractAnnotation implements Serializable { + + /** For serialization. */ + private static final long serialVersionUID = 7008912287533127432L; + + /** The default font. */ + public static final Font DEFAULT_FONT + = new Font("SansSerif", Font.PLAIN, 10); + + /** The default paint. */ + public static final Paint DEFAULT_PAINT = Color.BLACK; + + /** The default text anchor. */ + public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER; + + /** The default rotation anchor. */ + public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER; + + /** The default rotation angle. */ + public static final double DEFAULT_ROTATION_ANGLE = 0.0; + + /** The text. */ + private String text; + + /** The font. */ + private Font font; + + /** The paint. */ + private transient Paint paint; + + /** The text anchor. */ + private TextAnchor textAnchor; + + /** The rotation anchor. */ + private TextAnchor rotationAnchor; + + /** The rotation angle. */ + private double rotationAngle; + + /** + * Creates a text annotation with default settings. + * + * @param text the text ({@code null} not permitted). + */ + protected TextAnnotation(String text) { + super(); + Args.nullNotPermitted(text, "text"); + this.text = text; + this.font = DEFAULT_FONT; + this.paint = DEFAULT_PAINT; + this.textAnchor = DEFAULT_TEXT_ANCHOR; + this.rotationAnchor = DEFAULT_ROTATION_ANCHOR; + this.rotationAngle = DEFAULT_ROTATION_ANGLE; + } + + /** + * Returns the text for the annotation. + * + * @return The text (never {@code null}). + * + * @see #setText(String) + */ + public String getText() { + return this.text; + } + + /** + * Sets the text for the annotation and calls + * {@link #fireAnnotationChanged()}. + * + * @param text the text ({@code null} not permitted). + * + * @see #getText() + */ + public void setText(String text) { + Args.nullNotPermitted(text, "text"); + this.text = text; + fireAnnotationChanged(); + } + + /** + * Returns the font for the annotation. + * + * @return The font (never {@code null}). + * + * @see #setFont(Font) + */ + public Font getFont() { + return this.font; + } + + /** + * Sets the font for the annotation and calls + * {@link #fireAnnotationChanged()}. + * + * @param font the font ({@code null} not permitted). + * + * @see #getFont() + */ + public void setFont(Font font) { + Args.nullNotPermitted(font, "font"); + this.font = font; + fireAnnotationChanged(); + } + + /** + * Returns the paint for the annotation. + * + * @return The paint (never {@code null}). + * + * @see #setPaint(Paint) + */ + public Paint getPaint() { + return this.paint; + } + + /** + * Sets the paint for the annotation and calls + * {@link #fireAnnotationChanged()}. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getPaint() + */ + public void setPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.paint = paint; + fireAnnotationChanged(); + } + + /** + * Returns the text anchor. + * + * @return The text anchor. + * + * @see #setTextAnchor(TextAnchor) + */ + public TextAnchor getTextAnchor() { + return this.textAnchor; + } + + /** + * Sets the text anchor (the point on the text bounding rectangle that is + * aligned to the (x, y) coordinate of the annotation) and calls + * {@link #fireAnnotationChanged()}. + * + * @param anchor the anchor point ({@code null} not permitted). + * + * @see #getTextAnchor() + */ + public void setTextAnchor(TextAnchor anchor) { + Args.nullNotPermitted(anchor, "anchor"); + this.textAnchor = anchor; + fireAnnotationChanged(); + } + + /** + * Returns the rotation anchor. + * + * @return The rotation anchor point (never {@code null}). + * + * @see #setRotationAnchor(TextAnchor) + */ + public TextAnchor getRotationAnchor() { + return this.rotationAnchor; + } + + /** + * Sets the rotation anchor point and calls + * {@link #fireAnnotationChanged()}. + * + * @param anchor the anchor ({@code null} not permitted). + * + * @see #getRotationAnchor() + */ + public void setRotationAnchor(TextAnchor anchor) { + Args.nullNotPermitted(anchor, "anchor"); + this.rotationAnchor = anchor; + fireAnnotationChanged(); + } + + /** + * Returns the rotation angle in radians. + * + * @return The rotation angle. + * + * @see #setRotationAngle(double) + */ + public double getRotationAngle() { + return this.rotationAngle; + } + + /** + * Sets the rotation angle and calls {@link #fireAnnotationChanged()}. + * The angle is measured clockwise in radians. + * + * @param angle the angle (in radians). + * + * @see #getRotationAngle() + */ + public void setRotationAngle(double angle) { + this.rotationAngle = angle; + fireAnnotationChanged(); + } + + /** + * Tests this object for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return {@code true} or {@code false}. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + // now try to reject equality... + if (!(obj instanceof TextAnnotation)) { + return false; + } + TextAnnotation that = (TextAnnotation) obj; + if (!Objects.equals(this.text, that.getText())) { + return false; + } + if (!Objects.equals(this.font, that.getFont())) { + return false; + } + if (!PaintUtils.equal(this.paint, that.getPaint())) { + return false; + } + if (!Objects.equals(this.textAnchor, that.getTextAnchor())) { + return false; + } + if (!Objects.equals(this.rotationAnchor, that.getRotationAnchor())) { + return false; + } + if (Double.doubleToLongBits(this.rotationAngle) != + Double.doubleToLongBits(that.rotationAngle)) { + return false; + } + + // fix the "equals not symmetric" problem + if (!that.canEqual(this)) { + return false; + } + + return super.equals(obj); + } + + /** + * Ensures symmetry between super/subclass implementations of equals. For + * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. + * + * @param other Object + * + * @return true ONLY if the parameter is THIS class type + */ + @Override + public boolean canEqual(Object other) { + // fix the "equals not symmetric" problem + return (other instanceof TextAnnotation); + } + + /** + * Returns a hash code for this instance. + * + * @return A hash code. + */ + @Override + public int hashCode() { + int result = super.hashCode(); // equals calls superclass, hashCode must also + result = 37 * result + Objects.hashCode(this.font); + result = 37 * result + HashUtils.hashCodeForPaint(this.paint); + result = 37 * result + Objects.hashCode(this.rotationAnchor); + long temp = Double.doubleToLongBits(this.rotationAngle); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + result = 37 * result + Objects.hashCode(this.text); + result = 37 * result + Objects.hashCode(this.textAnchor); + return result; + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writePaint(this.paint, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + this.paint = SerialUtils.readPaint(stream); + } + +} diff --git a/src/main/java/org/jfree/chart/annotations/XYNoteAnnotation.java b/src/main/java/org/jfree/chart/annotations/XYNoteAnnotation.java index 9d88b9852..d95c443aa 100644 --- a/src/main/java/org/jfree/chart/annotations/XYNoteAnnotation.java +++ b/src/main/java/org/jfree/chart/annotations/XYNoteAnnotation.java @@ -1,462 +1,457 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------------------- - * XYNoteAnnotation.java - * --------------------- - * (C) Copyright 2003-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert (as XYPointerAnnotation.java); - * Contributor(s): Yuri Blankenstein (copy to XYNoteAnnotation.java; for ESI TNO); - * - */ -package org.jfree.chart.annotations; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Line2D; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Objects; - -import org.jfree.chart.HashUtils; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.AnnotationChangeEvent; -import org.jfree.chart.plot.Plot; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.text.TextUtils; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.SerialUtils; - -/** - * An line and label that can be placed on an {@link XYPlot}. The line is - * drawn at a user-definable angle so that it points towards the (x, y) - * location for the annotation. - *

- * The line length (and its offset from the (x, y) location) is controlled by - * the tip radius and the base radius attributes. Imagine two circles around - * the (x, y) coordinate: the inner circle defined by the tip radius, and the - * outer circle defined by the base radius. Now, draw the line starting at - * some point on the outer circle (the point is determined by the angle), with - * the line tip being drawn at a corresponding point on the inner circle. - */ -public class XYNoteAnnotation extends XYTextAnnotation - implements Cloneable, PublicCloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = -4031161445009858551L; - - /** The default tip radius (in Java2D units). */ - public static final double DEFAULT_TIP_RADIUS = 0.0; - - /** The default base radius (in Java2D units). */ - public static final double DEFAULT_BASE_RADIUS = 30.0; - - /** The default label offset (in Java2D units). */ - public static final double DEFAULT_LABEL_OFFSET = 3.0; - - /** The default line stroke. */ - public static final Stroke DEFAULT_LINE_STROKE = new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 2.0f, new float[] {2.0f}, 0.0f); - - /** The default line stroke. */ - public static final Paint DEFAULT_BACKGROUND_PAINT = new Color(255, 255, 203); - - /** The default line stroke. */ - public static final Paint DEFAULT_OUTLINE_PAINT = new Color(255, 204, 102); - - /** The angle of the line's line (in radians). */ - private double angle; - - /** - * The radius from the (x, y) point to the tip of the line (in Java2D - * units). - */ - private double tipRadius; - - /** - * The radius from the (x, y) point to the start of the line line (in - * Java2D units). - */ - private double baseRadius; - - /** The line stroke. */ - private transient Stroke lineStroke; - - /** The line paint. */ - private transient Paint linePaint; - - /** The radius from the base point to the anchor point for the label. */ - private double labelOffset; - - /** - * Creates a new label and line annotation. - * - * @param label the label ({@code null} permitted). - * @param x the x-coordinate (measured against the chart's domain axis). - * @param y the y-coordinate (measured against the chart's range axis). - * @param angle the angle of the line's line (in radians). - */ - public XYNoteAnnotation(String label, double x, double y, double angle) { - super(label, x, y); - this.angle = angle; - this.tipRadius = DEFAULT_TIP_RADIUS; - this.baseRadius = DEFAULT_BASE_RADIUS; - this.labelOffset = DEFAULT_LABEL_OFFSET; - this.lineStroke = DEFAULT_LINE_STROKE; - this.linePaint = Color.BLACK; - - setBackgroundPaint(DEFAULT_BACKGROUND_PAINT); - setOutlineVisible(true); - setOutlinePaint(DEFAULT_OUTLINE_PAINT); - setOutlineStroke(new BasicStroke(1.0f)); - } - - /** - * Returns the angle of the line. - * - * @return The angle (in radians). - * - * @see #setAngle(double) - */ - public double getAngle() { - return this.angle; - } - - /** - * Sets the angle of the line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param angle the angle (in radians). - * - * @see #getAngle() - */ - public void setAngle(double angle) { - this.angle = angle; - fireAnnotationChanged(); - } - - /** - * Returns the tip radius. - * - * @return The tip radius (in Java2D units). - * - * @see #setTipRadius(double) - */ - public double getTipRadius() { - return this.tipRadius; - } - - /** - * Sets the tip radius and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param radius the radius (in Java2D units). - * - * @see #getTipRadius() - */ - public void setTipRadius(double radius) { - this.tipRadius = radius; - fireAnnotationChanged(); - } - - /** - * Returns the base radius. - * - * @return The base radius (in Java2D units). - * - * @see #setBaseRadius(double) - */ - public double getBaseRadius() { - return this.baseRadius; - } - - /** - * Sets the base radius and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param radius the radius (in Java2D units). - * - * @see #getBaseRadius() - */ - public void setBaseRadius(double radius) { - this.baseRadius = radius; - fireAnnotationChanged(); - } - - /** - * Returns the label offset. - * - * @return The label offset (in Java2D units). - * - * @see #setLabelOffset(double) - */ - public double getLabelOffset() { - return this.labelOffset; - } - - /** - * Sets the label offset (from the line base, continuing in a straight - * line, in Java2D units) and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param offset the offset (in Java2D units). - * - * @see #getLabelOffset() - */ - public void setLabelOffset(double offset) { - this.labelOffset = offset; - fireAnnotationChanged(); - } - - /** - * Returns the stroke used to draw the line line. - * - * @return The line stroke (never {@code null}). - * - * @see #setLineStroke(Stroke) - */ - public Stroke getLineStroke() { - return this.lineStroke; - } - - /** - * Sets the stroke used to draw the line line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getLineStroke() - */ - public void setLineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.lineStroke = stroke; - fireAnnotationChanged(); - } - - /** - * Returns the paint used for the line. - * - * @return The line paint (never {@code null}). - * - * @see #setLinePaint(Paint) - */ - public Paint getLinePaint() { - return this.linePaint; - } - - /** - * Sets the paint used for the line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param paint the line paint ({@code null} not permitted). - * - * @see #getLinePaint() - */ - public void setLinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.linePaint = paint; - fireAnnotationChanged(); - } - - /** - * Draws the annotation. - * - * @param g2 the graphics device. - * @param plot the plot. - * @param dataArea the data area. - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - * @param rendererIndex the renderer index. - * @param info the plot rendering info. - */ - @Override - public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, - ValueAxis domainAxis, ValueAxis rangeAxis, int rendererIndex, - PlotRenderingInfo info) { - - PlotOrientation orientation = plot.getOrientation(); - RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( - plot.getDomainAxisLocation(), orientation); - RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( - plot.getRangeAxisLocation(), orientation); - double j2DX = domainAxis.valueToJava2D(getX(), dataArea, domainEdge); - double j2DY = rangeAxis.valueToJava2D(getY(), dataArea, rangeEdge); - if (orientation == PlotOrientation.HORIZONTAL) { - double temp = j2DX; - j2DX = j2DY; - j2DY = temp; - } - double startX = j2DX + Math.cos(this.angle) * this.baseRadius; - double startY = j2DY + Math.sin(this.angle) * this.baseRadius; - - double endX = j2DX + Math.cos(this.angle) * this.tipRadius; - double endY = j2DY + Math.sin(this.angle) * this.tipRadius; - - g2.setStroke(this.lineStroke); - g2.setPaint(this.linePaint); - Line2D line = new Line2D.Double(startX, startY, endX, endY); - g2.draw(line); - - // draw the label - double labelX = j2DX + Math.cos(this.angle) * (this.baseRadius - + this.labelOffset); - double labelY = j2DY + Math.sin(this.angle) * (this.baseRadius - + this.labelOffset); - g2.setFont(getFont()); - Shape hotspot = TextUtils.calculateRotatedStringBounds( - getText(), g2, (float) labelX, (float) labelY, getTextAnchor(), - getRotationAngle(), getRotationAnchor()); - if (getBackgroundPaint() != null) { - g2.setPaint(getBackgroundPaint()); - g2.fill(hotspot); - } - g2.setPaint(getPaint()); - TextUtils.drawRotatedString(getText(), g2, (float) labelX, - (float) labelY, getTextAnchor(), getRotationAngle(), - getRotationAnchor()); - if (isOutlineVisible()) { - g2.setStroke(getOutlineStroke()); - g2.setPaint(getOutlinePaint()); - g2.draw(hotspot); - } - - String toolTip = getToolTipText(); - String url = getURL(); - if (toolTip != null || url != null) { - addEntity(info, hotspot, rendererIndex, toolTip, url); - } - - } - - /** - * Tests this annotation for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return {@code true} or {@code false}. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof XYNoteAnnotation)) { - return false; - } - XYNoteAnnotation that = (XYNoteAnnotation) obj; - if (this.angle != that.angle) { - return false; - } - if (this.tipRadius != that.tipRadius) { - return false; - } - if (this.baseRadius != that.baseRadius) { - return false; - } - if (!this.linePaint.equals(that.linePaint)) { - return false; - } - if (!Objects.equals(this.lineStroke, that.lineStroke)) { - return false; - } - if (this.labelOffset != that.labelOffset) { - return false; - } - return super.equals(obj); - } - - /** - * Returns a hash code for this instance. - * - * @return A hash code. - */ - @Override - public int hashCode() { - int result = super.hashCode(); - long temp = Double.doubleToLongBits(this.angle); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(this.tipRadius); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(this.baseRadius); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - result = result * 37 + HashUtils.hashCodeForPaint(this.linePaint); - result = result * 37 + this.lineStroke.hashCode(); - temp = Double.doubleToLongBits(this.labelOffset); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Returns a clone of the annotation. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the annotation can't be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writePaint(this.linePaint, stream); - SerialUtils.writeStroke(this.lineStroke, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - this.linePaint = SerialUtils.readPaint(stream); - this.lineStroke = SerialUtils.readStroke(stream); - } -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------------------- + * XYNoteAnnotation.java + * --------------------- + * (C) Copyright 2003-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert (as XYPointerAnnotation.java); + * Contributor(s): Yuri Blankenstein (copy to XYNoteAnnotation.java; for ESI TNO); + * + */ +package org.jfree.chart.annotations; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Objects; + +import org.jfree.chart.HashUtils; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.text.TextUtils; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.SerialUtils; + +/** + * An line and label that can be placed on an {@link XYPlot}. The line is + * drawn at a user-definable angle so that it points towards the (x, y) + * location for the annotation. + *

+ * The line length (and its offset from the (x, y) location) is controlled by + * the tip radius and the base radius attributes. Imagine two circles around + * the (x, y) coordinate: the inner circle defined by the tip radius, and the + * outer circle defined by the base radius. Now, draw the line starting at + * some point on the outer circle (the point is determined by the angle), with + * the line tip being drawn at a corresponding point on the inner circle. + */ +public class XYNoteAnnotation extends XYTextAnnotation + implements Cloneable, PublicCloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = -4031161445009858551L; + + /** The default tip radius (in Java2D units). */ + public static final double DEFAULT_TIP_RADIUS = 0.0; + + /** The default base radius (in Java2D units). */ + public static final double DEFAULT_BASE_RADIUS = 30.0; + + /** The default label offset (in Java2D units). */ + public static final double DEFAULT_LABEL_OFFSET = 3.0; + + /** The default line stroke. */ + public static final Stroke DEFAULT_LINE_STROKE = new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 2.0f, new float[] {2.0f}, 0.0f); + + /** The default line stroke. */ + public static final Paint DEFAULT_BACKGROUND_PAINT = new Color(255, 255, 203); + + /** The default line stroke. */ + public static final Paint DEFAULT_OUTLINE_PAINT = new Color(255, 204, 102); + + /** The angle of the line's line (in radians). */ + private double angle; + + /** + * The radius from the (x, y) point to the tip of the line (in Java2D + * units). + */ + private double tipRadius; + + /** + * The radius from the (x, y) point to the start of the line line (in + * Java2D units). + */ + private double baseRadius; + + /** The line stroke. */ + private transient Stroke lineStroke; + + /** The line paint. */ + private transient Paint linePaint; + + /** The radius from the base point to the anchor point for the label. */ + private double labelOffset; + + /** + * Creates a new label and line annotation. + * + * @param label the label ({@code null} permitted). + * @param x the x-coordinate (measured against the chart's domain axis). + * @param y the y-coordinate (measured against the chart's range axis). + * @param angle the angle of the line's line (in radians). + */ + public XYNoteAnnotation(String label, double x, double y, double angle) { + super(label, x, y); + this.angle = angle; + this.tipRadius = DEFAULT_TIP_RADIUS; + this.baseRadius = DEFAULT_BASE_RADIUS; + this.labelOffset = DEFAULT_LABEL_OFFSET; + this.lineStroke = DEFAULT_LINE_STROKE; + this.linePaint = Color.BLACK; + + setBackgroundPaint(DEFAULT_BACKGROUND_PAINT); + setOutlineVisible(true); + setOutlinePaint(DEFAULT_OUTLINE_PAINT); + setOutlineStroke(new BasicStroke(1.0f)); + } + + /** + * Returns the angle of the line. + * + * @return The angle (in radians). + * + * @see #setAngle(double) + */ + public double getAngle() { + return this.angle; + } + + /** + * Sets the angle of the line and calls {@link #fireAnnotationChanged()}. + * + * @param angle the angle (in radians). + * + * @see #getAngle() + */ + public void setAngle(double angle) { + this.angle = angle; + fireAnnotationChanged(); + } + + /** + * Returns the tip radius. + * + * @return The tip radius (in Java2D units). + * + * @see #setTipRadius(double) + */ + public double getTipRadius() { + return this.tipRadius; + } + + /** + * Sets the tip radius and calls {@link #fireAnnotationChanged()}. + * + * @param radius the radius (in Java2D units). + * + * @see #getTipRadius() + */ + public void setTipRadius(double radius) { + this.tipRadius = radius; + fireAnnotationChanged(); + } + + /** + * Returns the base radius. + * + * @return The base radius (in Java2D units). + * + * @see #setBaseRadius(double) + */ + public double getBaseRadius() { + return this.baseRadius; + } + + /** + * Sets the base radius and calls {@link #fireAnnotationChanged()}. + * + * @param radius the radius (in Java2D units). + * + * @see #getBaseRadius() + */ + public void setBaseRadius(double radius) { + this.baseRadius = radius; + fireAnnotationChanged(); + } + + /** + * Returns the label offset. + * + * @return The label offset (in Java2D units). + * + * @see #setLabelOffset(double) + */ + public double getLabelOffset() { + return this.labelOffset; + } + + /** + * Sets the label offset (from the line base, continuing in a straight + * line, in Java2D units) and calls {@link #fireAnnotationChanged()}. + * + * @param offset the offset (in Java2D units). + * + * @see #getLabelOffset() + */ + public void setLabelOffset(double offset) { + this.labelOffset = offset; + fireAnnotationChanged(); + } + + /** + * Returns the stroke used to draw the line line. + * + * @return The line stroke (never {@code null}). + * + * @see #setLineStroke(Stroke) + */ + public Stroke getLineStroke() { + return this.lineStroke; + } + + /** + * Sets the stroke used to draw the line and calls + * {@link #fireAnnotationChanged()}. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getLineStroke() + */ + public void setLineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.lineStroke = stroke; + fireAnnotationChanged(); + } + + /** + * Returns the paint used for the line. + * + * @return The line paint (never {@code null}). + * + * @see #setLinePaint(Paint) + */ + public Paint getLinePaint() { + return this.linePaint; + } + + /** + * Sets the paint used for the line and calls + * {@link #fireAnnotationChanged()}. + * + * @param paint the line paint ({@code null} not permitted). + * + * @see #getLinePaint() + */ + public void setLinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.linePaint = paint; + fireAnnotationChanged(); + } + + /** + * Draws the annotation. + * + * @param g2 the graphics device. + * @param plot the plot. + * @param dataArea the data area. + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + * @param rendererIndex the renderer index. + * @param info the plot rendering info. + */ + @Override + public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, + ValueAxis domainAxis, ValueAxis rangeAxis, int rendererIndex, + PlotRenderingInfo info) { + + PlotOrientation orientation = plot.getOrientation(); + RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( + plot.getDomainAxisLocation(), orientation); + RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( + plot.getRangeAxisLocation(), orientation); + double j2DX = domainAxis.valueToJava2D(getX(), dataArea, domainEdge); + double j2DY = rangeAxis.valueToJava2D(getY(), dataArea, rangeEdge); + if (orientation == PlotOrientation.HORIZONTAL) { + double temp = j2DX; + j2DX = j2DY; + j2DY = temp; + } + double startX = j2DX + Math.cos(this.angle) * this.baseRadius; + double startY = j2DY + Math.sin(this.angle) * this.baseRadius; + + double endX = j2DX + Math.cos(this.angle) * this.tipRadius; + double endY = j2DY + Math.sin(this.angle) * this.tipRadius; + + g2.setStroke(this.lineStroke); + g2.setPaint(this.linePaint); + Line2D line = new Line2D.Double(startX, startY, endX, endY); + g2.draw(line); + + // draw the label + double labelX = j2DX + Math.cos(this.angle) * (this.baseRadius + + this.labelOffset); + double labelY = j2DY + Math.sin(this.angle) * (this.baseRadius + + this.labelOffset); + g2.setFont(getFont()); + Shape hotspot = TextUtils.calculateRotatedStringBounds( + getText(), g2, (float) labelX, (float) labelY, getTextAnchor(), + getRotationAngle(), getRotationAnchor()); + if (getBackgroundPaint() != null) { + g2.setPaint(getBackgroundPaint()); + g2.fill(hotspot); + } + g2.setPaint(getPaint()); + TextUtils.drawRotatedString(getText(), g2, (float) labelX, + (float) labelY, getTextAnchor(), getRotationAngle(), + getRotationAnchor()); + if (isOutlineVisible()) { + g2.setStroke(getOutlineStroke()); + g2.setPaint(getOutlinePaint()); + g2.draw(hotspot); + } + + String toolTip = getToolTipText(); + String url = getURL(); + if (toolTip != null || url != null) { + addEntity(info, hotspot, rendererIndex, toolTip, url); + } + + } + + /** + * Tests this annotation for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return {@code true} or {@code false}. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof XYNoteAnnotation)) { + return false; + } + XYNoteAnnotation that = (XYNoteAnnotation) obj; + if (this.angle != that.angle) { + return false; + } + if (this.tipRadius != that.tipRadius) { + return false; + } + if (this.baseRadius != that.baseRadius) { + return false; + } + if (!this.linePaint.equals(that.linePaint)) { + return false; + } + if (!Objects.equals(this.lineStroke, that.lineStroke)) { + return false; + } + if (this.labelOffset != that.labelOffset) { + return false; + } + return super.equals(obj); + } + + /** + * Returns a hash code for this instance. + * + * @return A hash code. + */ + @Override + public int hashCode() { + int result = super.hashCode(); + long temp = Double.doubleToLongBits(this.angle); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(this.tipRadius); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(this.baseRadius); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + result = result * 37 + HashUtils.hashCodeForPaint(this.linePaint); + result = result * 37 + this.lineStroke.hashCode(); + temp = Double.doubleToLongBits(this.labelOffset); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + /** + * Returns a clone of the annotation. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the annotation can't be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writePaint(this.linePaint, stream); + SerialUtils.writeStroke(this.lineStroke, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + this.linePaint = SerialUtils.readPaint(stream); + this.lineStroke = SerialUtils.readStroke(stream); + } +} diff --git a/src/main/java/org/jfree/chart/annotations/XYPointerAnnotation.java b/src/main/java/org/jfree/chart/annotations/XYPointerAnnotation.java index cb1d07462..b70a5aa5f 100644 --- a/src/main/java/org/jfree/chart/annotations/XYPointerAnnotation.java +++ b/src/main/java/org/jfree/chart/annotations/XYPointerAnnotation.java @@ -1,576 +1,569 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------------ - * XYPointerAnnotation.java - * ------------------------ - * (C) Copyright 2003-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): Peter Kolb (patch 2809117); - Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); - * - */ - -package org.jfree.chart.annotations; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.GeneralPath; -import java.awt.geom.Line2D; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Objects; - -import org.jfree.chart.HashUtils; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.AnnotationChangeEvent; -import org.jfree.chart.plot.Plot; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.text.TextUtils; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PaintUtils; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.SerialUtils; - -/** - * An arrow and label that can be placed on an {@link XYPlot}. The arrow is - * drawn at a user-definable angle so that it points towards the (x, y) - * location for the annotation. - *

- * The arrow length (and its offset from the (x, y) location) is controlled by - * the tip radius and the base radius attributes. Imagine two circles around - * the (x, y) coordinate: the inner circle defined by the tip radius, and the - * outer circle defined by the base radius. Now, draw the arrow starting at - * some point on the outer circle (the point is determined by the angle), with - * the arrow tip being drawn at a corresponding point on the inner circle. - */ -public class XYPointerAnnotation extends XYTextAnnotation - implements Cloneable, PublicCloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = -4031161445009858551L; - - /** The default tip radius (in Java2D units). */ - public static final double DEFAULT_TIP_RADIUS = 10.0; - - /** The default base radius (in Java2D units). */ - public static final double DEFAULT_BASE_RADIUS = 30.0; - - /** The default label offset (in Java2D units). */ - public static final double DEFAULT_LABEL_OFFSET = 3.0; - - /** The default arrow length (in Java2D units). */ - public static final double DEFAULT_ARROW_LENGTH = 5.0; - - /** The default arrow width (in Java2D units). */ - public static final double DEFAULT_ARROW_WIDTH = 3.0; - - /** The angle of the arrow's line (in radians). */ - private double angle; - - /** - * The radius from the (x, y) point to the tip of the arrow (in Java2D - * units). - */ - private double tipRadius; - - /** - * The radius from the (x, y) point to the start of the arrow line (in - * Java2D units). - */ - private double baseRadius; - - /** The length of the arrow head (in Java2D units). */ - private double arrowLength; - - /** The arrow width (in Java2D units, per side). */ - private double arrowWidth; - - /** The arrow stroke. */ - private transient Stroke arrowStroke; - - /** The arrow paint. */ - private transient Paint arrowPaint; - - /** The radius from the base point to the anchor point for the label. */ - private double labelOffset; - - /** - * Creates a new label and arrow annotation. - * - * @param label the label ({@code null} permitted). - * @param x the x-coordinate (measured against the chart's domain axis). - * @param y the y-coordinate (measured against the chart's range axis). - * @param angle the angle of the arrow's line (in radians). - */ - public XYPointerAnnotation(String label, double x, double y, double angle) { - - super(label, x, y); - Args.requireFinite(x, "x"); - Args.requireFinite(y, "y"); - Args.requireFinite(angle, "angle"); - this.angle = angle; - this.tipRadius = DEFAULT_TIP_RADIUS; - this.baseRadius = DEFAULT_BASE_RADIUS; - this.arrowLength = DEFAULT_ARROW_LENGTH; - this.arrowWidth = DEFAULT_ARROW_WIDTH; - this.labelOffset = DEFAULT_LABEL_OFFSET; - this.arrowStroke = new BasicStroke(1.0f); - this.arrowPaint = Color.BLACK; - - } - - /** - * Returns the angle of the arrow. - * - * @return The angle (in radians). - * - * @see #setAngle(double) - */ - public double getAngle() { - return this.angle; - } - - /** - * Sets the angle of the arrow and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param angle the angle (in radians). - * - * @see #getAngle() - */ - public void setAngle(double angle) { - this.angle = angle; - fireAnnotationChanged(); - } - - /** - * Returns the tip radius. - * - * @return The tip radius (in Java2D units). - * - * @see #setTipRadius(double) - */ - public double getTipRadius() { - return this.tipRadius; - } - - /** - * Sets the tip radius and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param radius the radius (in Java2D units). - * - * @see #getTipRadius() - */ - public void setTipRadius(double radius) { - this.tipRadius = radius; - fireAnnotationChanged(); - } - - /** - * Returns the base radius. - * - * @return The base radius (in Java2D units). - * - * @see #setBaseRadius(double) - */ - public double getBaseRadius() { - return this.baseRadius; - } - - /** - * Sets the base radius and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param radius the radius (in Java2D units). - * - * @see #getBaseRadius() - */ - public void setBaseRadius(double radius) { - this.baseRadius = radius; - fireAnnotationChanged(); - } - - /** - * Returns the label offset. - * - * @return The label offset (in Java2D units). - * - * @see #setLabelOffset(double) - */ - public double getLabelOffset() { - return this.labelOffset; - } - - /** - * Sets the label offset (from the arrow base, continuing in a straight - * line, in Java2D units) and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param offset the offset (in Java2D units). - * - * @see #getLabelOffset() - */ - public void setLabelOffset(double offset) { - this.labelOffset = offset; - fireAnnotationChanged(); - } - - /** - * Returns the arrow length. - * - * @return The arrow length. - * - * @see #setArrowLength(double) - */ - public double getArrowLength() { - return this.arrowLength; - } - - /** - * Sets the arrow length and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param length the length. - * - * @see #getArrowLength() - */ - public void setArrowLength(double length) { - this.arrowLength = length; - fireAnnotationChanged(); - } - - /** - * Returns the arrow width. - * - * @return The arrow width (in Java2D units). - * - * @see #setArrowWidth(double) - */ - public double getArrowWidth() { - return this.arrowWidth; - } - - /** - * Sets the arrow width and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param width the width (in Java2D units). - * - * @see #getArrowWidth() - */ - public void setArrowWidth(double width) { - this.arrowWidth = width; - fireAnnotationChanged(); - } - - /** - * Returns the stroke used to draw the arrow line. - * - * @return The arrow stroke (never {@code null}). - * - * @see #setArrowStroke(Stroke) - */ - public Stroke getArrowStroke() { - return this.arrowStroke; - } - - /** - * Sets the stroke used to draw the arrow line and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getArrowStroke() - */ - public void setArrowStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.arrowStroke = stroke; - fireAnnotationChanged(); - } - - /** - * Returns the paint used for the arrow. - * - * @return The arrow paint (never {@code null}). - * - * @see #setArrowPaint(Paint) - */ - public Paint getArrowPaint() { - return this.arrowPaint; - } - - /** - * Sets the paint used for the arrow and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param paint the arrow paint ({@code null} not permitted). - * - * @see #getArrowPaint() - */ - public void setArrowPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.arrowPaint = paint; - fireAnnotationChanged(); - } - - /** - * Draws the annotation. - * - * @param g2 the graphics device. - * @param plot the plot. - * @param dataArea the data area. - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - * @param rendererIndex the renderer index. - * @param info the plot rendering info. - */ - @Override - public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, - ValueAxis domainAxis, ValueAxis rangeAxis, int rendererIndex, - PlotRenderingInfo info) { - - PlotOrientation orientation = plot.getOrientation(); - RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( - plot.getDomainAxisLocation(), orientation); - RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( - plot.getRangeAxisLocation(), orientation); - double j2DX = domainAxis.valueToJava2D(getX(), dataArea, domainEdge); - double j2DY = rangeAxis.valueToJava2D(getY(), dataArea, rangeEdge); - if (orientation == PlotOrientation.HORIZONTAL) { - double temp = j2DX; - j2DX = j2DY; - j2DY = temp; - } - double startX = j2DX + Math.cos(this.angle) * this.baseRadius; - double startY = j2DY + Math.sin(this.angle) * this.baseRadius; - - double endX = j2DX + Math.cos(this.angle) * this.tipRadius; - double endY = j2DY + Math.sin(this.angle) * this.tipRadius; - - double arrowBaseX = endX + Math.cos(this.angle) * this.arrowLength; - double arrowBaseY = endY + Math.sin(this.angle) * this.arrowLength; - - double arrowLeftX = arrowBaseX - + Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth; - double arrowLeftY = arrowBaseY - + Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth; - - double arrowRightX = arrowBaseX - - Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth; - double arrowRightY = arrowBaseY - - Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth; - - GeneralPath arrow = new GeneralPath(); - arrow.moveTo((float) endX, (float) endY); - arrow.lineTo((float) arrowLeftX, (float) arrowLeftY); - arrow.lineTo((float) arrowRightX, (float) arrowRightY); - arrow.closePath(); - - g2.setStroke(this.arrowStroke); - g2.setPaint(this.arrowPaint); - Line2D line = new Line2D.Double(startX, startY, arrowBaseX, arrowBaseY); - g2.draw(line); - g2.fill(arrow); - - // draw the label - double labelX = j2DX + Math.cos(this.angle) * (this.baseRadius - + this.labelOffset); - double labelY = j2DY + Math.sin(this.angle) * (this.baseRadius - + this.labelOffset); - g2.setFont(getFont()); - Shape hotspot = TextUtils.calculateRotatedStringBounds( - getText(), g2, (float) labelX, (float) labelY, getTextAnchor(), - getRotationAngle(), getRotationAnchor()); - if (getBackgroundPaint() != null) { - g2.setPaint(getBackgroundPaint()); - g2.fill(hotspot); - } - g2.setPaint(getPaint()); - TextUtils.drawRotatedString(getText(), g2, (float) labelX, - (float) labelY, getTextAnchor(), getRotationAngle(), - getRotationAnchor()); - if (isOutlineVisible()) { - g2.setStroke(getOutlineStroke()); - g2.setPaint(getOutlinePaint()); - g2.draw(hotspot); - } - - String toolTip = getToolTipText(); - String url = getURL(); - if (toolTip != null || url != null) { - addEntity(info, hotspot, rendererIndex, toolTip, url); - } - - } - - /** - * Tests this annotation for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return {@code true} or {@code false}. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof XYPointerAnnotation)) { - return false; - } - XYPointerAnnotation that = (XYPointerAnnotation) obj; - if (Double.doubleToLongBits(this.angle) != - Double.doubleToLongBits(that.angle)) { - return false; - } - if (Double.doubleToLongBits(this.tipRadius) != - Double.doubleToLongBits(that.tipRadius)) { - return false; - } - if (Double.doubleToLongBits(this.baseRadius) != - Double.doubleToLongBits(that.baseRadius)) { - return false; - } - if (Double.doubleToLongBits(this.arrowLength) != - Double.doubleToLongBits(that.arrowLength)) { - return false; - } - if (Double.doubleToLongBits(this.arrowWidth) != - Double.doubleToLongBits(that.arrowWidth)) { - return false; - } - if (!PaintUtils.equal(this.arrowPaint, that.arrowPaint)) { - return false; - } - if (!Objects.equals(this.arrowStroke, that.arrowStroke)) { - return false; - } - if (Double.doubleToLongBits(this.labelOffset) != - Double.doubleToLongBits(that.labelOffset)) { - return false; - } - - // fix the "equals not symmetric" problem - if (!that.canEqual(this)) { - return false; - } - - return super.equals(obj); - } - - /** - * Ensures symmetry between super/subclass implementations of equals. For - * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. - * - * @param other Object - * - * @return true ONLY if the parameter is THIS class type - */ - @Override - public boolean canEqual(Object other) { - // fix the "equals not symmetric" problem - return (other instanceof XYPointerAnnotation); - } - - /** - * Returns a hash code for this instance. - * - * @return A hash code. - */ - @Override - public int hashCode() { - int hash = super.hashCode(); // equals calls superclass, hashCode must also - hash = 41 * hash + (int) (Double.doubleToLongBits(this.angle) ^ - (Double.doubleToLongBits(this.angle) >>> 32)); - hash = 41 * hash + (int) (Double.doubleToLongBits(this.tipRadius) ^ - (Double.doubleToLongBits(this.tipRadius) >>> 32)); - hash = 41 * hash + (int) (Double.doubleToLongBits(this.baseRadius) ^ - (Double.doubleToLongBits(this.baseRadius) >>> 32)); - hash = 41 * hash + (int) (Double.doubleToLongBits(this.arrowLength) ^ - (Double.doubleToLongBits(this.arrowLength) >>> 32)); - hash = 41 * hash + (int) (Double.doubleToLongBits(this.arrowWidth) ^ - (Double.doubleToLongBits(this.arrowWidth) >>> 32)); - hash = 41 * hash + HashUtils.hashCodeForPaint(this.arrowPaint); - hash = 41 * hash + Objects.hashCode(this.arrowStroke); - hash = 41 * hash + (int) (Double.doubleToLongBits(this.labelOffset) ^ - (Double.doubleToLongBits(this.labelOffset) >>> 32)); - return hash; - } - - /** - * Returns a clone of the annotation. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the annotation can't be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writePaint(this.arrowPaint, stream); - SerialUtils.writeStroke(this.arrowStroke, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - this.arrowPaint = SerialUtils.readPaint(stream); - this.arrowStroke = SerialUtils.readStroke(stream); - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------------ + * XYPointerAnnotation.java + * ------------------------ + * (C) Copyright 2003-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): Peter Kolb (patch 2809117); + Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); + * + */ + +package org.jfree.chart.annotations; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Objects; + +import org.jfree.chart.HashUtils; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.text.TextUtils; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PaintUtils; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.SerialUtils; + +/** + * An arrow and label that can be placed on an {@link XYPlot}. The arrow is + * drawn at a user-definable angle so that it points towards the (x, y) + * location for the annotation. + *

+ * The arrow length (and its offset from the (x, y) location) is controlled by + * the tip radius and the base radius attributes. Imagine two circles around + * the (x, y) coordinate: the inner circle defined by the tip radius, and the + * outer circle defined by the base radius. Now, draw the arrow starting at + * some point on the outer circle (the point is determined by the angle), with + * the arrow tip being drawn at a corresponding point on the inner circle. + */ +public class XYPointerAnnotation extends XYTextAnnotation + implements Cloneable, PublicCloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = -4031161445009858551L; + + /** The default tip radius (in Java2D units). */ + public static final double DEFAULT_TIP_RADIUS = 10.0; + + /** The default base radius (in Java2D units). */ + public static final double DEFAULT_BASE_RADIUS = 30.0; + + /** The default label offset (in Java2D units). */ + public static final double DEFAULT_LABEL_OFFSET = 3.0; + + /** The default arrow length (in Java2D units). */ + public static final double DEFAULT_ARROW_LENGTH = 5.0; + + /** The default arrow width (in Java2D units). */ + public static final double DEFAULT_ARROW_WIDTH = 3.0; + + /** The angle of the arrow's line (in radians). */ + private double angle; + + /** + * The radius from the (x, y) point to the tip of the arrow (in Java2D + * units). + */ + private double tipRadius; + + /** + * The radius from the (x, y) point to the start of the arrow line (in + * Java2D units). + */ + private double baseRadius; + + /** The length of the arrow head (in Java2D units). */ + private double arrowLength; + + /** The arrow width (in Java2D units, per side). */ + private double arrowWidth; + + /** The arrow stroke. */ + private transient Stroke arrowStroke; + + /** The arrow paint. */ + private transient Paint arrowPaint; + + /** The radius from the base point to the anchor point for the label. */ + private double labelOffset; + + /** + * Creates a new label and arrow annotation. + * + * @param label the label ({@code null} permitted). + * @param x the x-coordinate (measured against the chart's domain axis). + * @param y the y-coordinate (measured against the chart's range axis). + * @param angle the angle of the arrow's line (in radians). + */ + public XYPointerAnnotation(String label, double x, double y, double angle) { + + super(label, x, y); + Args.requireFinite(x, "x"); + Args.requireFinite(y, "y"); + Args.requireFinite(angle, "angle"); + this.angle = angle; + this.tipRadius = DEFAULT_TIP_RADIUS; + this.baseRadius = DEFAULT_BASE_RADIUS; + this.arrowLength = DEFAULT_ARROW_LENGTH; + this.arrowWidth = DEFAULT_ARROW_WIDTH; + this.labelOffset = DEFAULT_LABEL_OFFSET; + this.arrowStroke = new BasicStroke(1.0f); + this.arrowPaint = Color.BLACK; + + } + + /** + * Returns the angle of the arrow. + * + * @return The angle (in radians). + * + * @see #setAngle(double) + */ + public double getAngle() { + return this.angle; + } + + /** + * Sets the angle of the arrow and calls {@link #fireAnnotationChanged()}. + * + * @param angle the angle (in radians). + * + * @see #getAngle() + */ + public void setAngle(double angle) { + this.angle = angle; + fireAnnotationChanged(); + } + + /** + * Returns the tip radius. + * + * @return The tip radius (in Java2D units). + * + * @see #setTipRadius(double) + */ + public double getTipRadius() { + return this.tipRadius; + } + + /** + * Sets the tip radius and calls {@link #fireAnnotationChanged()}. + * + * @param radius the radius (in Java2D units). + * + * @see #getTipRadius() + */ + public void setTipRadius(double radius) { + this.tipRadius = radius; + fireAnnotationChanged(); + } + + /** + * Returns the base radius. + * + * @return The base radius (in Java2D units). + * + * @see #setBaseRadius(double) + */ + public double getBaseRadius() { + return this.baseRadius; + } + + /** + * Sets the base radius and calls {@link #fireAnnotationChanged()}. + * + * @param radius the radius (in Java2D units). + * + * @see #getBaseRadius() + */ + public void setBaseRadius(double radius) { + this.baseRadius = radius; + fireAnnotationChanged(); + } + + /** + * Returns the label offset. + * + * @return The label offset (in Java2D units). + * + * @see #setLabelOffset(double) + */ + public double getLabelOffset() { + return this.labelOffset; + } + + /** + * Sets the label offset (from the arrow base, continuing in a straight + * line, in Java2D units) and calls {@link #fireAnnotationChanged()}. + * + * @param offset the offset (in Java2D units). + * + * @see #getLabelOffset() + */ + public void setLabelOffset(double offset) { + this.labelOffset = offset; + fireAnnotationChanged(); + } + + /** + * Returns the arrow length. + * + * @return The arrow length. + * + * @see #setArrowLength(double) + */ + public double getArrowLength() { + return this.arrowLength; + } + + /** + * Sets the arrow length and calls {@link #fireAnnotationChanged()}. + * + * @param length the length. + * + * @see #getArrowLength() + */ + public void setArrowLength(double length) { + this.arrowLength = length; + fireAnnotationChanged(); + } + + /** + * Returns the arrow width. + * + * @return The arrow width (in Java2D units). + * + * @see #setArrowWidth(double) + */ + public double getArrowWidth() { + return this.arrowWidth; + } + + /** + * Sets the arrow width and calls {@link #fireAnnotationChanged()}. + * + * @param width the width (in Java2D units). + * + * @see #getArrowWidth() + */ + public void setArrowWidth(double width) { + this.arrowWidth = width; + fireAnnotationChanged(); + } + + /** + * Returns the stroke used to draw the arrow line. + * + * @return The arrow stroke (never {@code null}). + * + * @see #setArrowStroke(Stroke) + */ + public Stroke getArrowStroke() { + return this.arrowStroke; + } + + /** + * Sets the stroke used to draw the arrow line and calls + * {@link #fireAnnotationChanged()}. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getArrowStroke() + */ + public void setArrowStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.arrowStroke = stroke; + fireAnnotationChanged(); + } + + /** + * Returns the paint used for the arrow. + * + * @return The arrow paint (never {@code null}). + * + * @see #setArrowPaint(Paint) + */ + public Paint getArrowPaint() { + return this.arrowPaint; + } + + /** + * Sets the paint used for the arrow and calls + * {@link #fireAnnotationChanged()}. + * + * @param paint the arrow paint ({@code null} not permitted). + * + * @see #getArrowPaint() + */ + public void setArrowPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.arrowPaint = paint; + fireAnnotationChanged(); + } + + /** + * Draws the annotation. + * + * @param g2 the graphics device. + * @param plot the plot. + * @param dataArea the data area. + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + * @param rendererIndex the renderer index. + * @param info the plot rendering info. + */ + @Override + public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, + ValueAxis domainAxis, ValueAxis rangeAxis, int rendererIndex, + PlotRenderingInfo info) { + + PlotOrientation orientation = plot.getOrientation(); + RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( + plot.getDomainAxisLocation(), orientation); + RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( + plot.getRangeAxisLocation(), orientation); + double j2DX = domainAxis.valueToJava2D(getX(), dataArea, domainEdge); + double j2DY = rangeAxis.valueToJava2D(getY(), dataArea, rangeEdge); + if (orientation == PlotOrientation.HORIZONTAL) { + double temp = j2DX; + j2DX = j2DY; + j2DY = temp; + } + double startX = j2DX + Math.cos(this.angle) * this.baseRadius; + double startY = j2DY + Math.sin(this.angle) * this.baseRadius; + + double endX = j2DX + Math.cos(this.angle) * this.tipRadius; + double endY = j2DY + Math.sin(this.angle) * this.tipRadius; + + double arrowBaseX = endX + Math.cos(this.angle) * this.arrowLength; + double arrowBaseY = endY + Math.sin(this.angle) * this.arrowLength; + + double arrowLeftX = arrowBaseX + + Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth; + double arrowLeftY = arrowBaseY + + Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth; + + double arrowRightX = arrowBaseX + - Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth; + double arrowRightY = arrowBaseY + - Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth; + + GeneralPath arrow = new GeneralPath(); + arrow.moveTo((float) endX, (float) endY); + arrow.lineTo((float) arrowLeftX, (float) arrowLeftY); + arrow.lineTo((float) arrowRightX, (float) arrowRightY); + arrow.closePath(); + + g2.setStroke(this.arrowStroke); + g2.setPaint(this.arrowPaint); + Line2D line = new Line2D.Double(startX, startY, arrowBaseX, arrowBaseY); + g2.draw(line); + g2.fill(arrow); + + // draw the label + double labelX = j2DX + Math.cos(this.angle) * (this.baseRadius + + this.labelOffset); + double labelY = j2DY + Math.sin(this.angle) * (this.baseRadius + + this.labelOffset); + g2.setFont(getFont()); + Shape hotspot = TextUtils.calculateRotatedStringBounds( + getText(), g2, (float) labelX, (float) labelY, getTextAnchor(), + getRotationAngle(), getRotationAnchor()); + if (getBackgroundPaint() != null) { + g2.setPaint(getBackgroundPaint()); + g2.fill(hotspot); + } + g2.setPaint(getPaint()); + TextUtils.drawRotatedString(getText(), g2, (float) labelX, + (float) labelY, getTextAnchor(), getRotationAngle(), + getRotationAnchor()); + if (isOutlineVisible()) { + g2.setStroke(getOutlineStroke()); + g2.setPaint(getOutlinePaint()); + g2.draw(hotspot); + } + + String toolTip = getToolTipText(); + String url = getURL(); + if (toolTip != null || url != null) { + addEntity(info, hotspot, rendererIndex, toolTip, url); + } + + } + + /** + * Tests this annotation for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return {@code true} or {@code false}. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof XYPointerAnnotation)) { + return false; + } + XYPointerAnnotation that = (XYPointerAnnotation) obj; + if (Double.doubleToLongBits(this.angle) != + Double.doubleToLongBits(that.angle)) { + return false; + } + if (Double.doubleToLongBits(this.tipRadius) != + Double.doubleToLongBits(that.tipRadius)) { + return false; + } + if (Double.doubleToLongBits(this.baseRadius) != + Double.doubleToLongBits(that.baseRadius)) { + return false; + } + if (Double.doubleToLongBits(this.arrowLength) != + Double.doubleToLongBits(that.arrowLength)) { + return false; + } + if (Double.doubleToLongBits(this.arrowWidth) != + Double.doubleToLongBits(that.arrowWidth)) { + return false; + } + if (!PaintUtils.equal(this.arrowPaint, that.arrowPaint)) { + return false; + } + if (!Objects.equals(this.arrowStroke, that.arrowStroke)) { + return false; + } + if (Double.doubleToLongBits(this.labelOffset) != + Double.doubleToLongBits(that.labelOffset)) { + return false; + } + + // fix the "equals not symmetric" problem + if (!that.canEqual(this)) { + return false; + } + + return super.equals(obj); + } + + /** + * Ensures symmetry between super/subclass implementations of equals. For + * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. + * + * @param other Object + * + * @return true ONLY if the parameter is THIS class type + */ + @Override + public boolean canEqual(Object other) { + // fix the "equals not symmetric" problem + return (other instanceof XYPointerAnnotation); + } + + /** + * Returns a hash code for this instance. + * + * @return A hash code. + */ + @Override + public int hashCode() { + int hash = super.hashCode(); // equals calls superclass, hashCode must also + hash = 41 * hash + (int) (Double.doubleToLongBits(this.angle) ^ + (Double.doubleToLongBits(this.angle) >>> 32)); + hash = 41 * hash + (int) (Double.doubleToLongBits(this.tipRadius) ^ + (Double.doubleToLongBits(this.tipRadius) >>> 32)); + hash = 41 * hash + (int) (Double.doubleToLongBits(this.baseRadius) ^ + (Double.doubleToLongBits(this.baseRadius) >>> 32)); + hash = 41 * hash + (int) (Double.doubleToLongBits(this.arrowLength) ^ + (Double.doubleToLongBits(this.arrowLength) >>> 32)); + hash = 41 * hash + (int) (Double.doubleToLongBits(this.arrowWidth) ^ + (Double.doubleToLongBits(this.arrowWidth) >>> 32)); + hash = 41 * hash + HashUtils.hashCodeForPaint(this.arrowPaint); + hash = 41 * hash + Objects.hashCode(this.arrowStroke); + hash = 41 * hash + (int) (Double.doubleToLongBits(this.labelOffset) ^ + (Double.doubleToLongBits(this.labelOffset) >>> 32)); + return hash; + } + + /** + * Returns a clone of the annotation. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the annotation can't be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writePaint(this.arrowPaint, stream); + SerialUtils.writeStroke(this.arrowStroke, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + this.arrowPaint = SerialUtils.readPaint(stream); + this.arrowStroke = SerialUtils.readStroke(stream); + } + +} diff --git a/src/main/java/org/jfree/chart/annotations/XYTextAnnotation.java b/src/main/java/org/jfree/chart/annotations/XYTextAnnotation.java index 4c90873be..856600461 100644 --- a/src/main/java/org/jfree/chart/annotations/XYTextAnnotation.java +++ b/src/main/java/org/jfree/chart/annotations/XYTextAnnotation.java @@ -1,661 +1,658 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------------------- - * XYTextAnnotation.java - * --------------------- - * (C) Copyright 2002-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): Peter Kolb (patch 2809117); - * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); - * - */ - -package org.jfree.chart.annotations; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Objects; -import org.jfree.chart.HashUtils; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.AnnotationChangeEvent; -import org.jfree.chart.plot.Plot; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.text.TextUtils; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.TextAnchor; -import org.jfree.chart.util.PaintUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.SerialUtils; - -/** - * A text annotation that can be placed at a particular (x, y) location on an - * {@link XYPlot}. - */ -public class XYTextAnnotation extends AbstractXYAnnotation - implements Cloneable, PublicCloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = -2946063342782506328L; - - /** The default font. */ - public static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, - 10); - - /** The default paint. */ - public static final Paint DEFAULT_PAINT = Color.BLACK; - - /** The default text anchor. */ - public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER; - - /** The default rotation anchor. */ - public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER; - - /** The default rotation angle. */ - public static final double DEFAULT_ROTATION_ANGLE = 0.0; - - /** The text. */ - private String text; - - /** The font. */ - private Font font; - - /** The paint. */ - private transient Paint paint; - - /** The x-coordinate. */ - private double x; - - /** The y-coordinate. */ - private double y; - - /** The text anchor (to be aligned with (x, y)). */ - private TextAnchor textAnchor; - - /** The rotation anchor. */ - private TextAnchor rotationAnchor; - - /** The rotation angle. */ - private double rotationAngle; - - /** The background paint (possibly null). */ - private transient Paint backgroundPaint; - - /** The flag that controls the visibility of the outline. */ - private boolean outlineVisible; - - /** The outline paint (never null). */ - private transient Paint outlinePaint; - - /** The outline stroke (never null). */ - private transient Stroke outlineStroke; - - /** - * Creates a new annotation to be displayed at the given coordinates. The - * coordinates are specified in data space (they will be converted to - * Java2D space for display). - * - * @param text the text ({@code null} not permitted). - * @param x the x-coordinate (in data space, must be finite). - * @param y the y-coordinate (in data space, must be finite). - */ - public XYTextAnnotation(String text, double x, double y) { - super(); - Args.nullNotPermitted(text, "text"); - Args.requireFinite(x, "x"); - Args.requireFinite(y, "y"); - this.text = text; - this.font = DEFAULT_FONT; - this.paint = DEFAULT_PAINT; - this.x = x; - this.y = y; - this.textAnchor = DEFAULT_TEXT_ANCHOR; - this.rotationAnchor = DEFAULT_ROTATION_ANCHOR; - this.rotationAngle = DEFAULT_ROTATION_ANGLE; - - // by default the outline and background won't be visible - this.backgroundPaint = null; - this.outlineVisible = false; - this.outlinePaint = Color.BLACK; - this.outlineStroke = new BasicStroke(0.5f); - } - - /** - * Returns the text for the annotation. - * - * @return The text (never {@code null}). - * - * @see #setText(String) - */ - public String getText() { - return this.text; - } - - /** - * Sets the text for the annotation. - * - * @param text the text ({@code null} not permitted). - * - * @see #getText() - */ - public void setText(String text) { - Args.nullNotPermitted(text, "text"); - this.text = text; - fireAnnotationChanged(); - } - - /** - * Returns the font for the annotation. - * - * @return The font (never {@code null}). - * - * @see #setFont(Font) - */ - public Font getFont() { - return this.font; - } - - /** - * Sets the font for the annotation and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param font the font ({@code null} not permitted). - * - * @see #getFont() - */ - public void setFont(Font font) { - Args.nullNotPermitted(font, "font"); - this.font = font; - fireAnnotationChanged(); - } - - /** - * Returns the paint for the annotation. - * - * @return The paint (never {@code null}). - * - * @see #setPaint(Paint) - */ - public Paint getPaint() { - return this.paint; - } - - /** - * Sets the paint for the annotation and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getPaint() - */ - public void setPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.paint = paint; - fireAnnotationChanged(); - } - - /** - * Returns the text anchor. - * - * @return The text anchor (never {@code null}). - * - * @see #setTextAnchor(TextAnchor) - */ - public TextAnchor getTextAnchor() { - return this.textAnchor; - } - - /** - * Sets the text anchor (the point on the text bounding rectangle that is - * aligned to the (x, y) coordinate of the annotation) and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param anchor the anchor point ({@code null} not permitted). - * - * @see #getTextAnchor() - */ - public void setTextAnchor(TextAnchor anchor) { - Args.nullNotPermitted(anchor, "anchor"); - this.textAnchor = anchor; - fireAnnotationChanged(); - } - - /** - * Returns the rotation anchor. - * - * @return The rotation anchor point (never {@code null}). - * - * @see #setRotationAnchor(TextAnchor) - */ - public TextAnchor getRotationAnchor() { - return this.rotationAnchor; - } - - /** - * Sets the rotation anchor point and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param anchor the anchor ({@code null} not permitted). - * - * @see #getRotationAnchor() - */ - public void setRotationAnchor(TextAnchor anchor) { - Args.nullNotPermitted(anchor, "anchor"); - this.rotationAnchor = anchor; - fireAnnotationChanged(); - } - - /** - * Returns the rotation angle. - * - * @return The rotation angle. - * - * @see #setRotationAngle(double) - */ - public double getRotationAngle() { - return this.rotationAngle; - } - - /** - * Sets the rotation angle and sends an {@link AnnotationChangeEvent} to - * all registered listeners. The angle is measured clockwise in radians. - * - * @param angle the angle (in radians). - * - * @see #getRotationAngle() - */ - public void setRotationAngle(double angle) { - this.rotationAngle = angle; - fireAnnotationChanged(); - } - - /** - * Returns the x coordinate for the text anchor point (measured against the - * domain axis). - * - * @return The x coordinate (in data space). - * - * @see #setX(double) - */ - public double getX() { - return this.x; - } - - /** - * Sets the x coordinate for the text anchor point (measured against the - * domain axis) and sends an {@link AnnotationChangeEvent} to all - * registered listeners. - * - * @param x the x coordinate (in data space). - * - * @see #getX() - */ - public void setX(double x) { - Args.requireFinite(x, "x"); - this.x = x; - fireAnnotationChanged(); - } - - /** - * Returns the y coordinate for the text anchor point (measured against the - * range axis). - * - * @return The y coordinate (in data space). - * - * @see #setY(double) - */ - public double getY() { - return this.y; - } - - /** - * Sets the y coordinate for the text anchor point (measured against the - * range axis) and sends an {@link AnnotationChangeEvent} to all registered - * listeners. - * - * @param y the y coordinate. - * - * @see #getY() - */ - public void setY(double y) { - Args.requireFinite(y, "y"); - this.y = y; - fireAnnotationChanged(); - } - - /** - * Returns the background paint for the annotation. - * - * @return The background paint (possibly {@code null}). - * - * @see #setBackgroundPaint(Paint) - */ - public Paint getBackgroundPaint() { - return this.backgroundPaint; - } - - /** - * Sets the background paint for the annotation and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} permitted). - * - * @see #getBackgroundPaint() - */ - public void setBackgroundPaint(Paint paint) { - this.backgroundPaint = paint; - fireAnnotationChanged(); - } - - /** - * Returns the outline paint for the annotation. - * - * @return The outline paint (never {@code null}). - * - * @see #setOutlinePaint(Paint) - */ - public Paint getOutlinePaint() { - return this.outlinePaint; - } - - /** - * Sets the outline paint for the annotation and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getOutlinePaint() - */ - public void setOutlinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.outlinePaint = paint; - fireAnnotationChanged(); - } - - /** - * Returns the outline stroke for the annotation. - * - * @return The outline stroke (never {@code null}). - * - * @see #setOutlineStroke(Stroke) - */ - public Stroke getOutlineStroke() { - return this.outlineStroke; - } - - /** - * Sets the outline stroke for the annotation and sends an - * {@link AnnotationChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getOutlineStroke() - */ - public void setOutlineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.outlineStroke = stroke; - fireAnnotationChanged(); - } - - /** - * Returns the flag that controls whether or not the outline is drawn. - * - * @return A boolean. - */ - public boolean isOutlineVisible() { - return this.outlineVisible; - } - - /** - * Sets the flag that controls whether or not the outline is drawn and - * sends an {@link AnnotationChangeEvent} to all registered listeners. - * - * @param visible the new flag value. - */ - public void setOutlineVisible(boolean visible) { - this.outlineVisible = visible; - fireAnnotationChanged(); - } - - /** - * Draws the annotation. - * - * @param g2 the graphics device. - * @param plot the plot. - * @param dataArea the data area. - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - * @param rendererIndex the renderer index. - * @param info an optional info object that will be populated with - * entity information. - */ - @Override - public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, - ValueAxis domainAxis, ValueAxis rangeAxis, - int rendererIndex, PlotRenderingInfo info) { - - PlotOrientation orientation = plot.getOrientation(); - RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( - plot.getDomainAxisLocation(), orientation); - RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( - plot.getRangeAxisLocation(), orientation); - - float anchorX = (float) domainAxis.valueToJava2D( - this.x, dataArea, domainEdge); - float anchorY = (float) rangeAxis.valueToJava2D( - this.y, dataArea, rangeEdge); - - if (orientation == PlotOrientation.HORIZONTAL) { - float tempAnchor = anchorX; - anchorX = anchorY; - anchorY = tempAnchor; - } - - g2.setFont(getFont()); - Shape hotspot = TextUtils.calculateRotatedStringBounds( - getText(), g2, anchorX, anchorY, getTextAnchor(), - getRotationAngle(), getRotationAnchor()); - if (this.backgroundPaint != null) { - g2.setPaint(this.backgroundPaint); - g2.fill(hotspot); - } - g2.setPaint(getPaint()); - TextUtils.drawRotatedString(getText(), g2, anchorX, anchorY, - getTextAnchor(), getRotationAngle(), getRotationAnchor()); - if (this.outlineVisible) { - g2.setStroke(this.outlineStroke); - g2.setPaint(this.outlinePaint); - g2.draw(hotspot); - } - - String toolTip = getToolTipText(); - String url = getURL(); - if (toolTip != null || url != null) { - addEntity(info, hotspot, rendererIndex, toolTip, url); - } - - } - - /** - * Tests this annotation for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof XYTextAnnotation)) { - return false; - } - XYTextAnnotation that = (XYTextAnnotation) obj; - if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(that.x)) { - return false; - } - if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(that.y)) { - return false; - } - if (Double.doubleToLongBits(this.rotationAngle) != - Double.doubleToLongBits(that.rotationAngle)) { - return false; - } - if (!PaintUtils.equal(this.paint, that.paint)) { - return false; - } - if (this.outlineVisible != that.outlineVisible) { - return false; - } - if (!Objects.equals(this.text, that.text)) { - return false; - } - if (!Objects.equals(this.font, that.font)) { - return false; - } - if (!Objects.equals(this.textAnchor, that.textAnchor)) { - return false; - } - if (!Objects.equals(this.rotationAnchor, that.rotationAnchor)) { - return false; - } - if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) { - return false; - } - if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { - return false; - } - if (!Objects.equals(this.outlineStroke, that.outlineStroke)) { - return false; - } - // fix the "equals not symmetric" problem - if (!that.canEqual(this)) { - return false; - } - - return super.equals(obj); - } - - /** - * Ensures symmetry between super/subclass implementations of equals. For - * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. - * - * @param other Object - * - * @return true ONLY if the parameter is THIS class type - */ - @Override - public boolean canEqual(Object other) { - // fix the "equals not symmetric" problem - return (other instanceof XYTextAnnotation); - } - - /** - * Returns a hash code for the object. - * - * @return A hash code. - */ - @Override - public int hashCode() { - int hash = super.hashCode(); // equals calls superclass, hashCode must also - hash = 23 * hash + Objects.hashCode(this.text); - hash = 23 * hash + Objects.hashCode(this.font); - hash = 23 * hash + HashUtils.hashCodeForPaint(this.paint); - hash = 23 * hash + (int) (Double.doubleToLongBits(this.x) ^ - (Double.doubleToLongBits(this.x) >>> 32)); - hash = 23 * hash + (int) (Double.doubleToLongBits(this.y) ^ - (Double.doubleToLongBits(this.y) >>> 32)); - hash = 23 * hash + Objects.hashCode(this.textAnchor); - hash = 23 * hash + Objects.hashCode(this.rotationAnchor); - hash = 23 * hash + (int) (Double.doubleToLongBits(this.rotationAngle) ^ - (Double.doubleToLongBits(this.rotationAngle) >>> 32)); - hash = 23 * hash + HashUtils.hashCodeForPaint(this.backgroundPaint); - hash = 23 * hash + (this.outlineVisible ? 1 : 0); - hash = 23 * hash + HashUtils.hashCodeForPaint(this.outlinePaint); - hash = 23 * hash + Objects.hashCode(this.outlineStroke); - return hash; - } - - /** - * Returns a clone of the annotation. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the annotation can't be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writePaint(this.paint, stream); - SerialUtils.writePaint(this.backgroundPaint, stream); - SerialUtils.writePaint(this.outlinePaint, stream); - SerialUtils.writeStroke(this.outlineStroke, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - this.paint = SerialUtils.readPaint(stream); - this.backgroundPaint = SerialUtils.readPaint(stream); - this.outlinePaint = SerialUtils.readPaint(stream); - this.outlineStroke = SerialUtils.readStroke(stream); - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------------------- + * XYTextAnnotation.java + * --------------------- + * (C) Copyright 2002-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): Peter Kolb (patch 2809117); + * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); + * + */ + +package org.jfree.chart.annotations; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Objects; +import org.jfree.chart.HashUtils; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.text.TextUtils; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.TextAnchor; +import org.jfree.chart.util.PaintUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.SerialUtils; + +/** + * A text annotation that can be placed at a particular (x, y) location on an + * {@link XYPlot}. + */ +public class XYTextAnnotation extends AbstractXYAnnotation + implements Cloneable, PublicCloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = -2946063342782506328L; + + /** The default font. */ + public static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, + 10); + + /** The default paint. */ + public static final Paint DEFAULT_PAINT = Color.BLACK; + + /** The default text anchor. */ + public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER; + + /** The default rotation anchor. */ + public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER; + + /** The default rotation angle. */ + public static final double DEFAULT_ROTATION_ANGLE = 0.0; + + /** The text. */ + private String text; + + /** The font. */ + private Font font; + + /** The paint. */ + private transient Paint paint; + + /** The x-coordinate. */ + private double x; + + /** The y-coordinate. */ + private double y; + + /** The text anchor (to be aligned with (x, y)). */ + private TextAnchor textAnchor; + + /** The rotation anchor. */ + private TextAnchor rotationAnchor; + + /** The rotation angle. */ + private double rotationAngle; + + /** The background paint (possibly null). */ + private transient Paint backgroundPaint; + + /** The flag that controls the visibility of the outline. */ + private boolean outlineVisible; + + /** The outline paint (never null). */ + private transient Paint outlinePaint; + + /** The outline stroke (never null). */ + private transient Stroke outlineStroke; + + /** + * Creates a new annotation to be displayed at the given coordinates. The + * coordinates are specified in data space (they will be converted to + * Java2D space for display). + * + * @param text the text ({@code null} not permitted). + * @param x the x-coordinate (in data space, must be finite). + * @param y the y-coordinate (in data space, must be finite). + */ + public XYTextAnnotation(String text, double x, double y) { + super(); + Args.nullNotPermitted(text, "text"); + Args.requireFinite(x, "x"); + Args.requireFinite(y, "y"); + this.text = text; + this.font = DEFAULT_FONT; + this.paint = DEFAULT_PAINT; + this.x = x; + this.y = y; + this.textAnchor = DEFAULT_TEXT_ANCHOR; + this.rotationAnchor = DEFAULT_ROTATION_ANCHOR; + this.rotationAngle = DEFAULT_ROTATION_ANGLE; + + // by default the outline and background won't be visible + this.backgroundPaint = null; + this.outlineVisible = false; + this.outlinePaint = Color.BLACK; + this.outlineStroke = new BasicStroke(0.5f); + } + + /** + * Returns the text for the annotation. + * + * @return The text (never {@code null}). + * + * @see #setText(String) + */ + public String getText() { + return this.text; + } + + /** + * Sets the text for the annotation. + * + * @param text the text ({@code null} not permitted). + * + * @see #getText() + */ + public void setText(String text) { + Args.nullNotPermitted(text, "text"); + this.text = text; + fireAnnotationChanged(); + } + + /** + * Returns the font for the annotation. + * + * @return The font (never {@code null}). + * + * @see #setFont(Font) + */ + public Font getFont() { + return this.font; + } + + /** + * Sets the font for the annotation and calls + * {@link #fireAnnotationChanged()}. + * + * @param font the font ({@code null} not permitted). + * + * @see #getFont() + */ + public void setFont(Font font) { + Args.nullNotPermitted(font, "font"); + this.font = font; + fireAnnotationChanged(); + } + + /** + * Returns the paint for the annotation. + * + * @return The paint (never {@code null}). + * + * @see #setPaint(Paint) + */ + public Paint getPaint() { + return this.paint; + } + + /** + * Sets the paint for the annotation and calls + * {@link #fireAnnotationChanged()}. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getPaint() + */ + public void setPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.paint = paint; + fireAnnotationChanged(); + } + + /** + * Returns the text anchor. + * + * @return The text anchor (never {@code null}). + * + * @see #setTextAnchor(TextAnchor) + */ + public TextAnchor getTextAnchor() { + return this.textAnchor; + } + + /** + * Sets the text anchor (the point on the text bounding rectangle that is + * aligned to the (x, y) coordinate of the annotation) and calls + * {@link #fireAnnotationChanged()}. + * + * @param anchor the anchor point ({@code null} not permitted). + * + * @see #getTextAnchor() + */ + public void setTextAnchor(TextAnchor anchor) { + Args.nullNotPermitted(anchor, "anchor"); + this.textAnchor = anchor; + fireAnnotationChanged(); + } + + /** + * Returns the rotation anchor. + * + * @return The rotation anchor point (never {@code null}). + * + * @see #setRotationAnchor(TextAnchor) + */ + public TextAnchor getRotationAnchor() { + return this.rotationAnchor; + } + + /** + * Sets the rotation anchor point and calls + * {@link #fireAnnotationChanged()}. + * + * @param anchor the anchor ({@code null} not permitted). + * + * @see #getRotationAnchor() + */ + public void setRotationAnchor(TextAnchor anchor) { + Args.nullNotPermitted(anchor, "anchor"); + this.rotationAnchor = anchor; + fireAnnotationChanged(); + } + + /** + * Returns the rotation angle. + * + * @return The rotation angle. + * + * @see #setRotationAngle(double) + */ + public double getRotationAngle() { + return this.rotationAngle; + } + + /** + * Sets the rotation angle and calls {@link #fireAnnotationChanged()}. + * The angle is measured clockwise in radians. + * + * @param angle the angle (in radians). + * + * @see #getRotationAngle() + */ + public void setRotationAngle(double angle) { + this.rotationAngle = angle; + fireAnnotationChanged(); + } + + /** + * Returns the x coordinate for the text anchor point (measured against the + * domain axis). + * + * @return The x coordinate (in data space). + * + * @see #setX(double) + */ + public double getX() { + return this.x; + } + + /** + * Sets the x coordinate for the text anchor point (measured against the + * domain axis) and calls {@link #fireAnnotationChanged()}. + * + * @param x the x coordinate (in data space). + * + * @see #getX() + */ + public void setX(double x) { + Args.requireFinite(x, "x"); + this.x = x; + fireAnnotationChanged(); + } + + /** + * Returns the y coordinate for the text anchor point (measured against the + * range axis). + * + * @return The y coordinate (in data space). + * + * @see #setY(double) + */ + public double getY() { + return this.y; + } + + /** + * Sets the y coordinate for the text anchor point (measured against the + * range axis) and calls {@link #fireAnnotationChanged()}. + * + * @param y the y coordinate. + * + * @see #getY() + */ + public void setY(double y) { + Args.requireFinite(y, "y"); + this.y = y; + fireAnnotationChanged(); + } + + /** + * Returns the background paint for the annotation. + * + * @return The background paint (possibly {@code null}). + * + * @see #setBackgroundPaint(Paint) + */ + public Paint getBackgroundPaint() { + return this.backgroundPaint; + } + + /** + * Sets the background paint for the annotation and calls + * {@link #fireAnnotationChanged()}. + * + * @param paint the paint ({@code null} permitted). + * + * @see #getBackgroundPaint() + */ + public void setBackgroundPaint(Paint paint) { + this.backgroundPaint = paint; + fireAnnotationChanged(); + } + + /** + * Returns the outline paint for the annotation. + * + * @return The outline paint (never {@code null}). + * + * @see #setOutlinePaint(Paint) + */ + public Paint getOutlinePaint() { + return this.outlinePaint; + } + + /** + * Sets the outline paint for the annotation and calls + * {@link #fireAnnotationChanged()}. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getOutlinePaint() + */ + public void setOutlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.outlinePaint = paint; + fireAnnotationChanged(); + } + + /** + * Returns the outline stroke for the annotation. + * + * @return The outline stroke (never {@code null}). + * + * @see #setOutlineStroke(Stroke) + */ + public Stroke getOutlineStroke() { + return this.outlineStroke; + } + + /** + * Sets the outline stroke for the annotation and calls + * {@link #fireAnnotationChanged()}. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getOutlineStroke() + */ + public void setOutlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.outlineStroke = stroke; + fireAnnotationChanged(); + } + + /** + * Returns the flag that controls whether or not the outline is drawn. + * + * @return A boolean. + */ + public boolean isOutlineVisible() { + return this.outlineVisible; + } + + /** + * Sets the flag that controls whether or not the outline is drawn and + * calls {@link #fireAnnotationChanged()}. + * + * @param visible the new flag value. + */ + public void setOutlineVisible(boolean visible) { + this.outlineVisible = visible; + fireAnnotationChanged(); + } + + /** + * Draws the annotation. + * + * @param g2 the graphics device. + * @param plot the plot. + * @param dataArea the data area. + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + * @param rendererIndex the renderer index. + * @param info an optional info object that will be populated with + * entity information. + */ + @Override + public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, + ValueAxis domainAxis, ValueAxis rangeAxis, + int rendererIndex, PlotRenderingInfo info) { + + PlotOrientation orientation = plot.getOrientation(); + RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( + plot.getDomainAxisLocation(), orientation); + RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( + plot.getRangeAxisLocation(), orientation); + + float anchorX = (float) domainAxis.valueToJava2D( + this.x, dataArea, domainEdge); + float anchorY = (float) rangeAxis.valueToJava2D( + this.y, dataArea, rangeEdge); + + if (orientation == PlotOrientation.HORIZONTAL) { + float tempAnchor = anchorX; + anchorX = anchorY; + anchorY = tempAnchor; + } + + g2.setFont(getFont()); + Shape hotspot = TextUtils.calculateRotatedStringBounds( + getText(), g2, anchorX, anchorY, getTextAnchor(), + getRotationAngle(), getRotationAnchor()); + if (this.backgroundPaint != null) { + g2.setPaint(this.backgroundPaint); + g2.fill(hotspot); + } + g2.setPaint(getPaint()); + TextUtils.drawRotatedString(getText(), g2, anchorX, anchorY, + getTextAnchor(), getRotationAngle(), getRotationAnchor()); + if (this.outlineVisible) { + g2.setStroke(this.outlineStroke); + g2.setPaint(this.outlinePaint); + g2.draw(hotspot); + } + + String toolTip = getToolTipText(); + String url = getURL(); + if (toolTip != null || url != null) { + addEntity(info, hotspot, rendererIndex, toolTip, url); + } + + } + + /** + * Tests this annotation for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof XYTextAnnotation)) { + return false; + } + XYTextAnnotation that = (XYTextAnnotation) obj; + if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(that.x)) { + return false; + } + if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(that.y)) { + return false; + } + if (Double.doubleToLongBits(this.rotationAngle) != + Double.doubleToLongBits(that.rotationAngle)) { + return false; + } + if (!PaintUtils.equal(this.paint, that.paint)) { + return false; + } + if (this.outlineVisible != that.outlineVisible) { + return false; + } + if (!Objects.equals(this.text, that.text)) { + return false; + } + if (!Objects.equals(this.font, that.font)) { + return false; + } + if (!Objects.equals(this.textAnchor, that.textAnchor)) { + return false; + } + if (!Objects.equals(this.rotationAnchor, that.rotationAnchor)) { + return false; + } + if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) { + return false; + } + if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { + return false; + } + if (!Objects.equals(this.outlineStroke, that.outlineStroke)) { + return false; + } + // fix the "equals not symmetric" problem + if (!that.canEqual(this)) { + return false; + } + + return super.equals(obj); + } + + /** + * Ensures symmetry between super/subclass implementations of equals. For + * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. + * + * @param other Object + * + * @return true ONLY if the parameter is THIS class type + */ + @Override + public boolean canEqual(Object other) { + // fix the "equals not symmetric" problem + return (other instanceof XYTextAnnotation); + } + + /** + * Returns a hash code for the object. + * + * @return A hash code. + */ + @Override + public int hashCode() { + int hash = super.hashCode(); // equals calls superclass, hashCode must also + hash = 23 * hash + Objects.hashCode(this.text); + hash = 23 * hash + Objects.hashCode(this.font); + hash = 23 * hash + HashUtils.hashCodeForPaint(this.paint); + hash = 23 * hash + (int) (Double.doubleToLongBits(this.x) ^ + (Double.doubleToLongBits(this.x) >>> 32)); + hash = 23 * hash + (int) (Double.doubleToLongBits(this.y) ^ + (Double.doubleToLongBits(this.y) >>> 32)); + hash = 23 * hash + Objects.hashCode(this.textAnchor); + hash = 23 * hash + Objects.hashCode(this.rotationAnchor); + hash = 23 * hash + (int) (Double.doubleToLongBits(this.rotationAngle) ^ + (Double.doubleToLongBits(this.rotationAngle) >>> 32)); + hash = 23 * hash + HashUtils.hashCodeForPaint(this.backgroundPaint); + hash = 23 * hash + (this.outlineVisible ? 1 : 0); + hash = 23 * hash + HashUtils.hashCodeForPaint(this.outlinePaint); + hash = 23 * hash + Objects.hashCode(this.outlineStroke); + return hash; + } + + /** + * Returns a clone of the annotation. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the annotation can't be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writePaint(this.paint, stream); + SerialUtils.writePaint(this.backgroundPaint, stream); + SerialUtils.writePaint(this.outlinePaint, stream); + SerialUtils.writeStroke(this.outlineStroke, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + this.paint = SerialUtils.readPaint(stream); + this.backgroundPaint = SerialUtils.readPaint(stream); + this.outlinePaint = SerialUtils.readPaint(stream); + this.outlineStroke = SerialUtils.readStroke(stream); + } + +} diff --git a/src/main/java/org/jfree/chart/annotations/XYTitleAnnotation.java b/src/main/java/org/jfree/chart/annotations/XYTitleAnnotation.java index 1415b4d76..36cea96ec 100644 --- a/src/main/java/org/jfree/chart/annotations/XYTitleAnnotation.java +++ b/src/main/java/org/jfree/chart/annotations/XYTitleAnnotation.java @@ -48,7 +48,6 @@ import org.jfree.chart.block.BlockParams; import org.jfree.chart.block.EntityBlockResult; import org.jfree.chart.block.RectangleConstraint; -import org.jfree.chart.event.AnnotationChangeEvent; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; @@ -185,8 +184,7 @@ public double getMaxWidth() { } /** - * Sets the maximum width and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the maximum width and calls {@link #fireAnnotationChanged()}. * * @param max the maximum width (0.0 or less means no maximum). */ @@ -205,8 +203,7 @@ public double getMaxHeight() { } /** - * Sets the maximum height and sends an - * {@link AnnotationChangeEvent} to all registered listeners. + * Sets the maximum height and calls {@link #fireAnnotationChanged()}. * * @param max the maximum height. */ diff --git a/src/main/java/org/jfree/chart/axis/CategoryAxis.java b/src/main/java/org/jfree/chart/axis/CategoryAxis.java index 717590840..0c8c4eb79 100644 --- a/src/main/java/org/jfree/chart/axis/CategoryAxis.java +++ b/src/main/java/org/jfree/chart/axis/CategoryAxis.java @@ -58,7 +58,6 @@ import org.jfree.chart.entity.CategoryLabelEntity; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.AxisChangeEvent; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.PlotRenderingInfo; @@ -174,8 +173,7 @@ public double getLowerMargin() { } /** - * Sets the lower margin for the axis and sends an {@link AxisChangeEvent} - * to all registered listeners. + * Sets the lower margin for the axis and calls {@link #fireChangeEvent()}. * * @param margin the margin as a percentage of the axis length (for * example, 0.05 is five percent). @@ -200,8 +198,7 @@ public double getUpperMargin() { } /** - * Sets the upper margin for the axis and sends an {@link AxisChangeEvent} - * to all registered listeners. + * Sets the upper margin for the axis and calls {@link #fireChangeEvent()}. * * @param margin the margin as a percentage of the axis length (for * example, 0.05 is five percent). @@ -225,9 +222,9 @@ public double getCategoryMargin() { } /** - * Sets the category margin and sends an {@link AxisChangeEvent} to all - * registered listeners. The overall category margin is distributed over - * N-1 gaps, where N is the number of categories on the axis. + * Sets the category margin and calls {@link #fireChangeEvent()}. The + * overall category margin is distributed over N-1 gaps, where N is the + * number of categories on the axis. * * @param margin the margin as a percentage of the axis length (for * example, 0.05 is five percent). @@ -251,8 +248,8 @@ public int getMaximumCategoryLabelLines() { } /** - * Sets the maximum number of lines to use for each category label and - * sends an {@link AxisChangeEvent} to all registered listeners. + * Sets the maximum number of lines to use for each category label and calls + * {@link #fireChangeEvent()}. * * @param lines the maximum number of lines. * @@ -275,8 +272,8 @@ public float getMaximumCategoryLabelWidthRatio() { } /** - * Sets the maximum category label width ratio and sends an - * {@link AxisChangeEvent} to all registered listeners. + * Sets the maximum category label width ratio and calls + * {@link #fireChangeEvent()}. * * @param ratio the ratio. * @@ -326,8 +323,8 @@ public CategoryLabelPositions getCategoryLabelPositions() { } /** - * Sets the category label position specification for the axis and sends an - * {@link AxisChangeEvent} to all registered listeners. + * Sets the category label position specification for the axis and calls + * {@link #fireChangeEvent()}. * * @param positions the positions ({@code null} not permitted). * @@ -359,8 +356,8 @@ public Font getTickLabelFont(Comparable category) { } /** - * Sets the font for the tick label for the specified category and sends - * an {@link AxisChangeEvent} to all registered listeners. + * Sets the font for the tick label for the specified category and calls + * {@link #fireChangeEvent()}. * * @param category the category ({@code null} not permitted). * @param font the font ({@code null} permitted). @@ -398,8 +395,8 @@ public Paint getTickLabelPaint(Comparable category) { } /** - * Sets the paint for the tick label for the specified category and sends - * an {@link AxisChangeEvent} to all registered listeners. + * Sets the paint for the tick label for the specified category and calls + * {@link #fireChangeEvent()}. * * @param category the category ({@code null} not permitted). * @param paint the paint ({@code null} permitted). @@ -418,8 +415,8 @@ public void setTickLabelPaint(Comparable category, Paint paint) { } /** - * Adds a tooltip to the specified category and sends an - * {@link AxisChangeEvent} to all registered listeners. + * Adds a tooltip to the specified category and calls + * {@link #fireChangeEvent()}. * * @param category the category ({@code null} not permitted). * @param tooltip the tooltip text ({@code null} permitted). @@ -450,8 +447,7 @@ public String getCategoryLabelToolTip(Comparable category) { /** * Removes the tooltip for the specified category and, if there was a value - * associated with that category, sends an {@link AxisChangeEvent} to all - * registered listeners. + * associated with that category, calls {@link #fireChangeEvent()}. * * @param category the category ({@code null} not permitted). * @@ -466,8 +462,7 @@ public void removeCategoryLabelToolTip(Comparable category) { } /** - * Clears the category label tooltips and sends an {@link AxisChangeEvent} - * to all registered listeners. + * Clears the category label tooltips and calls {@link #fireChangeEvent()}. * * @see #addCategoryLabelToolTip(Comparable, String) * @see #removeCategoryLabelToolTip(Comparable) @@ -478,8 +473,8 @@ public void clearCategoryLabelToolTips() { } /** - * Adds a URL (to be used in image maps) to the specified category and - * sends an {@link AxisChangeEvent} to all registered listeners. + * Adds a URL (to be used in image maps) to the specified category and calls + * {@link #fireChangeEvent()}. * * @param category the category ({@code null} not permitted). * @param url the URL text ({@code null} permitted). @@ -509,8 +504,7 @@ public String getCategoryLabelURL(Comparable category) { /** * Removes the URL for the specified category and, if there was a URL - * associated with that category, sends an {@link AxisChangeEvent} to all - * registered listeners. + * associated with that category, calls {@link #fireChangeEvent()}. * * @param category the category ({@code null} not permitted). * @@ -525,8 +519,7 @@ public void removeCategoryLabelURL(Comparable category) { } /** - * Clears the category label URLs and sends an {@link AxisChangeEvent} - * to all registered listeners. + * Clears the category label URLs and calls {@link #fireChangeEvent()}. * * @see #addCategoryLabelURL(Comparable, String) * @see #removeCategoryLabelURL(Comparable) diff --git a/src/main/java/org/jfree/chart/axis/DateAxis.java b/src/main/java/org/jfree/chart/axis/DateAxis.java index b050da844..d555e4ada 100644 --- a/src/main/java/org/jfree/chart/axis/DateAxis.java +++ b/src/main/java/org/jfree/chart/axis/DateAxis.java @@ -60,7 +60,6 @@ import java.util.Objects; import java.util.TimeZone; -import org.jfree.chart.event.AxisChangeEvent; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.PlotRenderingInfo; import org.jfree.chart.plot.ValueAxisPlot; @@ -295,8 +294,7 @@ public TimeZone getTimeZone() { } /** - * Sets the time zone for the axis and sends an {@link AxisChangeEvent} to - * all registered listeners. + * Sets the time zone for the axis and calls {@link #fireChangeEvent()}. * * @param zone the time zone ({@code null} not permitted). * @@ -343,7 +341,7 @@ public Timeline getTimeline() { /** * Sets the underlying timeline to use for this axis. If the timeline is - * changed, an {@link AxisChangeEvent} is sent to all registered listeners. + * changed, it calls {@link #fireChangeEvent()}. * * @param timeline the timeline. */ @@ -386,8 +384,7 @@ public void setTickUnit(DateTickUnit unit) { } /** - * Sets the tick unit attribute and, if requested, sends an - * {@link AxisChangeEvent} to all registered listeners. + * Sets the tick unit attribute and, if requested, calls {@link #fireChangeEvent()}. * * @param unit the new tick unit. * @param notify notify registered listeners? @@ -419,9 +416,8 @@ public DateFormat getDateFormatOverride() { } /** - * Sets the date format override and sends an {@link AxisChangeEvent} to - * all registered listeners. If this is non-null, then it will be - * used to format the dates on the axis. + * Sets the date format override and calls {@link #fireChangeEvent()}. If + * this is non-null, then it will be used to format the dates on the axis. * * @param formatter the date formatter ({@code null} permitted). */ @@ -431,9 +427,8 @@ public void setDateFormatOverride(DateFormat formatter) { } /** - * Sets the upper and lower bounds for the axis and sends an - * {@link AxisChangeEvent} to all registered listeners. As a side-effect, - * the auto-range flag is set to false. + * Sets the upper and lower bounds for the axis by calling + * {@link #setRange(org.jfree.data.Range, boolean, boolean)}. * * @param range the new range ({@code null} not permitted). */ @@ -443,9 +438,8 @@ public void setRange(Range range) { } /** - * Sets the range for the axis, if requested, sends an - * {@link AxisChangeEvent} to all registered listeners. As a side-effect, - * the auto-range flag is set to {@code false} (optional). + * Sets the range for the axis by calling + * {@link #setRange(org.jfree.data.Range, boolean, boolean)}.. * * @param range the range ({@code null} not permitted). * @param turnOffAutoRange a flag that controls whether or not the auto @@ -466,8 +460,7 @@ public void setRange(Range range, boolean turnOffAutoRange, } /** - * Sets the axis range and sends an {@link AxisChangeEvent} to all - * registered listeners. + * Sets the axis range by calling {@link #setRange(org.jfree.data.Range)}. * * @param lower the lower bound for the axis. * @param upper the upper bound for the axis. @@ -480,8 +473,7 @@ public void setRange(Date lower, Date upper) { } /** - * Sets the axis range and sends an {@link AxisChangeEvent} to all - * registered listeners. + * Sets the axis range by calling {@link #setRange(org.jfree.data.Range)}. * * @param lower the lower bound for the axis. * @param upper the upper bound for the axis. @@ -516,11 +508,10 @@ public Date getMinimumDate() { } /** - * Sets the minimum date visible on the axis and sends an - * {@link AxisChangeEvent} to all registered listeners. If - * {@code date} is on or after the current maximum date for - * the axis, the maximum date will be shifted to preserve the current - * length of the axis. + * Sets the minimum date visible on the axis calls + * {@link #fireChangeEvent()}. If {@code date} is on or after the current + * maximum date for the axis, the maximum date will be shifted to preserve + * the current length of the axis. * * @param date the date ({@code null} not permitted). * @@ -564,11 +555,10 @@ public Date getMaximumDate() { } /** - * Sets the maximum date visible on the axis and sends an - * {@link AxisChangeEvent} to all registered listeners. If - * {@code maximumDate} is on or before the current minimum date for - * the axis, the minimum date will be shifted to preserve the current - * length of the axis. + * Sets the maximum date visible on the axis and calls + * {@link #fireChangeEvent()}. If {@code maximumDate} is on or before the + * current minimum date for the axis, the minimum date will be shifted to + * preserve the current length of the axis. * * @param maximumDate the date ({@code null} not permitted). * @@ -601,7 +591,7 @@ public DateTickMarkPosition getTickMarkPosition() { /** * Sets the tick mark position (start, middle or end of the time period) - * and sends an {@link AxisChangeEvent} to all registered listeners. + * and calls {@link #fireChangeEvent()}. * * @param position the position ({@code null} not permitted). */ diff --git a/src/main/java/org/jfree/chart/axis/LogAxis.java b/src/main/java/org/jfree/chart/axis/LogAxis.java index db23e8ca0..28e88ec06 100644 --- a/src/main/java/org/jfree/chart/axis/LogAxis.java +++ b/src/main/java/org/jfree/chart/axis/LogAxis.java @@ -1,1040 +1,1034 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------ - * LogAxis.java - * ------------ - * (C) Copyright 2006-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): Andrew Mickish (patch 1868745); - * Peter Kolb (patches 1934255 and 2603321); - */ - -package org.jfree.chart.axis; - -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.font.FontRenderContext; -import java.awt.font.LineMetrics; -import java.awt.font.TextAttribute; -import java.awt.geom.Rectangle2D; -import java.text.AttributedString; -import java.text.DecimalFormat; -import java.text.Format; -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import org.jfree.chart.event.AxisChangeEvent; -import org.jfree.chart.plot.Plot; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.ValueAxisPlot; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.RectangleInsets; -import org.jfree.chart.ui.TextAnchor; -import org.jfree.chart.util.AttrStringUtils; -import org.jfree.chart.util.Args; -import org.jfree.data.Range; - -/** - * A numerical axis that uses a logarithmic scale. The class is an - * alternative to the {@link LogarithmicAxis} class. - */ -public class LogAxis extends ValueAxis { - - /** The logarithm base. */ - private double base = 10.0; - - /** The logarithm of the base value - cached for performance. */ - private double baseLog = Math.log(10.0); - - /** - * The base symbol to display (if {@code null} then the numerical - * value of the base is displayed). - */ - private String baseSymbol = null; - - /** - * The formatter to use for the base value when the base is displayed - * as a numerical value. - */ - private Format baseFormatter = new DecimalFormat("0"); - - /** The smallest value permitted on the axis. */ - private double smallestValue = 1E-100; - - /** The current tick unit. */ - private NumberTickUnit tickUnit; - - /** The override number format. */ - private NumberFormat numberFormatOverride; - - /** - * Creates a new {@code LogAxis} with no label. - */ - public LogAxis() { - this(null); - } - - /** - * Creates a new {@code LogAxis} with the given label. - * - * @param label the axis label ({@code null} permitted). - */ - public LogAxis(String label) { - super(label, new NumberTickUnitSource()); - setDefaultAutoRange(new Range(0.01, 1.0)); - this.tickUnit = new NumberTickUnit(1.0, new DecimalFormat("0.#"), 10); - } - - /** - * Returns the base for the logarithm calculation. The default value is - * {@code 10.0}. - * - * @return The base for the logarithm calculation. - * - * @see #setBase(double) - */ - public double getBase() { - return this.base; - } - - /** - * Sets the base for the logarithm calculation and sends a change event to - * all registered listeners. - * - * @param base the base value (must be > 1.0). - * - * @see #getBase() - */ - public void setBase(double base) { - if (base <= 1.0) { - throw new IllegalArgumentException("Requires 'base' > 1.0."); - } - this.base = base; - this.baseLog = Math.log(base); - fireChangeEvent(); - } - - /** - * Returns the symbol used to represent the base of the logarithmic scale - * for the axis. If this is {@code null} (the default) then the - * numerical value of the base is displayed. - * - * @return The base symbol (possibly {@code null}). - */ - public String getBaseSymbol() { - return this.baseSymbol; - } - - /** - * Sets the symbol used to represent the base value of the logarithmic - * scale and sends a change event to all registered listeners. - * - * @param symbol the symbol ({@code null} permitted). - */ - public void setBaseSymbol(String symbol) { - this.baseSymbol = symbol; - fireChangeEvent(); - } - - /** - * Returns the formatter used to format the base value of the logarithmic - * scale when it is displayed numerically. The default value is - * {@code new DecimalFormat("0")}. - * - * @return The base formatter (never {@code null}). - */ - public Format getBaseFormatter() { - return this.baseFormatter; - } - - /** - * Sets the formatter used to format the base value of the logarithmic - * scale when it is displayed numerically and sends a change event to all - * registered listeners. - * - * @param formatter the formatter ({@code null} not permitted). - */ - public void setBaseFormatter(Format formatter) { - Args.nullNotPermitted(formatter, "formatter"); - this.baseFormatter = formatter; - fireChangeEvent(); - } - - /** - * Returns the smallest value represented by the axis. - * - * @return The smallest value represented by the axis. - * - * @see #setSmallestValue(double) - */ - public double getSmallestValue() { - return this.smallestValue; - } - - /** - * Sets the smallest value represented by the axis and sends a change event - * to all registered listeners. - * - * @param value the value. - * - * @see #getSmallestValue() - */ - public void setSmallestValue(double value) { - if (value <= 0.0) { - throw new IllegalArgumentException("Requires 'value' > 0.0."); - } - this.smallestValue = value; - fireChangeEvent(); - } - - /** - * Returns the current tick unit. - * - * @return The current tick unit. - * - * @see #setTickUnit(NumberTickUnit) - */ - public NumberTickUnit getTickUnit() { - return this.tickUnit; - } - - /** - * Sets the tick unit for the axis and sends an {@link AxisChangeEvent} to - * all registered listeners. A side effect of calling this method is that - * the "auto-select" feature for tick units is switched off (you can - * restore it using the {@link ValueAxis#setAutoTickUnitSelection(boolean)} - * method). - * - * @param unit the new tick unit ({@code null} not permitted). - * - * @see #getTickUnit() - */ - public void setTickUnit(NumberTickUnit unit) { - // defer argument checking... - setTickUnit(unit, true, true); - } - - /** - * Sets the tick unit for the axis and, if requested, sends an - * {@link AxisChangeEvent} to all registered listeners. In addition, an - * option is provided to turn off the "auto-select" feature for tick units - * (you can restore it using the - * {@link ValueAxis#setAutoTickUnitSelection(boolean)} method). - * - * @param unit the new tick unit ({@code null} not permitted). - * @param notify notify listeners? - * @param turnOffAutoSelect turn off the auto-tick selection? - * - * @see #getTickUnit() - */ - public void setTickUnit(NumberTickUnit unit, boolean notify, - boolean turnOffAutoSelect) { - Args.nullNotPermitted(unit, "unit"); - this.tickUnit = unit; - if (turnOffAutoSelect) { - setAutoTickUnitSelection(false, false); - } - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the number format override. If this is non-{@code null}, - * then it will be used to format the numbers on the axis. - * - * @return The number formatter (possibly {@code null}). - * - * @see #setNumberFormatOverride(NumberFormat) - */ - public NumberFormat getNumberFormatOverride() { - return this.numberFormatOverride; - } - - /** - * Sets the number format override and sends a change event to all - * registered listeners. If this is non-{@code null}, then it will be - * used to format the numbers on the axis. - * - * @param formatter the number formatter ({@code null} permitted). - * - * @see #getNumberFormatOverride() - */ - public void setNumberFormatOverride(NumberFormat formatter) { - this.numberFormatOverride = formatter; - fireChangeEvent(); - } - - /** - * Calculates the log of the given value, using the current base. - * - * @param value the value. - * - * @return The log of the given value. - * - * @see #calculateValue(double) - * @see #getBase() - */ - public double calculateLog(double value) { - return Math.log(value) / this.baseLog; - } - - /** - * Calculates the value from a given log. - * - * @param log the log value. - * - * @return The value with the given log. - * - * @see #calculateLog(double) - * @see #getBase() - */ - public double calculateValue(double log) { - return Math.pow(this.base, log); - } - - private double calculateValueNoINF(double log) { - double result = calculateValue(log); - if (Double.isInfinite(result)) { - result = Double.MAX_VALUE; - } - if (result <= 0.0) { - result = Double.MIN_VALUE; - } - return result; - } - - /** - * Converts a Java2D coordinate to an axis value, assuming that the - * axis is aligned to the specified {@code edge} of the {@code area}. - * - * @param java2DValue the Java2D coordinate. - * @param area the area for plotting data ({@code null} not - * permitted). - * @param edge the edge that the axis is aligned to ({@code null} not - * permitted). - * - * @return A value along the axis scale. - */ - @Override - public double java2DToValue(double java2DValue, Rectangle2D area, - RectangleEdge edge) { - - Range range = getRange(); - double axisMin = calculateLog(Math.max(this.smallestValue, - range.getLowerBound())); - double axisMax = calculateLog(range.getUpperBound()); - - double min = 0.0; - double max = 0.0; - if (RectangleEdge.isTopOrBottom(edge)) { - min = area.getX(); - max = area.getMaxX(); - } else if (RectangleEdge.isLeftOrRight(edge)) { - min = area.getMaxY(); - max = area.getY(); - } - double log; - if (isInverted()) { - log = axisMax - (java2DValue - min) / (max - min) - * (axisMax - axisMin); - } else { - log = axisMin + (java2DValue - min) / (max - min) - * (axisMax - axisMin); - } - return calculateValue(log); - } - - /** - * Converts a value on the axis scale to a Java2D coordinate relative to - * the given {@code area}, based on the axis running along the - * specified {@code edge}. - * - * @param value the data value. - * @param area the area ({@code null} not permitted). - * @param edge the edge ({@code null} not permitted). - * - * @return The Java2D coordinate corresponding to {@code value}. - */ - @Override - public double valueToJava2D(double value, Rectangle2D area, - RectangleEdge edge) { - - Range range = getRange(); - double axisMin = calculateLog(range.getLowerBound()); - double axisMax = calculateLog(range.getUpperBound()); - value = calculateLog(value); - - double min = 0.0; - double max = 0.0; - if (RectangleEdge.isTopOrBottom(edge)) { - min = area.getX(); - max = area.getMaxX(); - } else if (RectangleEdge.isLeftOrRight(edge)) { - max = area.getMinY(); - min = area.getMaxY(); - } - if (isInverted()) { - return max - - ((value - axisMin) / (axisMax - axisMin)) * (max - min); - } else { - return min - + ((value - axisMin) / (axisMax - axisMin)) * (max - min); - } - } - - /** - * Configures the axis. This method is typically called when an axis - * is assigned to a new plot. - */ - @Override - public void configure() { - if (isAutoRange()) { - autoAdjustRange(); - } - } - - /** - * Adjusts the axis range to match the data range that the axis is - * required to display. - */ - @Override - protected void autoAdjustRange() { - Plot plot = getPlot(); - if (plot == null) { - return; // no plot, no data - } - - if (plot instanceof ValueAxisPlot) { - ValueAxisPlot vap = (ValueAxisPlot) plot; - - Range r = vap.getDataRange(this); - if (r == null) { - r = getDefaultAutoRange(); - } - - double upper = r.getUpperBound(); - double lower = Math.max(r.getLowerBound(), this.smallestValue); - double range = upper - lower; - - // if fixed auto range, then derive lower bound... - double fixedAutoRange = getFixedAutoRange(); - if (fixedAutoRange > 0.0) { - lower = Math.max(upper - fixedAutoRange, this.smallestValue); - } - else { - // ensure the autorange is at least in size... - double minRange = getAutoRangeMinimumSize(); - if (range < minRange) { - double expand = (minRange - range) / 2; - upper = upper + expand; - lower = lower - expand; - } - - // apply the margins - these should apply to the exponent range - double logUpper = calculateLog(upper); - double logLower = calculateLog(lower); - double logRange = logUpper - logLower; - logUpper = logUpper + getUpperMargin() * logRange; - logLower = logLower - getLowerMargin() * logRange; - upper = calculateValueNoINF(logUpper); - lower = calculateValueNoINF(logLower); - } - setRange(new Range(lower, upper), false, false); - } - - } - - /** - * Draws the axis on a Java 2D graphics device (such as the screen or a - * printer). - * - * @param g2 the graphics device ({@code null} not permitted). - * @param cursor the cursor location (determines where to draw the axis). - * @param plotArea the area within which the axes and plot should be drawn. - * @param dataArea the area within which the data should be drawn. - * @param edge the axis location ({@code null} not permitted). - * @param plotState collects information about the plot ({@code null} - * permitted). - * - * @return The axis state (never {@code null}). - */ - @Override - public AxisState draw(Graphics2D g2, double cursor, Rectangle2D plotArea, - Rectangle2D dataArea, RectangleEdge edge, - PlotRenderingInfo plotState) { - - AxisState state; - // if the axis is not visible, don't draw it... - if (!isVisible()) { - state = new AxisState(cursor); - // even though the axis is not visible, we need ticks for the - // gridlines... - List ticks = refreshTicks(g2, state, dataArea, edge); - state.setTicks(ticks); - return state; - } - state = drawTickMarksAndLabels(g2, cursor, plotArea, dataArea, edge); - if (getAttributedLabel() != null) { - state = drawAttributedLabel(getAttributedLabel(), g2, plotArea, - dataArea, edge, state); - - } else { - state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state); - } - createAndAddEntity(cursor, state, dataArea, edge, plotState); - return state; - } - - /** - * Calculates the positions of the tick labels for the axis, storing the - * results in the tick label list (ready for drawing). - * - * @param g2 the graphics device. - * @param state the axis state. - * @param dataArea the area in which the plot should be drawn. - * @param edge the location of the axis. - * - * @return A list of ticks. - */ - @Override - public List refreshTicks(Graphics2D g2, AxisState state, - Rectangle2D dataArea, RectangleEdge edge) { - List result = new java.util.ArrayList(); - if (RectangleEdge.isTopOrBottom(edge)) { - result = refreshTicksHorizontal(g2, dataArea, edge); - } - else if (RectangleEdge.isLeftOrRight(edge)) { - result = refreshTicksVertical(g2, dataArea, edge); - } - return result; - } - - /** - * Returns a list of ticks for an axis at the top or bottom of the chart. - * - * @param g2 the graphics device ({@code null} not permitted). - * @param dataArea the data area ({@code null} not permitted). - * @param edge the edge ({@code null} not permitted). - * - * @return A list of ticks. - */ - protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea, - RectangleEdge edge) { - - Range range = getRange(); - List ticks = new ArrayList(); - Font tickLabelFont = getTickLabelFont(); - g2.setFont(tickLabelFont); - TextAnchor textAnchor; - if (edge == RectangleEdge.TOP) { - textAnchor = TextAnchor.BOTTOM_CENTER; - } - else { - textAnchor = TextAnchor.TOP_CENTER; - } - - if (isAutoTickUnitSelection()) { - selectAutoTickUnit(g2, dataArea, edge); - } - int minorTickCount = this.tickUnit.getMinorTickCount(); - double unit = getTickUnit().getSize(); - double index = Math.ceil(calculateLog(getRange().getLowerBound()) - / unit); - double start = index * unit; - double end = calculateLog(getUpperBound()); - double current = start; - boolean hasTicks = (this.tickUnit.getSize() > 0.0) - && !Double.isInfinite(start); - while (hasTicks && current <= end) { - double v = calculateValueNoINF(current); - if (range.contains(v)) { - ticks.add(new LogTick(TickType.MAJOR, v, createTickLabel(v), - textAnchor)); - } - // add minor ticks (for gridlines) - double next = Math.pow(this.base, current - + this.tickUnit.getSize()); - for (int i = 1; i < minorTickCount; i++) { - double minorV = v + i * ((next - v) / minorTickCount); - if (range.contains(minorV)) { - ticks.add(new LogTick(TickType.MINOR, minorV, null, - textAnchor)); - } - } - current = current + this.tickUnit.getSize(); - } - return ticks; - } - - /** - * Returns a list of ticks for an axis at the left or right of the chart. - * - * @param g2 the graphics device ({@code null} not permitted). - * @param dataArea the data area ({@code null} not permitted). - * @param edge the edge that the axis is aligned to ({@code null} - * not permitted). - * - * @return A list of ticks. - */ - protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea, - RectangleEdge edge) { - - Range range = getRange(); - List ticks = new ArrayList(); - Font tickLabelFont = getTickLabelFont(); - g2.setFont(tickLabelFont); - TextAnchor textAnchor; - if (edge == RectangleEdge.RIGHT) { - textAnchor = TextAnchor.CENTER_LEFT; - } - else { - textAnchor = TextAnchor.CENTER_RIGHT; - } - - if (isAutoTickUnitSelection()) { - selectAutoTickUnit(g2, dataArea, edge); - } - int minorTickCount = this.tickUnit.getMinorTickCount(); - double unit = getTickUnit().getSize(); - double index = Math.ceil(calculateLog(getRange().getLowerBound()) - / unit); - double start = index * unit; - double end = calculateLog(getUpperBound()); - double current = start; - boolean hasTicks = (this.tickUnit.getSize() > 0.0) - && !Double.isInfinite(start); - while (hasTicks && current <= end) { - double v = calculateValueNoINF(current); - if (range.contains(v)) { - ticks.add(new LogTick(TickType.MAJOR, v, createTickLabel(v), - textAnchor)); - } - // add minor ticks (for gridlines) - double next = Math.pow(this.base, current - + this.tickUnit.getSize()); - for (int i = 1; i < minorTickCount; i++) { - double minorV = v + i * ((next - v) / minorTickCount); - if (range.contains(minorV)) { - ticks.add(new LogTick(TickType.MINOR, minorV, null, - textAnchor)); - } - } - current = current + this.tickUnit.getSize(); - } - return ticks; - } - - /** - * Selects an appropriate tick value for the axis. The strategy is to - * display as many ticks as possible (selected from an array of 'standard' - * tick units) without the labels overlapping. - * - * @param g2 the graphics device ({@code null} not permitted). - * @param dataArea the area defined by the axes ({@code null} not - * permitted). - * @param edge the axis location ({@code null} not permitted). - */ - protected void selectAutoTickUnit(Graphics2D g2, Rectangle2D dataArea, - RectangleEdge edge) { - if (RectangleEdge.isTopOrBottom(edge)) { - selectHorizontalAutoTickUnit(g2, dataArea, edge); - } - else if (RectangleEdge.isLeftOrRight(edge)) { - selectVerticalAutoTickUnit(g2, dataArea, edge); - } - } - - /** - * Selects an appropriate tick value for the axis. The strategy is to - * display as many ticks as possible (selected from an array of 'standard' - * tick units) without the labels overlapping. - * - * @param g2 the graphics device. - * @param dataArea the area defined by the axes. - * @param edge the axis location. - */ - protected void selectHorizontalAutoTickUnit(Graphics2D g2, - Rectangle2D dataArea, RectangleEdge edge) { - - // select a tick unit that is the next one bigger than the current - // (log) range divided by 50 - Range range = getRange(); - double logAxisMin = calculateLog(Math.max(this.smallestValue, - range.getLowerBound())); - double logAxisMax = calculateLog(range.getUpperBound()); - double size = (logAxisMax - logAxisMin) / 50; - TickUnitSource tickUnits = getStandardTickUnits(); - TickUnit candidate = tickUnits.getCeilingTickUnit(size); - TickUnit prevCandidate = candidate; - boolean found = false; - while (!found) { - // while the tick labels overlap and there are more tick sizes available, - // choose the next bigger label - this.tickUnit = (NumberTickUnit) candidate; - double tickLabelWidth = estimateMaximumTickLabelWidth(g2, - candidate); - // what is the available space for one unit? - double candidateWidth = exponentLengthToJava2D(candidate.getSize(), - dataArea, edge); - if (tickLabelWidth < candidateWidth) { - found = true; - } else if (Double.isNaN(candidateWidth)) { - candidate = prevCandidate; - found = true; - } else { - prevCandidate = candidate; - candidate = tickUnits.getLargerTickUnit(prevCandidate); - if (candidate.equals(prevCandidate)) { - found = true; // there are no more candidates - } - } - } - setTickUnit((NumberTickUnit) candidate, false, false); - } - - /** - * Converts a length in data coordinates into the corresponding length in - * Java2D coordinates. - * - * @param length the length. - * @param area the plot area. - * @param edge the edge along which the axis lies. - * - * @return The length in Java2D coordinates. - */ - public double exponentLengthToJava2D(double length, Rectangle2D area, - RectangleEdge edge) { - double one = valueToJava2D(calculateValueNoINF(1.0), area, edge); - double l = valueToJava2D(calculateValueNoINF(length + 1.0), area, edge); - return Math.abs(l - one); - } - - /** - * Selects an appropriate tick value for the axis. The strategy is to - * display as many ticks as possible (selected from an array of 'standard' - * tick units) without the labels overlapping. - * - * @param g2 the graphics device. - * @param dataArea the area in which the plot should be drawn. - * @param edge the axis location. - */ - protected void selectVerticalAutoTickUnit(Graphics2D g2, - Rectangle2D dataArea, RectangleEdge edge) { - // select a tick unit that is the next one bigger than the current - // (log) range divided by 50 - Range range = getRange(); - double logAxisMin = calculateLog(Math.max(this.smallestValue, - range.getLowerBound())); - double logAxisMax = calculateLog(range.getUpperBound()); - double size = (logAxisMax - logAxisMin) / 50; - TickUnitSource tickUnits = getStandardTickUnits(); - TickUnit candidate = tickUnits.getCeilingTickUnit(size); - TickUnit prevCandidate = candidate; - boolean found = false; - while (!found) { - // while the tick labels overlap and there are more tick sizes available, - // choose the next bigger label - this.tickUnit = (NumberTickUnit) candidate; - double tickLabelHeight = estimateMaximumTickLabelHeight(g2); - // what is the available space for one unit? - double candidateHeight = exponentLengthToJava2D(candidate.getSize(), - dataArea, edge); - if (tickLabelHeight < candidateHeight) { - found = true; - } else if (Double.isNaN(candidateHeight)) { - candidate = prevCandidate; - found = true; - } else { - prevCandidate = candidate; - candidate = tickUnits.getLargerTickUnit(prevCandidate); - if (candidate.equals(prevCandidate)) { - found = true; // there are no more candidates - } - } - } - setTickUnit((NumberTickUnit) candidate, false, false); - } - - /** - * Creates a tick label for the specified value based on the current - * tick unit (used for formatting the exponent). - * - * @param value the value. - * - * @return The label. - */ - protected AttributedString createTickLabel(double value) { - if (this.numberFormatOverride != null) { - String text = this.numberFormatOverride.format(value); - AttributedString as = new AttributedString(text); - as.addAttribute(TextAttribute.FONT, getTickLabelFont()); - return as; - } else { - String baseStr = this.baseSymbol; - if (baseStr == null) { - baseStr = this.baseFormatter.format(this.base); - } - double logy = calculateLog(value); - String exponentStr = getTickUnit().valueToString(logy); - AttributedString as = new AttributedString(baseStr + exponentStr); - as.addAttributes(getTickLabelFont().getAttributes(), 0, (baseStr - + exponentStr).length()); - as.addAttribute(TextAttribute.SUPERSCRIPT, - TextAttribute.SUPERSCRIPT_SUPER, baseStr.length(), - baseStr.length() + exponentStr.length()); - return as; - } - } - - /** - * Estimates the maximum tick label height. - * - * @param g2 the graphics device. - * - * @return The maximum height. - */ - protected double estimateMaximumTickLabelHeight(Graphics2D g2) { - RectangleInsets tickLabelInsets = getTickLabelInsets(); - double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom(); - - Font tickLabelFont = getTickLabelFont(); - FontRenderContext frc = g2.getFontRenderContext(); - result += tickLabelFont.getLineMetrics("123", frc).getHeight(); - return result; - } - - /** - * Estimates the maximum width of the tick labels, assuming the specified - * tick unit is used. - *

- * Rather than computing the string bounds of every tick on the axis, we - * just look at two values: the lower bound and the upper bound for the - * axis. These two values will usually be representative. - * - * @param g2 the graphics device. - * @param unit the tick unit to use for calculation. - * - * @return The estimated maximum width of the tick labels. - */ - protected double estimateMaximumTickLabelWidth(Graphics2D g2, - TickUnit unit) { - - RectangleInsets tickLabelInsets = getTickLabelInsets(); - double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight(); - - if (isVerticalTickLabels()) { - // all tick labels have the same width (equal to the height of the - // font)... - FontRenderContext frc = g2.getFontRenderContext(); - LineMetrics lm = getTickLabelFont().getLineMetrics("0", frc); - result += lm.getHeight(); - } - else { - // look at lower and upper bounds... - Range range = getRange(); - double lower = range.getLowerBound(); - double upper = range.getUpperBound(); - AttributedString lowerStr = createTickLabel(lower); - AttributedString upperStr = createTickLabel(upper); - double w1 = AttrStringUtils.getTextBounds(lowerStr, g2).getWidth(); - double w2 = AttrStringUtils.getTextBounds(upperStr, g2).getWidth(); - result += Math.max(w1, w2); - } - return result; - } - - /** - * Zooms in on the current range. - * - * @param lowerPercent the new lower bound. - * @param upperPercent the new upper bound. - */ - @Override - public void zoomRange(double lowerPercent, double upperPercent) { - Range range = getRange(); - double start = range.getLowerBound(); - double end = range.getUpperBound(); - double log1 = calculateLog(start); - double log2 = calculateLog(end); - double length = log2 - log1; - Range adjusted; - if (isInverted()) { - double logA = log1 + length * (1 - upperPercent); - double logB = log1 + length * (1 - lowerPercent); - adjusted = new Range(calculateValueNoINF(logA), - calculateValueNoINF(logB)); - } - else { - double logA = log1 + length * lowerPercent; - double logB = log1 + length * upperPercent; - adjusted = new Range(calculateValueNoINF(logA), - calculateValueNoINF(logB)); - } - setRange(adjusted); - } - - /** - * Slides the axis range by the specified percentage. - * - * @param percent the percentage. - */ - @Override - public void pan(double percent) { - Range range = getRange(); - double lower = range.getLowerBound(); - double upper = range.getUpperBound(); - double log1 = calculateLog(lower); - double log2 = calculateLog(upper); - double length = log2 - log1; - double adj = length * percent; - log1 = log1 + adj; - log2 = log2 + adj; - setRange(calculateValueNoINF(log1), calculateValueNoINF(log2)); - } - - /** - * Increases or decreases the axis range by the specified percentage about - * the central value and sends an {@link AxisChangeEvent} to all registered - * listeners. - *

- * To double the length of the axis range, use 200% (2.0). - * To halve the length of the axis range, use 50% (0.5). - * - * @param percent the resize factor. - * - * @see #resizeRange(double, double) - */ - @Override - public void resizeRange(double percent) { - Range range = getRange(); - double logMin = calculateLog(range.getLowerBound()); - double logMax = calculateLog(range.getUpperBound()); - double centralValue = calculateValueNoINF((logMin + logMax) / 2.0); - resizeRange(percent, centralValue); - } - - @Override - public void resizeRange(double percent, double anchorValue) { - resizeRange2(percent, anchorValue); - } - - /** - * Resizes the axis length to the specified percentage of the current - * range and sends a change event to all registered listeners. If - * {@code percent} is greater than 1.0 (100 percent) then the axis - * range is increased (which has the effect of zooming out), while if the - * {@code percent} is less than 1.0 the axis range is decreased - * (which has the effect of zooming in). The resize occurs around an - * anchor value (which may not be in the center of the axis). This is used - * to support mouse wheel zooming around an arbitrary point on the plot. - *

- * This method is overridden to perform the percentage calculations on the - * log values (which are linear for this axis). - * - * @param percent the percentage (must be greater than zero). - * @param anchorValue the anchor value. - */ - @Override - public void resizeRange2(double percent, double anchorValue) { - if (percent > 0.0) { - double logAnchorValue = calculateLog(anchorValue); - Range range = getRange(); - double logAxisMin = calculateLog(range.getLowerBound()); - double logAxisMax = calculateLog(range.getUpperBound()); - - double left = percent * (logAnchorValue - logAxisMin); - double right = percent * (logAxisMax - logAnchorValue); - - double upperBound = calculateValueNoINF(logAnchorValue + right); - Range adjusted = new Range(calculateValueNoINF( - logAnchorValue - left), upperBound); - setRange(adjusted); - } - else { - setAutoRange(true); - } - } - - /** - * Tests this axis for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof LogAxis)) { - return false; - } - LogAxis that = (LogAxis) obj; - if (this.base != that.base) { - return false; - } - if (!Objects.equals(this.baseSymbol, that.baseSymbol)) { - return false; - } - if (!this.baseFormatter.equals(that.baseFormatter)) { - return false; - } - if (this.smallestValue != that.smallestValue) { - return false; - } - if (!Objects.equals(this.numberFormatOverride, - that.numberFormatOverride)) { - return false; - } - return super.equals(obj); - } - - /** - * Returns a hash code for this instance. - * - * @return A hash code. - */ - @Override - public int hashCode() { - int result = 193; - long temp = Double.doubleToLongBits(this.base); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(this.smallestValue); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - if (this.numberFormatOverride != null) { - result = 37 * result + this.numberFormatOverride.hashCode(); - } - result = 37 * result + this.tickUnit.hashCode(); - return result; - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------ + * LogAxis.java + * ------------ + * (C) Copyright 2006-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): Andrew Mickish (patch 1868745); + * Peter Kolb (patches 1934255 and 2603321); + */ + +package org.jfree.chart.axis; + +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.font.FontRenderContext; +import java.awt.font.LineMetrics; +import java.awt.font.TextAttribute; +import java.awt.geom.Rectangle2D; +import java.text.AttributedString; +import java.text.DecimalFormat; +import java.text.Format; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.ValueAxisPlot; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.chart.ui.TextAnchor; +import org.jfree.chart.util.AttrStringUtils; +import org.jfree.chart.util.Args; +import org.jfree.data.Range; + +/** + * A numerical axis that uses a logarithmic scale. The class is an + * alternative to the {@link LogarithmicAxis} class. + */ +public class LogAxis extends ValueAxis { + + /** The logarithm base. */ + private double base = 10.0; + + /** The logarithm of the base value - cached for performance. */ + private double baseLog = Math.log(10.0); + + /** + * The base symbol to display (if {@code null} then the numerical + * value of the base is displayed). + */ + private String baseSymbol = null; + + /** + * The formatter to use for the base value when the base is displayed + * as a numerical value. + */ + private Format baseFormatter = new DecimalFormat("0"); + + /** The smallest value permitted on the axis. */ + private double smallestValue = 1E-100; + + /** The current tick unit. */ + private NumberTickUnit tickUnit; + + /** The override number format. */ + private NumberFormat numberFormatOverride; + + /** + * Creates a new {@code LogAxis} with no label. + */ + public LogAxis() { + this(null); + } + + /** + * Creates a new {@code LogAxis} with the given label. + * + * @param label the axis label ({@code null} permitted). + */ + public LogAxis(String label) { + super(label, new NumberTickUnitSource()); + setDefaultAutoRange(new Range(0.01, 1.0)); + this.tickUnit = new NumberTickUnit(1.0, new DecimalFormat("0.#"), 10); + } + + /** + * Returns the base for the logarithm calculation. The default value is + * {@code 10.0}. + * + * @return The base for the logarithm calculation. + * + * @see #setBase(double) + */ + public double getBase() { + return this.base; + } + + /** + * Sets the base for the logarithm calculation and sends a change event to + * all registered listeners. + * + * @param base the base value (must be > 1.0). + * + * @see #getBase() + */ + public void setBase(double base) { + if (base <= 1.0) { + throw new IllegalArgumentException("Requires 'base' > 1.0."); + } + this.base = base; + this.baseLog = Math.log(base); + fireChangeEvent(); + } + + /** + * Returns the symbol used to represent the base of the logarithmic scale + * for the axis. If this is {@code null} (the default) then the + * numerical value of the base is displayed. + * + * @return The base symbol (possibly {@code null}). + */ + public String getBaseSymbol() { + return this.baseSymbol; + } + + /** + * Sets the symbol used to represent the base value of the logarithmic + * scale and sends a change event to all registered listeners. + * + * @param symbol the symbol ({@code null} permitted). + */ + public void setBaseSymbol(String symbol) { + this.baseSymbol = symbol; + fireChangeEvent(); + } + + /** + * Returns the formatter used to format the base value of the logarithmic + * scale when it is displayed numerically. The default value is + * {@code new DecimalFormat("0")}. + * + * @return The base formatter (never {@code null}). + */ + public Format getBaseFormatter() { + return this.baseFormatter; + } + + /** + * Sets the formatter used to format the base value of the logarithmic + * scale when it is displayed numerically and sends a change event to all + * registered listeners. + * + * @param formatter the formatter ({@code null} not permitted). + */ + public void setBaseFormatter(Format formatter) { + Args.nullNotPermitted(formatter, "formatter"); + this.baseFormatter = formatter; + fireChangeEvent(); + } + + /** + * Returns the smallest value represented by the axis. + * + * @return The smallest value represented by the axis. + * + * @see #setSmallestValue(double) + */ + public double getSmallestValue() { + return this.smallestValue; + } + + /** + * Sets the smallest value represented by the axis and sends a change event + * to all registered listeners. + * + * @param value the value. + * + * @see #getSmallestValue() + */ + public void setSmallestValue(double value) { + if (value <= 0.0) { + throw new IllegalArgumentException("Requires 'value' > 0.0."); + } + this.smallestValue = value; + fireChangeEvent(); + } + + /** + * Returns the current tick unit. + * + * @return The current tick unit. + * + * @see #setTickUnit(NumberTickUnit) + */ + public NumberTickUnit getTickUnit() { + return this.tickUnit; + } + + /** + * Sets the tick unit for the axis by calling + * {@link #setTickUnit(org.jfree.chart.axis.NumberTickUnit, boolean, boolean)}. + * + * @param unit the new tick unit ({@code null} not permitted). + * + * @see #getTickUnit() + */ + public void setTickUnit(NumberTickUnit unit) { + // defer argument checking... + setTickUnit(unit, true, true); + } + + /** + * Sets the tick unit for the axis and, if requested, calls + * {@link #fireChangeEvent()}. In addition, an option is provided to turn + * off the "auto-select" feature for tick units (you can restore it using + * the {@link ValueAxis#setAutoTickUnitSelection(boolean)} method). + * + * @param unit the new tick unit ({@code null} not permitted). + * @param notify notify listeners? + * @param turnOffAutoSelect turn off the auto-tick selection? + * + * @see #getTickUnit() + */ + public void setTickUnit(NumberTickUnit unit, boolean notify, + boolean turnOffAutoSelect) { + Args.nullNotPermitted(unit, "unit"); + this.tickUnit = unit; + if (turnOffAutoSelect) { + setAutoTickUnitSelection(false, false); + } + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the number format override. If this is non-{@code null}, + * then it will be used to format the numbers on the axis. + * + * @return The number formatter (possibly {@code null}). + * + * @see #setNumberFormatOverride(NumberFormat) + */ + public NumberFormat getNumberFormatOverride() { + return this.numberFormatOverride; + } + + /** + * Sets the number format override and sends a change event to all + * registered listeners. If this is non-{@code null}, then it will be + * used to format the numbers on the axis. + * + * @param formatter the number formatter ({@code null} permitted). + * + * @see #getNumberFormatOverride() + */ + public void setNumberFormatOverride(NumberFormat formatter) { + this.numberFormatOverride = formatter; + fireChangeEvent(); + } + + /** + * Calculates the log of the given value, using the current base. + * + * @param value the value. + * + * @return The log of the given value. + * + * @see #calculateValue(double) + * @see #getBase() + */ + public double calculateLog(double value) { + return Math.log(value) / this.baseLog; + } + + /** + * Calculates the value from a given log. + * + * @param log the log value. + * + * @return The value with the given log. + * + * @see #calculateLog(double) + * @see #getBase() + */ + public double calculateValue(double log) { + return Math.pow(this.base, log); + } + + private double calculateValueNoINF(double log) { + double result = calculateValue(log); + if (Double.isInfinite(result)) { + result = Double.MAX_VALUE; + } + if (result <= 0.0) { + result = Double.MIN_VALUE; + } + return result; + } + + /** + * Converts a Java2D coordinate to an axis value, assuming that the + * axis is aligned to the specified {@code edge} of the {@code area}. + * + * @param java2DValue the Java2D coordinate. + * @param area the area for plotting data ({@code null} not + * permitted). + * @param edge the edge that the axis is aligned to ({@code null} not + * permitted). + * + * @return A value along the axis scale. + */ + @Override + public double java2DToValue(double java2DValue, Rectangle2D area, + RectangleEdge edge) { + + Range range = getRange(); + double axisMin = calculateLog(Math.max(this.smallestValue, + range.getLowerBound())); + double axisMax = calculateLog(range.getUpperBound()); + + double min = 0.0; + double max = 0.0; + if (RectangleEdge.isTopOrBottom(edge)) { + min = area.getX(); + max = area.getMaxX(); + } else if (RectangleEdge.isLeftOrRight(edge)) { + min = area.getMaxY(); + max = area.getY(); + } + double log; + if (isInverted()) { + log = axisMax - (java2DValue - min) / (max - min) + * (axisMax - axisMin); + } else { + log = axisMin + (java2DValue - min) / (max - min) + * (axisMax - axisMin); + } + return calculateValue(log); + } + + /** + * Converts a value on the axis scale to a Java2D coordinate relative to + * the given {@code area}, based on the axis running along the + * specified {@code edge}. + * + * @param value the data value. + * @param area the area ({@code null} not permitted). + * @param edge the edge ({@code null} not permitted). + * + * @return The Java2D coordinate corresponding to {@code value}. + */ + @Override + public double valueToJava2D(double value, Rectangle2D area, + RectangleEdge edge) { + + Range range = getRange(); + double axisMin = calculateLog(range.getLowerBound()); + double axisMax = calculateLog(range.getUpperBound()); + value = calculateLog(value); + + double min = 0.0; + double max = 0.0; + if (RectangleEdge.isTopOrBottom(edge)) { + min = area.getX(); + max = area.getMaxX(); + } else if (RectangleEdge.isLeftOrRight(edge)) { + max = area.getMinY(); + min = area.getMaxY(); + } + if (isInverted()) { + return max + - ((value - axisMin) / (axisMax - axisMin)) * (max - min); + } else { + return min + + ((value - axisMin) / (axisMax - axisMin)) * (max - min); + } + } + + /** + * Configures the axis. This method is typically called when an axis + * is assigned to a new plot. + */ + @Override + public void configure() { + if (isAutoRange()) { + autoAdjustRange(); + } + } + + /** + * Adjusts the axis range to match the data range that the axis is + * required to display. + */ + @Override + protected void autoAdjustRange() { + Plot plot = getPlot(); + if (plot == null) { + return; // no plot, no data + } + + if (plot instanceof ValueAxisPlot) { + ValueAxisPlot vap = (ValueAxisPlot) plot; + + Range r = vap.getDataRange(this); + if (r == null) { + r = getDefaultAutoRange(); + } + + double upper = r.getUpperBound(); + double lower = Math.max(r.getLowerBound(), this.smallestValue); + double range = upper - lower; + + // if fixed auto range, then derive lower bound... + double fixedAutoRange = getFixedAutoRange(); + if (fixedAutoRange > 0.0) { + lower = Math.max(upper - fixedAutoRange, this.smallestValue); + } + else { + // ensure the autorange is at least in size... + double minRange = getAutoRangeMinimumSize(); + if (range < minRange) { + double expand = (minRange - range) / 2; + upper = upper + expand; + lower = lower - expand; + } + + // apply the margins - these should apply to the exponent range + double logUpper = calculateLog(upper); + double logLower = calculateLog(lower); + double logRange = logUpper - logLower; + logUpper = logUpper + getUpperMargin() * logRange; + logLower = logLower - getLowerMargin() * logRange; + upper = calculateValueNoINF(logUpper); + lower = calculateValueNoINF(logLower); + } + setRange(new Range(lower, upper), false, false); + } + + } + + /** + * Draws the axis on a Java 2D graphics device (such as the screen or a + * printer). + * + * @param g2 the graphics device ({@code null} not permitted). + * @param cursor the cursor location (determines where to draw the axis). + * @param plotArea the area within which the axes and plot should be drawn. + * @param dataArea the area within which the data should be drawn. + * @param edge the axis location ({@code null} not permitted). + * @param plotState collects information about the plot ({@code null} + * permitted). + * + * @return The axis state (never {@code null}). + */ + @Override + public AxisState draw(Graphics2D g2, double cursor, Rectangle2D plotArea, + Rectangle2D dataArea, RectangleEdge edge, + PlotRenderingInfo plotState) { + + AxisState state; + // if the axis is not visible, don't draw it... + if (!isVisible()) { + state = new AxisState(cursor); + // even though the axis is not visible, we need ticks for the + // gridlines... + List ticks = refreshTicks(g2, state, dataArea, edge); + state.setTicks(ticks); + return state; + } + state = drawTickMarksAndLabels(g2, cursor, plotArea, dataArea, edge); + if (getAttributedLabel() != null) { + state = drawAttributedLabel(getAttributedLabel(), g2, plotArea, + dataArea, edge, state); + + } else { + state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state); + } + createAndAddEntity(cursor, state, dataArea, edge, plotState); + return state; + } + + /** + * Calculates the positions of the tick labels for the axis, storing the + * results in the tick label list (ready for drawing). + * + * @param g2 the graphics device. + * @param state the axis state. + * @param dataArea the area in which the plot should be drawn. + * @param edge the location of the axis. + * + * @return A list of ticks. + */ + @Override + public List refreshTicks(Graphics2D g2, AxisState state, + Rectangle2D dataArea, RectangleEdge edge) { + List result = new java.util.ArrayList(); + if (RectangleEdge.isTopOrBottom(edge)) { + result = refreshTicksHorizontal(g2, dataArea, edge); + } + else if (RectangleEdge.isLeftOrRight(edge)) { + result = refreshTicksVertical(g2, dataArea, edge); + } + return result; + } + + /** + * Returns a list of ticks for an axis at the top or bottom of the chart. + * + * @param g2 the graphics device ({@code null} not permitted). + * @param dataArea the data area ({@code null} not permitted). + * @param edge the edge ({@code null} not permitted). + * + * @return A list of ticks. + */ + protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea, + RectangleEdge edge) { + + Range range = getRange(); + List ticks = new ArrayList(); + Font tickLabelFont = getTickLabelFont(); + g2.setFont(tickLabelFont); + TextAnchor textAnchor; + if (edge == RectangleEdge.TOP) { + textAnchor = TextAnchor.BOTTOM_CENTER; + } + else { + textAnchor = TextAnchor.TOP_CENTER; + } + + if (isAutoTickUnitSelection()) { + selectAutoTickUnit(g2, dataArea, edge); + } + int minorTickCount = this.tickUnit.getMinorTickCount(); + double unit = getTickUnit().getSize(); + double index = Math.ceil(calculateLog(getRange().getLowerBound()) + / unit); + double start = index * unit; + double end = calculateLog(getUpperBound()); + double current = start; + boolean hasTicks = (this.tickUnit.getSize() > 0.0) + && !Double.isInfinite(start); + while (hasTicks && current <= end) { + double v = calculateValueNoINF(current); + if (range.contains(v)) { + ticks.add(new LogTick(TickType.MAJOR, v, createTickLabel(v), + textAnchor)); + } + // add minor ticks (for gridlines) + double next = Math.pow(this.base, current + + this.tickUnit.getSize()); + for (int i = 1; i < minorTickCount; i++) { + double minorV = v + i * ((next - v) / minorTickCount); + if (range.contains(minorV)) { + ticks.add(new LogTick(TickType.MINOR, minorV, null, + textAnchor)); + } + } + current = current + this.tickUnit.getSize(); + } + return ticks; + } + + /** + * Returns a list of ticks for an axis at the left or right of the chart. + * + * @param g2 the graphics device ({@code null} not permitted). + * @param dataArea the data area ({@code null} not permitted). + * @param edge the edge that the axis is aligned to ({@code null} + * not permitted). + * + * @return A list of ticks. + */ + protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea, + RectangleEdge edge) { + + Range range = getRange(); + List ticks = new ArrayList(); + Font tickLabelFont = getTickLabelFont(); + g2.setFont(tickLabelFont); + TextAnchor textAnchor; + if (edge == RectangleEdge.RIGHT) { + textAnchor = TextAnchor.CENTER_LEFT; + } + else { + textAnchor = TextAnchor.CENTER_RIGHT; + } + + if (isAutoTickUnitSelection()) { + selectAutoTickUnit(g2, dataArea, edge); + } + int minorTickCount = this.tickUnit.getMinorTickCount(); + double unit = getTickUnit().getSize(); + double index = Math.ceil(calculateLog(getRange().getLowerBound()) + / unit); + double start = index * unit; + double end = calculateLog(getUpperBound()); + double current = start; + boolean hasTicks = (this.tickUnit.getSize() > 0.0) + && !Double.isInfinite(start); + while (hasTicks && current <= end) { + double v = calculateValueNoINF(current); + if (range.contains(v)) { + ticks.add(new LogTick(TickType.MAJOR, v, createTickLabel(v), + textAnchor)); + } + // add minor ticks (for gridlines) + double next = Math.pow(this.base, current + + this.tickUnit.getSize()); + for (int i = 1; i < minorTickCount; i++) { + double minorV = v + i * ((next - v) / minorTickCount); + if (range.contains(minorV)) { + ticks.add(new LogTick(TickType.MINOR, minorV, null, + textAnchor)); + } + } + current = current + this.tickUnit.getSize(); + } + return ticks; + } + + /** + * Selects an appropriate tick value for the axis. The strategy is to + * display as many ticks as possible (selected from an array of 'standard' + * tick units) without the labels overlapping. + * + * @param g2 the graphics device ({@code null} not permitted). + * @param dataArea the area defined by the axes ({@code null} not + * permitted). + * @param edge the axis location ({@code null} not permitted). + */ + protected void selectAutoTickUnit(Graphics2D g2, Rectangle2D dataArea, + RectangleEdge edge) { + if (RectangleEdge.isTopOrBottom(edge)) { + selectHorizontalAutoTickUnit(g2, dataArea, edge); + } + else if (RectangleEdge.isLeftOrRight(edge)) { + selectVerticalAutoTickUnit(g2, dataArea, edge); + } + } + + /** + * Selects an appropriate tick value for the axis. The strategy is to + * display as many ticks as possible (selected from an array of 'standard' + * tick units) without the labels overlapping. + * + * @param g2 the graphics device. + * @param dataArea the area defined by the axes. + * @param edge the axis location. + */ + protected void selectHorizontalAutoTickUnit(Graphics2D g2, + Rectangle2D dataArea, RectangleEdge edge) { + + // select a tick unit that is the next one bigger than the current + // (log) range divided by 50 + Range range = getRange(); + double logAxisMin = calculateLog(Math.max(this.smallestValue, + range.getLowerBound())); + double logAxisMax = calculateLog(range.getUpperBound()); + double size = (logAxisMax - logAxisMin) / 50; + TickUnitSource tickUnits = getStandardTickUnits(); + TickUnit candidate = tickUnits.getCeilingTickUnit(size); + TickUnit prevCandidate = candidate; + boolean found = false; + while (!found) { + // while the tick labels overlap and there are more tick sizes available, + // choose the next bigger label + this.tickUnit = (NumberTickUnit) candidate; + double tickLabelWidth = estimateMaximumTickLabelWidth(g2, + candidate); + // what is the available space for one unit? + double candidateWidth = exponentLengthToJava2D(candidate.getSize(), + dataArea, edge); + if (tickLabelWidth < candidateWidth) { + found = true; + } else if (Double.isNaN(candidateWidth)) { + candidate = prevCandidate; + found = true; + } else { + prevCandidate = candidate; + candidate = tickUnits.getLargerTickUnit(prevCandidate); + if (candidate.equals(prevCandidate)) { + found = true; // there are no more candidates + } + } + } + setTickUnit((NumberTickUnit) candidate, false, false); + } + + /** + * Converts a length in data coordinates into the corresponding length in + * Java2D coordinates. + * + * @param length the length. + * @param area the plot area. + * @param edge the edge along which the axis lies. + * + * @return The length in Java2D coordinates. + */ + public double exponentLengthToJava2D(double length, Rectangle2D area, + RectangleEdge edge) { + double one = valueToJava2D(calculateValueNoINF(1.0), area, edge); + double l = valueToJava2D(calculateValueNoINF(length + 1.0), area, edge); + return Math.abs(l - one); + } + + /** + * Selects an appropriate tick value for the axis. The strategy is to + * display as many ticks as possible (selected from an array of 'standard' + * tick units) without the labels overlapping. + * + * @param g2 the graphics device. + * @param dataArea the area in which the plot should be drawn. + * @param edge the axis location. + */ + protected void selectVerticalAutoTickUnit(Graphics2D g2, + Rectangle2D dataArea, RectangleEdge edge) { + // select a tick unit that is the next one bigger than the current + // (log) range divided by 50 + Range range = getRange(); + double logAxisMin = calculateLog(Math.max(this.smallestValue, + range.getLowerBound())); + double logAxisMax = calculateLog(range.getUpperBound()); + double size = (logAxisMax - logAxisMin) / 50; + TickUnitSource tickUnits = getStandardTickUnits(); + TickUnit candidate = tickUnits.getCeilingTickUnit(size); + TickUnit prevCandidate = candidate; + boolean found = false; + while (!found) { + // while the tick labels overlap and there are more tick sizes available, + // choose the next bigger label + this.tickUnit = (NumberTickUnit) candidate; + double tickLabelHeight = estimateMaximumTickLabelHeight(g2); + // what is the available space for one unit? + double candidateHeight = exponentLengthToJava2D(candidate.getSize(), + dataArea, edge); + if (tickLabelHeight < candidateHeight) { + found = true; + } else if (Double.isNaN(candidateHeight)) { + candidate = prevCandidate; + found = true; + } else { + prevCandidate = candidate; + candidate = tickUnits.getLargerTickUnit(prevCandidate); + if (candidate.equals(prevCandidate)) { + found = true; // there are no more candidates + } + } + } + setTickUnit((NumberTickUnit) candidate, false, false); + } + + /** + * Creates a tick label for the specified value based on the current + * tick unit (used for formatting the exponent). + * + * @param value the value. + * + * @return The label. + */ + protected AttributedString createTickLabel(double value) { + if (this.numberFormatOverride != null) { + String text = this.numberFormatOverride.format(value); + AttributedString as = new AttributedString(text); + as.addAttribute(TextAttribute.FONT, getTickLabelFont()); + return as; + } else { + String baseStr = this.baseSymbol; + if (baseStr == null) { + baseStr = this.baseFormatter.format(this.base); + } + double logy = calculateLog(value); + String exponentStr = getTickUnit().valueToString(logy); + AttributedString as = new AttributedString(baseStr + exponentStr); + as.addAttributes(getTickLabelFont().getAttributes(), 0, (baseStr + + exponentStr).length()); + as.addAttribute(TextAttribute.SUPERSCRIPT, + TextAttribute.SUPERSCRIPT_SUPER, baseStr.length(), + baseStr.length() + exponentStr.length()); + return as; + } + } + + /** + * Estimates the maximum tick label height. + * + * @param g2 the graphics device. + * + * @return The maximum height. + */ + protected double estimateMaximumTickLabelHeight(Graphics2D g2) { + RectangleInsets tickLabelInsets = getTickLabelInsets(); + double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom(); + + Font tickLabelFont = getTickLabelFont(); + FontRenderContext frc = g2.getFontRenderContext(); + result += tickLabelFont.getLineMetrics("123", frc).getHeight(); + return result; + } + + /** + * Estimates the maximum width of the tick labels, assuming the specified + * tick unit is used. + *

+ * Rather than computing the string bounds of every tick on the axis, we + * just look at two values: the lower bound and the upper bound for the + * axis. These two values will usually be representative. + * + * @param g2 the graphics device. + * @param unit the tick unit to use for calculation. + * + * @return The estimated maximum width of the tick labels. + */ + protected double estimateMaximumTickLabelWidth(Graphics2D g2, + TickUnit unit) { + + RectangleInsets tickLabelInsets = getTickLabelInsets(); + double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight(); + + if (isVerticalTickLabels()) { + // all tick labels have the same width (equal to the height of the + // font)... + FontRenderContext frc = g2.getFontRenderContext(); + LineMetrics lm = getTickLabelFont().getLineMetrics("0", frc); + result += lm.getHeight(); + } + else { + // look at lower and upper bounds... + Range range = getRange(); + double lower = range.getLowerBound(); + double upper = range.getUpperBound(); + AttributedString lowerStr = createTickLabel(lower); + AttributedString upperStr = createTickLabel(upper); + double w1 = AttrStringUtils.getTextBounds(lowerStr, g2).getWidth(); + double w2 = AttrStringUtils.getTextBounds(upperStr, g2).getWidth(); + result += Math.max(w1, w2); + } + return result; + } + + /** + * Zooms in on the current range. + * + * @param lowerPercent the new lower bound. + * @param upperPercent the new upper bound. + */ + @Override + public void zoomRange(double lowerPercent, double upperPercent) { + Range range = getRange(); + double start = range.getLowerBound(); + double end = range.getUpperBound(); + double log1 = calculateLog(start); + double log2 = calculateLog(end); + double length = log2 - log1; + Range adjusted; + if (isInverted()) { + double logA = log1 + length * (1 - upperPercent); + double logB = log1 + length * (1 - lowerPercent); + adjusted = new Range(calculateValueNoINF(logA), + calculateValueNoINF(logB)); + } + else { + double logA = log1 + length * lowerPercent; + double logB = log1 + length * upperPercent; + adjusted = new Range(calculateValueNoINF(logA), + calculateValueNoINF(logB)); + } + setRange(adjusted); + } + + /** + * Slides the axis range by the specified percentage. + * + * @param percent the percentage. + */ + @Override + public void pan(double percent) { + Range range = getRange(); + double lower = range.getLowerBound(); + double upper = range.getUpperBound(); + double log1 = calculateLog(lower); + double log2 = calculateLog(upper); + double length = log2 - log1; + double adj = length * percent; + log1 = log1 + adj; + log2 = log2 + adj; + setRange(calculateValueNoINF(log1), calculateValueNoINF(log2)); + } + + /** + * Increases or decreases the axis range by calling + * {@link #resizeRange(double, double)}. + *

+ * To double the length of the axis range, use 200% (2.0). + * To halve the length of the axis range, use 50% (0.5). + * + * @param percent the resize factor. + * + * @see #resizeRange(double, double) + */ + @Override + public void resizeRange(double percent) { + Range range = getRange(); + double logMin = calculateLog(range.getLowerBound()); + double logMax = calculateLog(range.getUpperBound()); + double centralValue = calculateValueNoINF((logMin + logMax) / 2.0); + resizeRange(percent, centralValue); + } + + @Override + public void resizeRange(double percent, double anchorValue) { + resizeRange2(percent, anchorValue); + } + + /** + * Resizes the axis length to the specified percentage of the current + * range and sends a change event to all registered listeners. If + * {@code percent} is greater than 1.0 (100 percent) then the axis + * range is increased (which has the effect of zooming out), while if the + * {@code percent} is less than 1.0 the axis range is decreased + * (which has the effect of zooming in). The resize occurs around an + * anchor value (which may not be in the center of the axis). This is used + * to support mouse wheel zooming around an arbitrary point on the plot. + *

+ * This method is overridden to perform the percentage calculations on the + * log values (which are linear for this axis). + * + * @param percent the percentage (must be greater than zero). + * @param anchorValue the anchor value. + */ + @Override + public void resizeRange2(double percent, double anchorValue) { + if (percent > 0.0) { + double logAnchorValue = calculateLog(anchorValue); + Range range = getRange(); + double logAxisMin = calculateLog(range.getLowerBound()); + double logAxisMax = calculateLog(range.getUpperBound()); + + double left = percent * (logAnchorValue - logAxisMin); + double right = percent * (logAxisMax - logAnchorValue); + + double upperBound = calculateValueNoINF(logAnchorValue + right); + Range adjusted = new Range(calculateValueNoINF( + logAnchorValue - left), upperBound); + setRange(adjusted); + } + else { + setAutoRange(true); + } + } + + /** + * Tests this axis for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof LogAxis)) { + return false; + } + LogAxis that = (LogAxis) obj; + if (this.base != that.base) { + return false; + } + if (!Objects.equals(this.baseSymbol, that.baseSymbol)) { + return false; + } + if (!this.baseFormatter.equals(that.baseFormatter)) { + return false; + } + if (this.smallestValue != that.smallestValue) { + return false; + } + if (!Objects.equals(this.numberFormatOverride, + that.numberFormatOverride)) { + return false; + } + return super.equals(obj); + } + + /** + * Returns a hash code for this instance. + * + * @return A hash code. + */ + @Override + public int hashCode() { + int result = 193; + long temp = Double.doubleToLongBits(this.base); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(this.smallestValue); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + if (this.numberFormatOverride != null) { + result = 37 * result + this.numberFormatOverride.hashCode(); + } + result = 37 * result + this.tickUnit.hashCode(); + return result; + } + +} diff --git a/src/main/java/org/jfree/chart/axis/PeriodAxis.java b/src/main/java/org/jfree/chart/axis/PeriodAxis.java index 590639587..0ba44c3d6 100644 --- a/src/main/java/org/jfree/chart/axis/PeriodAxis.java +++ b/src/main/java/org/jfree/chart/axis/PeriodAxis.java @@ -60,7 +60,6 @@ import java.util.Locale; import java.util.TimeZone; -import org.jfree.chart.event.AxisChangeEvent; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.PlotRenderingInfo; import org.jfree.chart.plot.ValueAxisPlot; @@ -219,8 +218,8 @@ public RegularTimePeriod getFirst() { } /** - * Sets the first time period in the axis range and sends an - * {@link AxisChangeEvent} to all registered listeners. + * Sets the first time period in the axis range and calls + * {@link #fireChangeEvent()}. * * @param first the time period ({@code null} not permitted). */ @@ -241,8 +240,8 @@ public RegularTimePeriod getLast() { } /** - * Sets the last time period in the axis range and sends an - * {@link AxisChangeEvent} to all registered listeners. + * Sets the last time period in the axis range and calls + * {@link #fireChangeEvent()}. * * @param last the time period ({@code null} not permitted). */ @@ -300,7 +299,7 @@ public Class getAutoRangeTimePeriodClass() { /** * Sets the class used to create the first and last time periods for the * axis range when the auto-range flag is set to {@code true} and - * sends an {@link AxisChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param c the class ({@code null} not permitted). */ @@ -321,7 +320,7 @@ public Class getMajorTickTimePeriodClass() { /** * Sets the class that controls the spacing of the major tick marks, and - * sends an {@link AxisChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param c the class (a subclass of {@link RegularTimePeriod} is * expected). @@ -345,8 +344,7 @@ public boolean isMinorTickMarksVisible() { /** * Sets the flag that controls whether or not minor tick marks - * are displayed for the axis, and sends a {@link AxisChangeEvent} - * to all registered listeners. + * are displayed for the axis, and calls {@link #fireChangeEvent()}. * * @param visible the flag. */ @@ -367,7 +365,7 @@ public Class getMinorTickTimePeriodClass() { /** * Sets the class that controls the spacing of the minor tick marks, and - * sends an {@link AxisChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param c the class (a subclass of {@link RegularTimePeriod} is * expected). @@ -390,8 +388,7 @@ public Stroke getMinorTickMarkStroke() { /** * Sets the stroke used to display minor tick marks, if they are - * visible, and sends a {@link AxisChangeEvent} to all registered - * listeners. + * visible, and calls {@link #fireChangeEvent()}. * * @param stroke the stroke ({@code null} not permitted). */ @@ -413,8 +410,7 @@ public Paint getMinorTickMarkPaint() { /** * Sets the paint used to display minor tick marks, if they are - * visible, and sends a {@link AxisChangeEvent} to all registered - * listeners. + * visible, and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). */ @@ -435,8 +431,8 @@ public float getMinorTickMarkInsideLength() { } /** - * Sets the inside length of the minor tick marks and sends an - * {@link AxisChangeEvent} to all registered listeners. + * Sets the inside length of the minor tick marks and calls + * {@link #fireChangeEvent()}. * * @param length the length. */ @@ -457,8 +453,8 @@ public float getMinorTickMarkOutsideLength() { } /** - * Sets the outside length of the minor tick marks and sends an - * {@link AxisChangeEvent} to all registered listeners. + * Sets the outside length of the minor tick marks and calls + * {@link #fireChangeEvent()}. * * @param length the length. */ @@ -478,8 +474,7 @@ public PeriodAxisLabelInfo[] getLabelInfo() { } /** - * Sets the array of label info records and sends an - * {@link AxisChangeEvent} to all registered listeners. + * Sets the array of label info records and calls {@link #fireChangeEvent()}. * * @param info the info. */ @@ -489,9 +484,8 @@ public void setLabelInfo(PeriodAxisLabelInfo[] info) { } /** - * Sets the range for the axis, if requested, sends an - * {@link AxisChangeEvent} to all registered listeners. As a side-effect, - * the auto-range flag is set to {@code false} (optional). + * Sets {@link #first} and {@link #last}, then calls + * {@link ValueAxis#setRange(org.jfree.data.Range, boolean, boolean)}. * * @param range the range ({@code null} not permitted). * @param turnOffAutoRange a flag that controls whether or not the auto diff --git a/src/main/java/org/jfree/chart/axis/ValueAxis.java b/src/main/java/org/jfree/chart/axis/ValueAxis.java index 37f3d219c..0506c1037 100644 --- a/src/main/java/org/jfree/chart/axis/ValueAxis.java +++ b/src/main/java/org/jfree/chart/axis/ValueAxis.java @@ -58,7 +58,6 @@ import java.util.List; import java.util.Objects; -import org.jfree.chart.event.AxisChangeEvent; import org.jfree.chart.plot.Plot; import org.jfree.chart.text.TextUtils; import org.jfree.chart.ui.RectangleEdge; @@ -269,8 +268,7 @@ public boolean isVerticalTickLabels() { /** * Sets the flag that controls whether the tick labels are displayed * vertically (that is, rotated 90 degrees from horizontal). If the flag - * is changed, an {@link AxisChangeEvent} is sent to all registered - * listeners. + * is changed, it calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -297,8 +295,8 @@ public boolean isPositiveArrowVisible() { /** * Sets a flag that controls whether or not the axis lines has an arrow - * drawn that points in the positive direction for the axis, and sends an - * {@link AxisChangeEvent} to all registered listeners. + * drawn that points in the positive direction for the axis, and calls + * {@link #fireChangeEvent()}. * * @param visible the flag. * @@ -323,8 +321,8 @@ public boolean isNegativeArrowVisible() { /** * Sets a flag that controls whether or not the axis lines has an arrow - * drawn that points in the negative direction for the axis, and sends an - * {@link AxisChangeEvent} to all registered listeners. + * drawn that points in the negative direction for the axis, and calls + * {@link #fireChangeEvent()}. * * @param visible the flag. * @@ -349,8 +347,7 @@ public Shape getUpArrow() { /** * Sets the shape that can be displayed as an arrow pointing upwards at - * the end of an axis line and sends an {@link AxisChangeEvent} to all - * registered listeners. + * the end of an axis line and calls {@link #fireChangeEvent()}. * * @param arrow the arrow shape ({@code null} not permitted). * @@ -376,8 +373,7 @@ public Shape getDownArrow() { /** * Sets the shape that can be displayed as an arrow pointing downwards at - * the end of an axis line and sends an {@link AxisChangeEvent} to all - * registered listeners. + * the end of an axis line and calls {@link #fireChangeEvent()}. * * @param arrow the arrow shape ({@code null} not permitted). * @@ -403,8 +399,7 @@ public Shape getLeftArrow() { /** * Sets the shape that can be displayed as an arrow pointing left at the - * end of an axis line and sends an {@link AxisChangeEvent} to all - * registered listeners. + * end of an axis line and calls {@link #fireChangeEvent()}. * * @param arrow the arrow shape ({@code null} not permitted). * @@ -430,8 +425,7 @@ public Shape getRightArrow() { /** * Sets the shape that can be displayed as an arrow pointing rightwards at - * the end of an axis line and sends an {@link AxisChangeEvent} to all - * registered listeners. + * the end of an axis line and calls {@link #fireChangeEvent()}. * * @param arrow the arrow shape ({@code null} not permitted). * @@ -910,7 +904,7 @@ public void setAutoRange(boolean auto) { /** * Sets the auto range attribute. If the {@code notify} flag is set, - * an {@link AxisChangeEvent} is sent to registered listeners. + * {@link #fireChangeEvent()} is called. * * @param auto the flag. * @param notify notify listeners? @@ -940,8 +934,8 @@ public double getAutoRangeMinimumSize() { } /** - * Sets the auto range minimum size and sends an {@link AxisChangeEvent} - * to all registered listeners. + * Sets the auto range minimum size by calling + * {@link #setAutoRangeMinimumSize(double, boolean)}. * * @param size the size. * @@ -955,8 +949,7 @@ public void setAutoRangeMinimumSize(double size) { * Sets the minimum size allowed for the axis range when it is * automatically calculated. *

- * If requested, an {@link AxisChangeEvent} is forwarded to all registered - * listeners. + * If requested, {@link #fireChangeEvent()} is called. * * @param size the new minimum. * @param notify notify listeners? @@ -990,8 +983,7 @@ public Range getDefaultAutoRange() { } /** - * Sets the default auto range and sends an {@link AxisChangeEvent} to all - * registered listeners. + * Sets the default auto range and calls {@link #fireChangeEvent()}. * * @param range the range ({@code null} not permitted). * @@ -1019,9 +1011,9 @@ public double getLowerMargin() { /** * Sets the lower margin for the axis (as a percentage of the axis range) - * and sends an {@link AxisChangeEvent} to all registered listeners. This - * margin is added only when the axis range is auto-calculated - if you set - * the axis range manually, the margin is ignored. + * and calls {@link #fireChangeEvent()}. This margin is added only when the + * axis range is auto-calculated - if you set the axis range manually, the + * margin is ignored. * * @param margin the margin percentage (for example, 0.05 is five percent). * @@ -1052,9 +1044,9 @@ public double getUpperMargin() { /** * Sets the upper margin for the axis (as a percentage of the axis range) - * and sends an {@link AxisChangeEvent} to all registered listeners. This - * margin is added only when the axis range is auto-calculated - if you set - * the axis range manually, the margin is ignored. + * and calls {@link #fireChangeEvent()}. This margin is added only when the + * axis range is auto-calculated - if you set the axis range manually, the + * margin is ignored. * * @param margin the margin percentage (for example, 0.05 is five percent). * @@ -1107,8 +1099,14 @@ public double getLowerBound() { } /** - * Sets the lower bound for the axis range. An {@link AxisChangeEvent} is - * sent to all registered listeners. + * Sets the lower bound for the axis range by calling + * {@link #setRange(double, double)} with the given minimum value for the + * 1st argument. + * + * If the existing upper bound is greater than the given minimum value, that + * upper bound is used for the 2nd argument for setRange. If not, setRange + * is called with an upper bound of min + 1.0 for the 2nd + * argument. * * @param min the new minimum. * @@ -1135,8 +1133,13 @@ public double getUpperBound() { } /** - * Sets the upper bound for the axis range, and sends an - * {@link AxisChangeEvent} to all registered listeners. + * Sets the upper bound for the axis range by calling + * {@link #setRange(double, double)} with the given maximum value for the + * 2nd argument. + * + * If the existing lower bound is less than given maximum value, that lower + * bound is used for the 1st argument to setRange. If not, setRange is + * called with a lower bound of max - 1.0 for the 1st argument. * * @param max the new maximum. * @@ -1223,9 +1226,8 @@ public void setRange(double lower, double upper) { } /** - * Sets the range for the axis (after first adding the current margins to - * the specified range) and sends an {@link AxisChangeEvent} to all - * registered listeners. + * Sets the range for the axis by calling + * {@link #setRangeWithMargins(org.jfree.data.Range, boolean, boolean)}. * * @param range the range ({@code null} not permitted). */ @@ -1234,10 +1236,9 @@ public void setRangeWithMargins(Range range) { } /** - * Sets the range for the axis after first adding the current margins to - * the range and, if requested, sends an {@link AxisChangeEvent} to all - * registered listeners. As a side-effect, the auto-range flag is set to - * {@code false} (optional). + * Sets the range for the axis after first adding the current margins to the + * range and, if requested, calls {@link #fireChangeEvent()}. As a + * side-effect, the auto-range flag is set to {@code false} (optional). * * @param range the range (excluding margins, {@code null} not * permitted). @@ -1255,7 +1256,7 @@ public void setRangeWithMargins(Range range, boolean turnOffAutoRange, /** * Sets the axis range (after first adding the current margins to the - * range) and sends an {@link AxisChangeEvent} to all registered listeners. + * range) and calls {@link #fireChangeEvent()}. * As a side-effect, the auto-range flag is set to {@code false}. * * @param lower the lower axis limit. @@ -1333,11 +1334,10 @@ public TickUnitSource getStandardTickUnits() { } /** - * Sets the source for obtaining standard tick units for the axis and sends - * an {@link AxisChangeEvent} to all registered listeners. The axis will - * try to select the smallest tick unit from the source that does not cause - * the tick labels to overlap (see also the - * {@link #setAutoTickUnitSelection(boolean)} method. + * Sets the source for obtaining standard tick units for the axis and calls + * {@link #fireChangeEvent()}. The axis will try to select the smallest tick + * unit from the source that does not cause the tick labels to overlap (see + * also the {@link #setAutoTickUnitSelection(boolean)} method. * * @param source the source for standard tick units ({@code null} * permitted). @@ -1361,8 +1361,8 @@ public int getMinorTickCount() { } /** - * Sets the number of minor tick marks to display, and sends an - * {@link AxisChangeEvent} to all registered listeners. + * Sets the number of minor tick marks to display, and calls + * {@link #fireChangeEvent()}. * * @param count the count. * @@ -1431,9 +1431,9 @@ public abstract double java2DToValue(double java2DValue, Rectangle2D area, protected abstract void autoAdjustRange(); /** - * Centers the axis range about the specified value and sends an - * {@link AxisChangeEvent} to all registered listeners. - * + * Centers the axis range about the specified value and sends a change event + * to all registered listeners. + * * @param value the center value. */ public void centerRange(double value) { @@ -1445,8 +1445,7 @@ public void centerRange(double value) { /** * Increases or decreases the axis range by the specified percentage about - * the central value and sends an {@link AxisChangeEvent} to all registered - * listeners. + * the central value by calling {@link #resizeRange(double, double)}. *

* To double the length of the axis range, use 200% (2.0). * To halve the length of the axis range, use 50% (0.5). @@ -1461,8 +1460,10 @@ public void resizeRange(double percent) { /** * Increases or decreases the axis range by the specified percentage about - * the specified anchor value and sends an {@link AxisChangeEvent} to all - * registered listeners. + * the specified anchor value by calling + * {@link #setRange(org.jfree.data.Range)} with an adjusted range (if + * percent is greater than zero) or {@link #setAutoRange(boolean)} with true + * (if percent is zero). *

* To double the length of the axis range, use 200% (2.0). * To halve the length of the axis range, use 50% (0.5). @@ -1486,8 +1487,10 @@ public void resizeRange(double percent, double anchorValue) { /** * Increases or decreases the axis range by the specified percentage about - * the specified anchor value and sends an {@link AxisChangeEvent} to all - * registered listeners. + * the specified anchor value by calling + * {@link #setRange(org.jfree.data.Range)} with an adjusted range (if + * percent is greater than zero) or {@link #setAutoRange(boolean)} with true + * (if percent is zero). *

* To double the length of the axis range, use 200% (2.0). * To halve the length of the axis range, use 50% (0.5). diff --git a/src/main/java/org/jfree/chart/block/BlockFrame.java b/src/main/java/org/jfree/chart/block/BlockFrame.java index cf47a6f4d..ba597bfa7 100644 --- a/src/main/java/org/jfree/chart/block/BlockFrame.java +++ b/src/main/java/org/jfree/chart/block/BlockFrame.java @@ -39,12 +39,11 @@ import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; import org.jfree.chart.ui.RectangleInsets; -import org.jfree.chart.util.PublicCloneable; /** * A block frame is a type of border that can be drawn around the outside of * any {@link AbstractBlock}. Classes that implement this interface should - * implement {@link PublicCloneable} OR be immutable. + * implement {@link org.jfree.chart.util.PublicCloneable} OR be immutable. */ public interface BlockFrame { diff --git a/src/main/java/org/jfree/chart/editor/ChartEditor.java b/src/main/java/org/jfree/chart/editor/ChartEditor.java index 480f55658..03443531e 100644 --- a/src/main/java/org/jfree/chart/editor/ChartEditor.java +++ b/src/main/java/org/jfree/chart/editor/ChartEditor.java @@ -36,12 +36,10 @@ package org.jfree.chart.editor; -import javax.swing.JComponent; - import org.jfree.chart.JFreeChart; /** - * A chart editor is typically a {@link JComponent} containing a user interface + * A chart editor is typically a {@link javax.swing.JComponent} containing a user interface * for modifying the properties of a chart. * * @see ChartEditorManager#getChartEditor(JFreeChart) diff --git a/src/main/java/org/jfree/chart/entity/CategoryLabelEntity.java b/src/main/java/org/jfree/chart/entity/CategoryLabelEntity.java index b12517e7f..e0e28a076 100644 --- a/src/main/java/org/jfree/chart/entity/CategoryLabelEntity.java +++ b/src/main/java/org/jfree/chart/entity/CategoryLabelEntity.java @@ -1,145 +1,144 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------------ - * CategoryLabelEntity.java - * ------------------------ - * (C) Copyright 2006-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); - * - */ - -package org.jfree.chart.entity; - -import java.awt.Shape; -import java.util.Objects; - -import org.jfree.chart.HashUtils; -import org.jfree.chart.axis.CategoryAxis; - -/** - * An entity to represent the labels on a {@link CategoryAxis}. - */ -public class CategoryLabelEntity extends TickLabelEntity { - - /** The category key. */ - private final Comparable key; - - /** - * Creates a new entity. - * - * @param key the category key ({@code null} not permitted). - * @param area the hotspot. - * @param toolTipText the tool tip text. - * @param urlText the URL text. - */ - public CategoryLabelEntity(Comparable key, Shape area, - String toolTipText, String urlText) { - super(area, toolTipText, urlText); - Objects.requireNonNull(key); - this.key = key; - } - - /** - * Returns the category key. - * - * @return The category key. - */ - public Comparable getKey() { - return this.key; - } - - /** - * Tests this instance for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof CategoryLabelEntity)) { - return false; - } - CategoryLabelEntity that = (CategoryLabelEntity) obj; - - // fix the "equals not symmetric" problem - if (!that.canEqual(this)) { - return false; - } - if (!Objects.equals(this.key, that.key)) { - return false; - } - return super.equals(obj); - } - - /** - * Ensures symmetry between super/subclass implementations of equals. For - * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. - * - * @param other Object - * - * @return true ONLY if the parameter is THIS class type - */ - @Override - public boolean canEqual(Object other) { - // Solves Problem: equals not symmetric - return (other instanceof CategoryLabelEntity); - } - - /** - * Returns a hash code for this instance. - * - * @return A hash code. - */ - @Override - public int hashCode() { - int result = super.hashCode(); // equals calls superclass, hashCode must also - result = HashUtils.hashCode(result, this.key); - return result; - } - - /** - * Returns a string representation of this entity. This is primarily - * useful for debugging. - * - * @return A string representation of this entity. - */ - @Override - public String toString() { - StringBuilder sb = new StringBuilder("CategoryLabelEntity: "); - sb.append("category="); - sb.append(this.key); - sb.append(", tooltip=").append(getToolTipText()); - sb.append(", url=").append(getURLText()); - return sb.toString(); - } -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------------ + * CategoryLabelEntity.java + * ------------------------ + * (C) Copyright 2006-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); + * + */ + +package org.jfree.chart.entity; + +import java.awt.Shape; +import java.util.Objects; + +import org.jfree.chart.HashUtils; + +/** + * An entity to represent the labels on a {@link org.jfree.chart.axis.CategoryAxis}. + */ +public class CategoryLabelEntity extends TickLabelEntity { + + /** The category key. */ + private final Comparable key; + + /** + * Creates a new entity. + * + * @param key the category key ({@code null} not permitted). + * @param area the hotspot. + * @param toolTipText the tool tip text. + * @param urlText the URL text. + */ + public CategoryLabelEntity(Comparable key, Shape area, + String toolTipText, String urlText) { + super(area, toolTipText, urlText); + Objects.requireNonNull(key); + this.key = key; + } + + /** + * Returns the category key. + * + * @return The category key. + */ + public Comparable getKey() { + return this.key; + } + + /** + * Tests this instance for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof CategoryLabelEntity)) { + return false; + } + CategoryLabelEntity that = (CategoryLabelEntity) obj; + + // fix the "equals not symmetric" problem + if (!that.canEqual(this)) { + return false; + } + if (!Objects.equals(this.key, that.key)) { + return false; + } + return super.equals(obj); + } + + /** + * Ensures symmetry between super/subclass implementations of equals. For + * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. + * + * @param other Object + * + * @return true ONLY if the parameter is THIS class type + */ + @Override + public boolean canEqual(Object other) { + // Solves Problem: equals not symmetric + return (other instanceof CategoryLabelEntity); + } + + /** + * Returns a hash code for this instance. + * + * @return A hash code. + */ + @Override + public int hashCode() { + int result = super.hashCode(); // equals calls superclass, hashCode must also + result = HashUtils.hashCode(result, this.key); + return result; + } + + /** + * Returns a string representation of this entity. This is primarily + * useful for debugging. + * + * @return A string representation of this entity. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder("CategoryLabelEntity: "); + sb.append("category="); + sb.append(this.key); + sb.append(", tooltip=").append(getToolTipText()); + sb.append(", url=").append(getURLText()); + return sb.toString(); + } +} diff --git a/src/main/java/org/jfree/chart/entity/FlowEntity.java b/src/main/java/org/jfree/chart/entity/FlowEntity.java index 7e1bbc975..8ebe16fc8 100644 --- a/src/main/java/org/jfree/chart/entity/FlowEntity.java +++ b/src/main/java/org/jfree/chart/entity/FlowEntity.java @@ -1,133 +1,133 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------------- - * FlowEntity.java - * --------------- - * (C) Copyright 2021-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); - * - */ - -package org.jfree.chart.entity; - -import java.awt.Shape; -import java.util.Objects; -import org.jfree.chart.plot.flow.FlowPlot; -import org.jfree.chart.util.Args; -import org.jfree.data.flow.FlowKey; - -/** - * A chart entity representing the flow between two nodes in a {@link FlowPlot}. - * - * @since 1.5.3 - */ -public class FlowEntity extends ChartEntity { - - private FlowKey key; - - /** - * Creates a new instance. - * - * @param key the key identifying the flow ({@code null} not permitted). - * @param area the outline of the entity ({@code null} not permitted). - * @param toolTipText the tool tip text. - * @param urlText the URL text. - */ - public FlowEntity(FlowKey key, Shape area, String toolTipText, String urlText) { - super(area, toolTipText, urlText); - Args.nullNotPermitted(key, "key"); - this.key = key; - } - - /** - * Returns the key identifying the flow. - * - * @return The flow key (never {@code null}). - */ - public FlowKey getKey() { - return this.key; - } - - /** - * Returns a string representation of this instance, primarily for - * debugging purposes. - * - * @return A string. - */ - @Override - public String toString() { - return "[FlowEntity: " + this.key + "]"; - } - - /** - * Tests this instance for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (!(obj instanceof FlowEntity)) { - return false; - } - FlowEntity that = (FlowEntity) obj; - if (!java.util.Objects.equals(this.key, that.key)) { - return false; - } - // fix the "equals not symmetric" problem - if (!that.canEqual(this)) { - return false; - } - - return super.equals(obj); - } - - /** - * Ensures symmetry between super/subclass implementations of equals. For - * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. - * - * @param other Object - * - * @return true ONLY if the parameter is THIS class type - */ - @Override - public boolean canEqual(Object other) { - // fix the "equals not symmetric" problem - return (other instanceof FlowEntity); - } - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 79 * hash + Objects.hashCode(this.key); - return hash; - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------------- + * FlowEntity.java + * --------------- + * (C) Copyright 2021-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); + * + */ + +package org.jfree.chart.entity; + +import java.awt.Shape; +import java.util.Objects; +import org.jfree.chart.util.Args; +import org.jfree.data.flow.FlowKey; + +/** + * A chart entity representing the flow between two nodes in a + * {@link org.jfree.chart.plot.flow.FlowPlot}. + * + * @since 1.5.3 + */ +public class FlowEntity extends ChartEntity { + + private FlowKey key; + + /** + * Creates a new instance. + * + * @param key the key identifying the flow ({@code null} not permitted). + * @param area the outline of the entity ({@code null} not permitted). + * @param toolTipText the tool tip text. + * @param urlText the URL text. + */ + public FlowEntity(FlowKey key, Shape area, String toolTipText, String urlText) { + super(area, toolTipText, urlText); + Args.nullNotPermitted(key, "key"); + this.key = key; + } + + /** + * Returns the key identifying the flow. + * + * @return The flow key (never {@code null}). + */ + public FlowKey getKey() { + return this.key; + } + + /** + * Returns a string representation of this instance, primarily for + * debugging purposes. + * + * @return A string. + */ + @Override + public String toString() { + return "[FlowEntity: " + this.key + "]"; + } + + /** + * Tests this instance for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof FlowEntity)) { + return false; + } + FlowEntity that = (FlowEntity) obj; + if (!java.util.Objects.equals(this.key, that.key)) { + return false; + } + // fix the "equals not symmetric" problem + if (!that.canEqual(this)) { + return false; + } + + return super.equals(obj); + } + + /** + * Ensures symmetry between super/subclass implementations of equals. For + * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. + * + * @param other Object + * + * @return true ONLY if the parameter is THIS class type + */ + @Override + public boolean canEqual(Object other) { + // fix the "equals not symmetric" problem + return (other instanceof FlowEntity); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 79 * hash + Objects.hashCode(this.key); + return hash; + } + +} diff --git a/src/main/java/org/jfree/chart/entity/NodeEntity.java b/src/main/java/org/jfree/chart/entity/NodeEntity.java index e82434cbd..154df39af 100644 --- a/src/main/java/org/jfree/chart/entity/NodeEntity.java +++ b/src/main/java/org/jfree/chart/entity/NodeEntity.java @@ -1,97 +1,96 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------------- - * NodeEntity.java - * --------------- - * (C) Copyright 2021-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.chart.entity; - -import java.awt.Shape; -import org.jfree.chart.plot.flow.FlowPlot; -import org.jfree.chart.util.Args; -import org.jfree.data.flow.NodeKey; - -/** - * A chart entity representing a node in a {@link FlowPlot}. - * - * @since 1.5.3 - */ -public class NodeEntity extends ChartEntity { - - private NodeKey key; - - /** - * Creates a new instance. - * - * @param key the node key ({@code null} not permitted). - * @param area the outline of the entity ({@code null} not permitted). - * @param toolTipText the tool tip text. - */ - public NodeEntity(NodeKey key, Shape area, String toolTipText) { - super(area, toolTipText); - Args.nullNotPermitted(key, "key"); - this.key = key; - } - - /** - * Creates a new instance. - * - * @param area the outline of the entity ({@code null} not permitted). - * @param toolTipText the tool tip text. - * @param urlText the URL text. - */ - public NodeEntity(Shape area, String toolTipText, String urlText) { - super(area, toolTipText, urlText); - } - - /** - * Returns the node key. - * - * @return The node key (never {@code null}). - */ - public NodeKey getKey() { - return this.key; - } - - /** - * Returns a string representation of this instance, primarily for - * debugging purposes. - * - * @return A string. - */ - @Override - public String toString() { - return "[NodeEntity: " + this.key + "]"; - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------------- + * NodeEntity.java + * --------------- + * (C) Copyright 2021-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.chart.entity; + +import java.awt.Shape; +import org.jfree.chart.util.Args; +import org.jfree.data.flow.NodeKey; + +/** + * A chart entity representing a node in a {@link org.jfree.chart.plot.flow.FlowPlot}. + * + * @since 1.5.3 + */ +public class NodeEntity extends ChartEntity { + + private NodeKey key; + + /** + * Creates a new instance. + * + * @param key the node key ({@code null} not permitted). + * @param area the outline of the entity ({@code null} not permitted). + * @param toolTipText the tool tip text. + */ + public NodeEntity(NodeKey key, Shape area, String toolTipText) { + super(area, toolTipText); + Args.nullNotPermitted(key, "key"); + this.key = key; + } + + /** + * Creates a new instance. + * + * @param area the outline of the entity ({@code null} not permitted). + * @param toolTipText the tool tip text. + * @param urlText the URL text. + */ + public NodeEntity(Shape area, String toolTipText, String urlText) { + super(area, toolTipText, urlText); + } + + /** + * Returns the node key. + * + * @return The node key (never {@code null}). + */ + public NodeKey getKey() { + return this.key; + } + + /** + * Returns a string representation of this instance, primarily for + * debugging purposes. + * + * @return A string. + */ + @Override + public String toString() { + return "[NodeEntity: " + this.key + "]"; + } + +} diff --git a/src/main/java/org/jfree/chart/event/AnnotationChangeListener.java b/src/main/java/org/jfree/chart/event/AnnotationChangeListener.java index 1d1c7e7d6..d24d082d3 100644 --- a/src/main/java/org/jfree/chart/event/AnnotationChangeListener.java +++ b/src/main/java/org/jfree/chart/event/AnnotationChangeListener.java @@ -1,56 +1,54 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------------- - * AnnotationChangeListener.java - * ------------------------- - * (C) Copyright 2009-present, by David Gilbert and Contributors. - * - * Original Author: Peter Kolb (patch 2809117); - * Contributor(s): ; - * - */ - -package org.jfree.chart.event; - -import java.util.EventListener; - -import org.jfree.chart.annotations.Annotation; - -/** - * The interface that must be supported by classes that wish to receive - * notification of changes to an {@link Annotation}. - */ -public interface AnnotationChangeListener extends EventListener { - - /** - * Receives notification of an annotation change event. - * - * @param event the event. - */ - void annotationChanged(AnnotationChangeEvent event); - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------------- + * AnnotationChangeListener.java + * ------------------------- + * (C) Copyright 2009-present, by David Gilbert and Contributors. + * + * Original Author: Peter Kolb (patch 2809117); + * Contributor(s): ; + * + */ + +package org.jfree.chart.event; + +import java.util.EventListener; + +/** + * The interface that must be supported by classes that wish to receive + * notification of changes to an {@link org.jfree.chart.annotations.Annotation}. + */ +public interface AnnotationChangeListener extends EventListener { + + /** + * Receives notification of an annotation change event. + * + * @param event the event. + */ + void annotationChanged(AnnotationChangeEvent event); + +} diff --git a/src/main/java/org/jfree/chart/event/MarkerChangeListener.java b/src/main/java/org/jfree/chart/event/MarkerChangeListener.java index 64463666c..41034d3e0 100644 --- a/src/main/java/org/jfree/chart/event/MarkerChangeListener.java +++ b/src/main/java/org/jfree/chart/event/MarkerChangeListener.java @@ -38,11 +38,9 @@ import java.util.EventListener; -import org.jfree.chart.plot.Marker; - /** * The interface that must be supported by classes that wish to receive - * notification of changes to a {@link Marker}. + * notification of changes to a {@link org.jfree.chart.plot.Marker}. */ public interface MarkerChangeListener extends EventListener { diff --git a/src/main/java/org/jfree/chart/event/OverlayChangeEvent.java b/src/main/java/org/jfree/chart/event/OverlayChangeEvent.java index 8b7544b10..7e4d3615f 100644 --- a/src/main/java/org/jfree/chart/event/OverlayChangeEvent.java +++ b/src/main/java/org/jfree/chart/event/OverlayChangeEvent.java @@ -1,56 +1,55 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ----------------------- - * OverlayChangeEvent.java - * ----------------------- - * (C) Copyright 2009-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.chart.event; - -import java.util.EventObject; -import org.jfree.chart.panel.Overlay; - -/** - * A change event for an {@link Overlay}. - */ -public class OverlayChangeEvent extends EventObject { - - /** - * Creates a new change event. - * - * @param source the event source ({@code null} not permitted). - */ - public OverlayChangeEvent(Object source) { - super(source); // null check is in here - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ----------------------- + * OverlayChangeEvent.java + * ----------------------- + * (C) Copyright 2009-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.chart.event; + +import java.util.EventObject; + +/** + * A change event for an {@link org.jfree.chart.panel.Overlay}. + */ +public class OverlayChangeEvent extends EventObject { + + /** + * Creates a new change event. + * + * @param source the event source ({@code null} not permitted). + */ + public OverlayChangeEvent(Object source) { + super(source); // null check is in here + } + +} diff --git a/src/main/java/org/jfree/chart/event/OverlayChangeListener.java b/src/main/java/org/jfree/chart/event/OverlayChangeListener.java index 3aa08015b..99903b24e 100644 --- a/src/main/java/org/jfree/chart/event/OverlayChangeListener.java +++ b/src/main/java/org/jfree/chart/event/OverlayChangeListener.java @@ -1,54 +1,53 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------------------- - * OverlayChangeListener.java - * -------------------------- - * (C) Copyright 2009-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.chart.event; - -import java.util.EventListener; -import org.jfree.chart.panel.Overlay; - -/** - * A listener for changes to an {@link Overlay}. - */ -public interface OverlayChangeListener extends EventListener { - - /** - * This method is called to notify a listener of a change event. - * - * @param event the event. - */ - void overlayChanged(OverlayChangeEvent event); - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * -------------------------- + * OverlayChangeListener.java + * -------------------------- + * (C) Copyright 2009-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.chart.event; + +import java.util.EventListener; + +/** + * A listener for changes to an {@link org.jfree.chart.panel.Overlay}. + */ +public interface OverlayChangeListener extends EventListener { + + /** + * This method is called to notify a listener of a change event. + * + * @param event the event. + */ + void overlayChanged(OverlayChangeEvent event); + +} diff --git a/src/main/java/org/jfree/chart/imagemap/URLTagFragmentGenerator.java b/src/main/java/org/jfree/chart/imagemap/URLTagFragmentGenerator.java index 7bc3088c9..29c6b1532 100644 --- a/src/main/java/org/jfree/chart/imagemap/URLTagFragmentGenerator.java +++ b/src/main/java/org/jfree/chart/imagemap/URLTagFragmentGenerator.java @@ -35,11 +35,6 @@ package org.jfree.chart.imagemap; -import org.jfree.chart.urls.CategoryURLGenerator; -import org.jfree.chart.urls.PieURLGenerator; -import org.jfree.chart.urls.XYURLGenerator; -import org.jfree.chart.urls.XYZURLGenerator; - /** * Interface for generating the URL fragment of an HTML image map area tag. */ @@ -49,8 +44,10 @@ public interface URLTagFragmentGenerator { * Generates a URL string to go in an HTML image map. *

* Note that the {@code urlText} will be created by a URL generator - * (such as {@link CategoryURLGenerator}, {@link PieURLGenerator}, - * {@link XYURLGenerator} or {@link XYZURLGenerator}) and that generator is + * (such as {@link org.jfree.chart.urls.CategoryURLGenerator}, + * {@link org.jfree.chart.urls.PieURLGenerator}, + * {@link org.jfree.chart.urls.XYURLGenerator} or + * {@link org.jfree.chart.urls.XYZURLGenerator}) and that generator is * responsible for ensuring that the URL text is correctly escaped. * * @param urlText the URL text (fully escaped). diff --git a/src/main/java/org/jfree/chart/labels/BoxAndWhiskerToolTipGenerator.java b/src/main/java/org/jfree/chart/labels/BoxAndWhiskerToolTipGenerator.java index 7a5f2db4e..b91dfc236 100644 --- a/src/main/java/org/jfree/chart/labels/BoxAndWhiskerToolTipGenerator.java +++ b/src/main/java/org/jfree/chart/labels/BoxAndWhiskerToolTipGenerator.java @@ -37,7 +37,6 @@ package org.jfree.chart.labels; import java.io.Serializable; -import java.text.MessageFormat; import java.text.NumberFormat; import org.jfree.chart.util.PublicCloneable; @@ -94,7 +93,7 @@ public BoxAndWhiskerToolTipGenerator(String format, /** * Creates the array of items that can be passed to the - * {@link MessageFormat} class for creating labels. + * {@link java.text.MessageFormat} class for creating labels. * * @param dataset the dataset ({@code null} not permitted). * @param series the series (zero-based index). diff --git a/src/main/java/org/jfree/chart/labels/BoxAndWhiskerXYToolTipGenerator.java b/src/main/java/org/jfree/chart/labels/BoxAndWhiskerXYToolTipGenerator.java index 75e146491..5b63e15a8 100644 --- a/src/main/java/org/jfree/chart/labels/BoxAndWhiskerXYToolTipGenerator.java +++ b/src/main/java/org/jfree/chart/labels/BoxAndWhiskerXYToolTipGenerator.java @@ -38,7 +38,6 @@ import java.io.Serializable; import java.text.DateFormat; -import java.text.MessageFormat; import java.text.NumberFormat; import java.util.Date; @@ -101,7 +100,7 @@ public BoxAndWhiskerXYToolTipGenerator(String toolTipFormat, /** * Creates the array of items that can be passed to the - * {@link MessageFormat} class for creating labels. + * {@link java.text.MessageFormat} class for creating labels. * * @param dataset the dataset ({@code null} not permitted). * @param series the series (zero-based index). diff --git a/src/main/java/org/jfree/chart/labels/BubbleXYItemLabelGenerator.java b/src/main/java/org/jfree/chart/labels/BubbleXYItemLabelGenerator.java index faf04d88f..e641b2af1 100644 --- a/src/main/java/org/jfree/chart/labels/BubbleXYItemLabelGenerator.java +++ b/src/main/java/org/jfree/chart/labels/BubbleXYItemLabelGenerator.java @@ -43,15 +43,15 @@ import java.util.Objects; import org.jfree.chart.HashUtils; -import org.jfree.chart.renderer.xy.XYBubbleRenderer; import org.jfree.chart.util.Args; import org.jfree.chart.util.PublicCloneable; import org.jfree.data.xy.XYDataset; import org.jfree.data.xy.XYZDataset; /** - * An item label generator defined for use with the {@link XYBubbleRenderer} - * class, or any other class that uses an {@link XYZDataset}. + * An item label generator defined for use with the + * {@link org.jfree.chart.renderer.xy.XYBubbleRenderer} class, or any other + * class that uses an {@link XYZDataset}. */ public class BubbleXYItemLabelGenerator extends AbstractXYItemLabelGenerator implements XYItemLabelGenerator, PublicCloneable, Serializable { diff --git a/src/main/java/org/jfree/chart/labels/IntervalXYItemLabelGenerator.java b/src/main/java/org/jfree/chart/labels/IntervalXYItemLabelGenerator.java index e20cd4498..cce573b1d 100644 --- a/src/main/java/org/jfree/chart/labels/IntervalXYItemLabelGenerator.java +++ b/src/main/java/org/jfree/chart/labels/IntervalXYItemLabelGenerator.java @@ -38,7 +38,6 @@ import java.io.Serializable; import java.text.DateFormat; -import java.text.MessageFormat; import java.text.NumberFormat; import java.util.Date; import org.jfree.chart.util.PublicCloneable; @@ -133,7 +132,7 @@ public IntervalXYItemLabelGenerator(String formatString, /** * Creates the array of items that can be passed to the - * {@link MessageFormat} class for creating labels. + * {@link java.text.MessageFormat} class for creating labels. * * @param dataset the dataset ({@code null} not permitted). * @param series the series (zero-based index). diff --git a/src/main/java/org/jfree/chart/labels/IntervalXYToolTipGenerator.java b/src/main/java/org/jfree/chart/labels/IntervalXYToolTipGenerator.java index 63c7ea519..f254da650 100644 --- a/src/main/java/org/jfree/chart/labels/IntervalXYToolTipGenerator.java +++ b/src/main/java/org/jfree/chart/labels/IntervalXYToolTipGenerator.java @@ -1,258 +1,257 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------------------- - * IntervalXYToolTipGenerator.java - * ------------------------------- - * (C) Copyright 2015-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.chart.labels; - -import java.io.Serializable; -import java.text.DateFormat; -import java.text.MessageFormat; -import java.text.NumberFormat; -import java.util.Date; -import org.jfree.chart.util.PublicCloneable; - -import org.jfree.data.xy.IntervalXYDataset; -import org.jfree.data.xy.XYDataset; - -/** - * A tooltip generator for datasets that implement the - * {@link IntervalXYDataset} interface. - */ -public class IntervalXYToolTipGenerator extends AbstractXYItemLabelGenerator - implements XYToolTipGenerator, Cloneable, PublicCloneable, - Serializable { - - /** The default item label format. */ - public static final String DEFAULT_TOOL_TIP_FORMAT - = "{0}: ({1} - {2}), ({5} - {6})"; - - /** - * Creates a new tooltip generator using default number formatters. - */ - public IntervalXYToolTipGenerator() { - this(DEFAULT_TOOL_TIP_FORMAT, NumberFormat.getNumberInstance(), - NumberFormat.getNumberInstance()); - } - - /** - * Creates a new tooltip generator using the specified number formatters. - * - * @param formatString the item label format string ({@code null} not - * permitted). - * @param xFormat the format object for the x values ({@code null} - * not permitted). - * @param yFormat the format object for the y values ({@code null} - * not permitted). - */ - public IntervalXYToolTipGenerator(String formatString, - NumberFormat xFormat, NumberFormat yFormat) { - super(formatString, xFormat, yFormat); - } - - /** - * Creates a new tool tip generator using the specified formatters. - * - * @param formatString the item label format string ({@code null} - * not permitted). - * @param xFormat the format object for the x values ({@code null} - * not permitted). - * @param yFormat the format object for the y values ({@code null} - * not permitted). - */ - public IntervalXYToolTipGenerator(String formatString, - DateFormat xFormat, NumberFormat yFormat) { - super(formatString, xFormat, yFormat); - } - - /** - * Creates a new tool tip generator using the specified formatters (a - * number formatter for the x-values and a date formatter for the - * y-values). - * - * @param formatString the item label format string ({@code null} - * not permitted). - * @param xFormat the format object for the x values ({@code null} - * permitted). - * @param yFormat the format object for the y values ({@code null} - * not permitted). - */ - public IntervalXYToolTipGenerator(String formatString, - NumberFormat xFormat, DateFormat yFormat) { - super(formatString, xFormat, yFormat); - } - - /** - * Creates a new tool tip generator using the specified date formatters. - * - * @param formatString the label format string ({@code null} not - * permitted). - * @param xFormat the format object for the x values ({@code null} not - * permitted). - * @param yFormat the format object for the y values ({@code null} - * not permitted). - */ - public IntervalXYToolTipGenerator(String formatString, - DateFormat xFormat, DateFormat yFormat) { - super(formatString, xFormat, yFormat); - } - - /** - * Creates the array of items that can be passed to the - * {@link MessageFormat} class for creating labels. - * - * @param dataset the dataset ({@code null} not permitted). - * @param series the series (zero-based index). - * @param item the item (zero-based index). - * - * @return An array of seven items from the dataset formatted as - * {@code String} objects (never {@code null}). - */ - @Override - protected Object[] createItemArray(XYDataset dataset, int series, - int item) { - IntervalXYDataset intervalDataset = null; - if (dataset instanceof IntervalXYDataset) { - intervalDataset = (IntervalXYDataset) dataset; - } - Object[] result = new Object[7]; - result[0] = dataset.getSeriesKey(series).toString(); - - double x = dataset.getXValue(series, item); - double xs = x; - double xe = x; - double y = dataset.getYValue(series, item); - double ys = y; - double ye = y; - if (intervalDataset != null) { - xs = intervalDataset.getStartXValue(series, item); - xe = intervalDataset.getEndXValue(series, item); - ys = intervalDataset.getStartYValue(series, item); - ye = intervalDataset.getEndYValue(series, item); - } - - DateFormat xdf = getXDateFormat(); - if (xdf != null) { - result[1] = xdf.format(new Date((long) x)); - result[2] = xdf.format(new Date((long) xs)); - result[3] = xdf.format(new Date((long) xe)); - } else { - NumberFormat xnf = getXFormat(); - result[1] = xnf.format(x); - result[2] = xnf.format(xs); - result[3] = xnf.format(xe); - } - - NumberFormat ynf = getYFormat(); - DateFormat ydf = getYDateFormat(); - if (Double.isNaN(y) && dataset.getY(series, item) == null) { - result[4] = getNullYString(); - } else { - if (ydf != null) { - result[4] = ydf.format(new Date((long) y)); - } - else { - result[4] = ynf.format(y); - } - } - if (Double.isNaN(ys) && intervalDataset != null - && intervalDataset.getStartY(series, item) == null) { - result[5] = getNullYString(); - } else { - if (ydf != null) { - result[5] = ydf.format(new Date((long) ys)); - } - else { - result[5] = ynf.format(ys); - } - } - if (Double.isNaN(ye) && intervalDataset != null - && intervalDataset.getEndY(series, item) == null) { - result[6] = getNullYString(); - } else { - if (ydf != null) { - result[6] = ydf.format(new Date((long) ye)); - } - else { - result[6] = ynf.format(ye); - } - } - return result; - } - - /** - * Generates the tool tip text for an item in a dataset. - * - * @param dataset the dataset ({@code null} not permitted). - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * - * @return The tool tip text (possibly {@code null}). - */ - @Override - public String generateToolTip(XYDataset dataset, int series, int item) { - return generateLabelString(dataset, series, item); - } - - /** - * Returns an independent copy of the generator. - * - * @return A clone. - * - * @throws CloneNotSupportedException if cloning is not supported. - */ - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - /** - * Tests this object for equality with an arbitrary object. - * - * @param obj the other object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof IntervalXYToolTipGenerator)) { - return false; - } - return super.equals(obj); - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------------------- + * IntervalXYToolTipGenerator.java + * ------------------------------- + * (C) Copyright 2015-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.chart.labels; + +import java.io.Serializable; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.util.Date; +import org.jfree.chart.util.PublicCloneable; + +import org.jfree.data.xy.IntervalXYDataset; +import org.jfree.data.xy.XYDataset; + +/** + * A tooltip generator for datasets that implement the + * {@link IntervalXYDataset} interface. + */ +public class IntervalXYToolTipGenerator extends AbstractXYItemLabelGenerator + implements XYToolTipGenerator, Cloneable, PublicCloneable, + Serializable { + + /** The default item label format. */ + public static final String DEFAULT_TOOL_TIP_FORMAT + = "{0}: ({1} - {2}), ({5} - {6})"; + + /** + * Creates a new tooltip generator using default number formatters. + */ + public IntervalXYToolTipGenerator() { + this(DEFAULT_TOOL_TIP_FORMAT, NumberFormat.getNumberInstance(), + NumberFormat.getNumberInstance()); + } + + /** + * Creates a new tooltip generator using the specified number formatters. + * + * @param formatString the item label format string ({@code null} not + * permitted). + * @param xFormat the format object for the x values ({@code null} + * not permitted). + * @param yFormat the format object for the y values ({@code null} + * not permitted). + */ + public IntervalXYToolTipGenerator(String formatString, + NumberFormat xFormat, NumberFormat yFormat) { + super(formatString, xFormat, yFormat); + } + + /** + * Creates a new tool tip generator using the specified formatters. + * + * @param formatString the item label format string ({@code null} + * not permitted). + * @param xFormat the format object for the x values ({@code null} + * not permitted). + * @param yFormat the format object for the y values ({@code null} + * not permitted). + */ + public IntervalXYToolTipGenerator(String formatString, + DateFormat xFormat, NumberFormat yFormat) { + super(formatString, xFormat, yFormat); + } + + /** + * Creates a new tool tip generator using the specified formatters (a + * number formatter for the x-values and a date formatter for the + * y-values). + * + * @param formatString the item label format string ({@code null} + * not permitted). + * @param xFormat the format object for the x values ({@code null} + * permitted). + * @param yFormat the format object for the y values ({@code null} + * not permitted). + */ + public IntervalXYToolTipGenerator(String formatString, + NumberFormat xFormat, DateFormat yFormat) { + super(formatString, xFormat, yFormat); + } + + /** + * Creates a new tool tip generator using the specified date formatters. + * + * @param formatString the label format string ({@code null} not + * permitted). + * @param xFormat the format object for the x values ({@code null} not + * permitted). + * @param yFormat the format object for the y values ({@code null} + * not permitted). + */ + public IntervalXYToolTipGenerator(String formatString, + DateFormat xFormat, DateFormat yFormat) { + super(formatString, xFormat, yFormat); + } + + /** + * Creates the array of items that can be passed to the + * {@link java.text.MessageFormat} class for creating labels. + * + * @param dataset the dataset ({@code null} not permitted). + * @param series the series (zero-based index). + * @param item the item (zero-based index). + * + * @return An array of seven items from the dataset formatted as + * {@code String} objects (never {@code null}). + */ + @Override + protected Object[] createItemArray(XYDataset dataset, int series, + int item) { + IntervalXYDataset intervalDataset = null; + if (dataset instanceof IntervalXYDataset) { + intervalDataset = (IntervalXYDataset) dataset; + } + Object[] result = new Object[7]; + result[0] = dataset.getSeriesKey(series).toString(); + + double x = dataset.getXValue(series, item); + double xs = x; + double xe = x; + double y = dataset.getYValue(series, item); + double ys = y; + double ye = y; + if (intervalDataset != null) { + xs = intervalDataset.getStartXValue(series, item); + xe = intervalDataset.getEndXValue(series, item); + ys = intervalDataset.getStartYValue(series, item); + ye = intervalDataset.getEndYValue(series, item); + } + + DateFormat xdf = getXDateFormat(); + if (xdf != null) { + result[1] = xdf.format(new Date((long) x)); + result[2] = xdf.format(new Date((long) xs)); + result[3] = xdf.format(new Date((long) xe)); + } else { + NumberFormat xnf = getXFormat(); + result[1] = xnf.format(x); + result[2] = xnf.format(xs); + result[3] = xnf.format(xe); + } + + NumberFormat ynf = getYFormat(); + DateFormat ydf = getYDateFormat(); + if (Double.isNaN(y) && dataset.getY(series, item) == null) { + result[4] = getNullYString(); + } else { + if (ydf != null) { + result[4] = ydf.format(new Date((long) y)); + } + else { + result[4] = ynf.format(y); + } + } + if (Double.isNaN(ys) && intervalDataset != null + && intervalDataset.getStartY(series, item) == null) { + result[5] = getNullYString(); + } else { + if (ydf != null) { + result[5] = ydf.format(new Date((long) ys)); + } + else { + result[5] = ynf.format(ys); + } + } + if (Double.isNaN(ye) && intervalDataset != null + && intervalDataset.getEndY(series, item) == null) { + result[6] = getNullYString(); + } else { + if (ydf != null) { + result[6] = ydf.format(new Date((long) ye)); + } + else { + result[6] = ynf.format(ye); + } + } + return result; + } + + /** + * Generates the tool tip text for an item in a dataset. + * + * @param dataset the dataset ({@code null} not permitted). + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * + * @return The tool tip text (possibly {@code null}). + */ + @Override + public String generateToolTip(XYDataset dataset, int series, int item) { + return generateLabelString(dataset, series, item); + } + + /** + * Returns an independent copy of the generator. + * + * @return A clone. + * + * @throws CloneNotSupportedException if cloning is not supported. + */ + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * Tests this object for equality with an arbitrary object. + * + * @param obj the other object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof IntervalXYToolTipGenerator)) { + return false; + } + return super.equals(obj); + } + +} diff --git a/src/main/java/org/jfree/chart/labels/PieSectionLabelGenerator.java b/src/main/java/org/jfree/chart/labels/PieSectionLabelGenerator.java index 049c11d55..d34717a3a 100644 --- a/src/main/java/org/jfree/chart/labels/PieSectionLabelGenerator.java +++ b/src/main/java/org/jfree/chart/labels/PieSectionLabelGenerator.java @@ -36,9 +36,6 @@ package org.jfree.chart.labels; -import java.awt.Font; -import java.awt.Paint; -import java.awt.font.TextAttribute; import java.text.AttributedString; import org.jfree.data.general.PieDataset; @@ -66,20 +63,6 @@ public interface PieSectionLabelGenerator { * {@link #generateSectionLabel(PieDataset, Comparable)} will * provide the fallback). Only certain attributes are recognised by the * code that ultimately displays the labels: - *

* * @param dataset the dataset. * @param key the key. diff --git a/src/main/java/org/jfree/chart/labels/StandardPieSectionLabelGenerator.java b/src/main/java/org/jfree/chart/labels/StandardPieSectionLabelGenerator.java index 2cb5a79c5..655cf20be 100644 --- a/src/main/java/org/jfree/chart/labels/StandardPieSectionLabelGenerator.java +++ b/src/main/java/org/jfree/chart/labels/StandardPieSectionLabelGenerator.java @@ -36,9 +36,6 @@ package org.jfree.chart.labels; -import java.awt.Font; -import java.awt.Paint; -import java.awt.font.TextAttribute; import java.io.Serializable; import java.text.AttributedString; import java.text.NumberFormat; @@ -168,26 +165,7 @@ public String generateSectionLabel(PieDataset dataset, Comparable key) { } /** - * Generates an attributed label for the specified series, or - * {@code null} if no attributed label is available (in which case, - * the string returned by - * {@link #generateSectionLabel(PieDataset, Comparable)} will - * provide the fallback). Only certain attributes are recognised by the - * code that ultimately displays the labels: - * + * Calls {@link #getAttributedLabel(int)} with the index of the key within the dataset * * @param dataset the dataset ({@code null} not permitted). * @param key the key. diff --git a/src/main/java/org/jfree/chart/panel/AbstractOverlay.java b/src/main/java/org/jfree/chart/panel/AbstractOverlay.java index 543209df0..d4ae42607 100644 --- a/src/main/java/org/jfree/chart/panel/AbstractOverlay.java +++ b/src/main/java/org/jfree/chart/panel/AbstractOverlay.java @@ -1,112 +1,110 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------------- - * AbstractOverlay.java - * -------------------- - * (C) Copyright 2009-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.chart.panel; - -import javax.swing.event.EventListenerList; -import org.jfree.chart.ChartPanel; -import org.jfree.chart.event.ChartChangeEvent; -import org.jfree.chart.event.OverlayChangeEvent; -import org.jfree.chart.event.OverlayChangeListener; -import org.jfree.chart.util.Args; - -/** - * A base class for implementing overlays for a {@link ChartPanel}. - */ -public class AbstractOverlay { - - /** Storage for registered change listeners. */ - private final transient EventListenerList changeListeners; - - /** - * Default constructor. - */ - public AbstractOverlay() { - this.changeListeners = new EventListenerList(); - } - - /** - * Registers an object for notification of changes to the overlay. - * - * @param listener the listener ({@code null} not permitted). - * - * @see #removeChangeListener(OverlayChangeListener) - */ - public void addChangeListener(OverlayChangeListener listener) { - Args.nullNotPermitted(listener, "listener"); - this.changeListeners.add(OverlayChangeListener.class, listener); - } - - /** - * Deregisters an object for notification of changes to the overlay. - * - * @param listener the listener ({@code null} not permitted) - * - * @see #addChangeListener(OverlayChangeListener) - */ - public void removeChangeListener(OverlayChangeListener listener) { - Args.nullNotPermitted(listener, "listener"); - this.changeListeners.remove(OverlayChangeListener.class, listener); - } - - /** - * Sends a default {@link ChartChangeEvent} to all registered listeners. - *

- * This method is for convenience only. - */ - public void fireOverlayChanged() { - OverlayChangeEvent event = new OverlayChangeEvent(this); - notifyListeners(event); - } - - /** - * Sends a {@link ChartChangeEvent} to all registered listeners. - * - * @param event information about the event that triggered the - * notification. - */ - protected void notifyListeners(OverlayChangeEvent event) { - Object[] listeners = this.changeListeners.getListenerList(); - for (int i = listeners.length - 2; i >= 0; i -= 2) { - if (listeners[i] == OverlayChangeListener.class) { - ((OverlayChangeListener) listeners[i + 1]).overlayChanged( - event); - } - } - } - -} - +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * -------------------- + * AbstractOverlay.java + * -------------------- + * (C) Copyright 2009-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.chart.panel; + +import javax.swing.event.EventListenerList; +import org.jfree.chart.event.OverlayChangeEvent; +import org.jfree.chart.event.OverlayChangeListener; +import org.jfree.chart.util.Args; + +/** + * A base class for implementing overlays for a {@link org.jfree.chart.ChartPanel}. + */ +public class AbstractOverlay { + + /** Storage for registered change listeners. */ + private final transient EventListenerList changeListeners; + + /** + * Default constructor. + */ + public AbstractOverlay() { + this.changeListeners = new EventListenerList(); + } + + /** + * Registers an object for notification of changes to the overlay. + * + * @param listener the listener ({@code null} not permitted). + * + * @see #removeChangeListener(OverlayChangeListener) + */ + public void addChangeListener(OverlayChangeListener listener) { + Args.nullNotPermitted(listener, "listener"); + this.changeListeners.add(OverlayChangeListener.class, listener); + } + + /** + * Deregisters an object for notification of changes to the overlay. + * + * @param listener the listener ({@code null} not permitted) + * + * @see #addChangeListener(OverlayChangeListener) + */ + public void removeChangeListener(OverlayChangeListener listener) { + Args.nullNotPermitted(listener, "listener"); + this.changeListeners.remove(OverlayChangeListener.class, listener); + } + + /** + * Sends a default {@link OverlayChangeEvent} to all registered listeners. + *

+ * This method is for convenience only. + */ + public void fireOverlayChanged() { + OverlayChangeEvent event = new OverlayChangeEvent(this); + notifyListeners(event); + } + + /** + * Sends an {@link OverlayChangeEvent} to all registered listeners. + * + * @param event information about the event that triggered the + * notification. + */ + protected void notifyListeners(OverlayChangeEvent event) { + Object[] listeners = this.changeListeners.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == OverlayChangeListener.class) { + ((OverlayChangeListener) listeners[i + 1]).overlayChanged( + event); + } + } + } + +} + diff --git a/src/main/java/org/jfree/chart/panel/CrosshairOverlay.java b/src/main/java/org/jfree/chart/panel/CrosshairOverlay.java index af801c0db..7ff241571 100644 --- a/src/main/java/org/jfree/chart/panel/CrosshairOverlay.java +++ b/src/main/java/org/jfree/chart/panel/CrosshairOverlay.java @@ -1,599 +1,590 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------------------- - * CrosshairOverlay.java - * --------------------- - * (C) Copyright 2011-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): John Matthews, Michal Wozniak; - * - */ - -package org.jfree.chart.panel; - -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Line2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import org.jfree.chart.ChartPanel; -import org.jfree.chart.JFreeChart; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.OverlayChangeEvent; -import org.jfree.chart.plot.Crosshair; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.text.TextUtils; -import org.jfree.chart.ui.RectangleAnchor; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.TextAnchor; -import org.jfree.chart.util.ObjectUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; - -/** - * An overlay for a {@link ChartPanel} that draws crosshairs on a chart. If - * you are using the JavaFX extensions for JFreeChart, then you should use - * the {@code CrosshairOverlayFX} class. - */ -public class CrosshairOverlay extends AbstractOverlay implements Overlay, - PropertyChangeListener, PublicCloneable, Cloneable, Serializable { - - /** Storage for the crosshairs along the x-axis. */ - private List xCrosshairs; - - /** Storage for the crosshairs along the y-axis. */ - private List yCrosshairs; - - /** - * Creates a new overlay that initially contains no crosshairs. - */ - public CrosshairOverlay() { - super(); - this.xCrosshairs = new ArrayList<>(); - this.yCrosshairs = new ArrayList<>(); - } - - /** - * Adds a crosshair against the domain axis (x-axis) and sends an - * {@link OverlayChangeEvent} to all registered listeners. - * - * @param crosshair the crosshair ({@code null} not permitted). - * - * @see #removeDomainCrosshair(org.jfree.chart.plot.Crosshair) - * @see #addRangeCrosshair(org.jfree.chart.plot.Crosshair) - */ - public void addDomainCrosshair(Crosshair crosshair) { - Args.nullNotPermitted(crosshair, "crosshair"); - this.xCrosshairs.add(crosshair); - crosshair.addPropertyChangeListener(this); - fireOverlayChanged(); - } - - /** - * Removes a domain axis crosshair and sends an {@link OverlayChangeEvent} - * to all registered listeners. - * - * @param crosshair the crosshair ({@code null} not permitted). - * - * @see #addDomainCrosshair(org.jfree.chart.plot.Crosshair) - */ - public void removeDomainCrosshair(Crosshair crosshair) { - Args.nullNotPermitted(crosshair, "crosshair"); - if (this.xCrosshairs.remove(crosshair)) { - crosshair.removePropertyChangeListener(this); - fireOverlayChanged(); - } - } - - /** - * Clears all the domain crosshairs from the overlay and sends an - * {@link OverlayChangeEvent} to all registered listeners (unless there - * were no crosshairs to begin with). - */ - public void clearDomainCrosshairs() { - if (this.xCrosshairs.isEmpty()) { - return; // nothing to do - avoids firing change event - } - for (Crosshair c : getDomainCrosshairs()) { - this.xCrosshairs.remove(c); - c.removePropertyChangeListener(this); - } - fireOverlayChanged(); - } - - /** - * Returns a new list containing the domain crosshairs for this overlay. - * - * @return A list of crosshairs. - */ - public List getDomainCrosshairs() { - return new ArrayList<>(this.xCrosshairs); - } - - /** - * Adds a crosshair against the range axis and sends an - * {@link OverlayChangeEvent} to all registered listeners. - * - * @param crosshair the crosshair ({@code null} not permitted). - */ - public void addRangeCrosshair(Crosshair crosshair) { - Args.nullNotPermitted(crosshair, "crosshair"); - this.yCrosshairs.add(crosshair); - crosshair.addPropertyChangeListener(this); - fireOverlayChanged(); - } - - /** - * Removes a range axis crosshair and sends an {@link OverlayChangeEvent} - * to all registered listeners. - * - * @param crosshair the crosshair ({@code null} not permitted). - * - * @see #addRangeCrosshair(org.jfree.chart.plot.Crosshair) - */ - public void removeRangeCrosshair(Crosshair crosshair) { - Args.nullNotPermitted(crosshair, "crosshair"); - if (this.yCrosshairs.remove(crosshair)) { - crosshair.removePropertyChangeListener(this); - fireOverlayChanged(); - } - } - - /** - * Clears all the range crosshairs from the overlay and sends an - * {@link OverlayChangeEvent} to all registered listeners (unless there - * were no crosshairs to begin with). - */ - public void clearRangeCrosshairs() { - if (this.yCrosshairs.isEmpty()) { - return; // nothing to do - avoids change notification - } - for (Crosshair c : getRangeCrosshairs()) { - this.yCrosshairs.remove(c); - c.removePropertyChangeListener(this); - } - fireOverlayChanged(); - } - - /** - * Returns a new list containing the range crosshairs for this overlay. - * - * @return A list of crosshairs. - */ - public List getRangeCrosshairs() { - return new ArrayList<>(this.yCrosshairs); - } - - /** - * Receives a property change event (typically a change in one of the - * crosshairs). - * - * @param e the event. - */ - @Override - public void propertyChange(PropertyChangeEvent e) { - fireOverlayChanged(); - } - - /** - * Renders the crosshairs in the overlay on top of the chart that has just - * been rendered in the specified {@code chartPanel}. This method is - * called by the JFreeChart framework, you won't normally call it from - * user code. - * - * @param g2 the graphics target. - * @param chartPanel the chart panel. - */ - @Override - public void paintOverlay(Graphics2D g2, ChartPanel chartPanel) { - Shape savedClip = g2.getClip(); - Rectangle2D dataArea = chartPanel.getScreenDataArea(); - g2.clip(dataArea); - JFreeChart chart = chartPanel.getChart(); - XYPlot plot = (XYPlot) chart.getPlot(); - ValueAxis xAxis = plot.getDomainAxis(); - RectangleEdge xAxisEdge = plot.getDomainAxisEdge(); - for (Crosshair ch : this.xCrosshairs) { - if (ch.isVisible()) { - double x = ch.getValue(); - double xx = xAxis.valueToJava2D(x, dataArea, xAxisEdge); - if (plot.getOrientation() == PlotOrientation.VERTICAL) { - drawVerticalCrosshair(g2, dataArea, xx, ch); - } else { - drawHorizontalCrosshair(g2, dataArea, xx, ch); - } - } - } - ValueAxis yAxis = plot.getRangeAxis(); - RectangleEdge yAxisEdge = plot.getRangeAxisEdge(); - for (Crosshair ch : this.yCrosshairs) { - if (ch.isVisible()) { - double y = ch.getValue(); - double yy = yAxis.valueToJava2D(y, dataArea, yAxisEdge); - if (plot.getOrientation() == PlotOrientation.VERTICAL) { - drawHorizontalCrosshair(g2, dataArea, yy, ch); - } else { - drawVerticalCrosshair(g2, dataArea, yy, ch); - } - } - } - g2.setClip(savedClip); - } - - /** - * Draws a crosshair horizontally across the plot. - * - * @param g2 the graphics target. - * @param dataArea the data area. - * @param y the y-value in Java2D space. - * @param crosshair the crosshair. - */ - protected void drawHorizontalCrosshair(Graphics2D g2, Rectangle2D dataArea, - double y, Crosshair crosshair) { - - if (y >= dataArea.getMinY() && y <= dataArea.getMaxY()) { - Line2D line = new Line2D.Double(dataArea.getMinX(), y, - dataArea.getMaxX(), y); - Paint savedPaint = g2.getPaint(); - Stroke savedStroke = g2.getStroke(); - g2.setPaint(crosshair.getPaint()); - g2.setStroke(crosshair.getStroke()); - g2.draw(line); - if (crosshair.isLabelVisible()) { - String label = crosshair.getLabelGenerator().generateLabel( - crosshair); - if (label != null && !label.isEmpty()) { - Font savedFont = g2.getFont(); - g2.setFont(crosshair.getLabelFont()); - RectangleAnchor anchor = crosshair.getLabelAnchor(); - Point2D pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); - float xx = (float) pt.getX(); - float yy = (float) pt.getY(); - TextAnchor alignPt = textAlignPtForLabelAnchorH(anchor); - Shape hotspot = TextUtils.calculateRotatedStringBounds( - label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); - if (!dataArea.contains(hotspot.getBounds2D())) { - anchor = flipAnchorV(anchor); - pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); - xx = (float) pt.getX(); - yy = (float) pt.getY(); - alignPt = textAlignPtForLabelAnchorH(anchor); - hotspot = TextUtils.calculateRotatedStringBounds( - label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); - } - - g2.setPaint(crosshair.getLabelBackgroundPaint()); - g2.fill(hotspot); - if (crosshair.isLabelOutlineVisible()) { - g2.setPaint(crosshair.getLabelOutlinePaint()); - g2.setStroke(crosshair.getLabelOutlineStroke()); - g2.draw(hotspot); - } - g2.setPaint(crosshair.getLabelPaint()); - TextUtils.drawAlignedString(label, g2, xx, yy, alignPt); - g2.setFont(savedFont); - } - } - g2.setPaint(savedPaint); - g2.setStroke(savedStroke); - } - } - - /** - * Draws a crosshair vertically on the plot. - * - * @param g2 the graphics target. - * @param dataArea the data area. - * @param x the x-value in Java2D space. - * @param crosshair the crosshair. - */ - protected void drawVerticalCrosshair(Graphics2D g2, Rectangle2D dataArea, - double x, Crosshair crosshair) { - - if (x >= dataArea.getMinX() && x <= dataArea.getMaxX()) { - Line2D line = new Line2D.Double(x, dataArea.getMinY(), x, - dataArea.getMaxY()); - Paint savedPaint = g2.getPaint(); - Stroke savedStroke = g2.getStroke(); - g2.setPaint(crosshair.getPaint()); - g2.setStroke(crosshair.getStroke()); - g2.draw(line); - if (crosshair.isLabelVisible()) { - String label = crosshair.getLabelGenerator().generateLabel( - crosshair); - if (label != null && !label.isEmpty()) { - Font savedFont = g2.getFont(); - g2.setFont(crosshair.getLabelFont()); - RectangleAnchor anchor = crosshair.getLabelAnchor(); - Point2D pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); - float xx = (float) pt.getX(); - float yy = (float) pt.getY(); - TextAnchor alignPt = textAlignPtForLabelAnchorV(anchor); - Shape hotspot = TextUtils.calculateRotatedStringBounds( - label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); - if (!dataArea.contains(hotspot.getBounds2D())) { - anchor = flipAnchorH(anchor); - pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); - xx = (float) pt.getX(); - yy = (float) pt.getY(); - alignPt = textAlignPtForLabelAnchorV(anchor); - hotspot = TextUtils.calculateRotatedStringBounds( - label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); - } - g2.setPaint(crosshair.getLabelBackgroundPaint()); - g2.fill(hotspot); - if (crosshair.isLabelOutlineVisible()) { - g2.setPaint(crosshair.getLabelOutlinePaint()); - g2.setStroke(crosshair.getLabelOutlineStroke()); - g2.draw(hotspot); - } - g2.setPaint(crosshair.getLabelPaint()); - TextUtils.drawAlignedString(label, g2, xx, yy, alignPt); - g2.setFont(savedFont); - } - } - g2.setPaint(savedPaint); - g2.setStroke(savedStroke); - } - } - - /** - * Calculates the anchor point for a label. - * - * @param line the line for the crosshair. - * @param anchor the anchor point. - * @param deltaX the x-offset. - * @param deltaY the y-offset. - * - * @return The anchor point. - */ - private Point2D calculateLabelPoint(Line2D line, RectangleAnchor anchor, - double deltaX, double deltaY) { - double x, y; - boolean left = (anchor == RectangleAnchor.BOTTOM_LEFT - || anchor == RectangleAnchor.LEFT - || anchor == RectangleAnchor.TOP_LEFT); - boolean right = (anchor == RectangleAnchor.BOTTOM_RIGHT - || anchor == RectangleAnchor.RIGHT - || anchor == RectangleAnchor.TOP_RIGHT); - boolean top = (anchor == RectangleAnchor.TOP_LEFT - || anchor == RectangleAnchor.TOP - || anchor == RectangleAnchor.TOP_RIGHT); - boolean bottom = (anchor == RectangleAnchor.BOTTOM_LEFT - || anchor == RectangleAnchor.BOTTOM - || anchor == RectangleAnchor.BOTTOM_RIGHT); - Rectangle rect = line.getBounds(); - - // we expect the line to be vertical or horizontal - if (line.getX1() == line.getX2()) { // vertical - x = line.getX1(); - y = (line.getY1() + line.getY2()) / 2.0; - if (left) { - x = x - deltaX; - } - if (right) { - x = x + deltaX; - } - if (top) { - y = Math.min(line.getY1(), line.getY2()) + deltaY; - } - if (bottom) { - y = Math.max(line.getY1(), line.getY2()) - deltaY; - } - } - else { // horizontal - x = (line.getX1() + line.getX2()) / 2.0; - y = line.getY1(); - if (left) { - x = Math.min(line.getX1(), line.getX2()) + deltaX; - } - if (right) { - x = Math.max(line.getX1(), line.getX2()) - deltaX; - } - if (top) { - y = y - deltaY; - } - if (bottom) { - y = y + deltaY; - } - } - return new Point2D.Double(x, y); - } - - /** - * Returns the text anchor that is used to align a label to its anchor - * point. - * - * @param anchor the anchor. - * - * @return The text alignment point. - */ - private TextAnchor textAlignPtForLabelAnchorV(RectangleAnchor anchor) { - TextAnchor result = TextAnchor.CENTER; - if (anchor.equals(RectangleAnchor.TOP_LEFT)) { - result = TextAnchor.TOP_RIGHT; - } - else if (anchor.equals(RectangleAnchor.TOP)) { - result = TextAnchor.TOP_CENTER; - } - else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { - result = TextAnchor.TOP_LEFT; - } - else if (anchor.equals(RectangleAnchor.LEFT)) { - result = TextAnchor.HALF_ASCENT_RIGHT; - } - else if (anchor.equals(RectangleAnchor.RIGHT)) { - result = TextAnchor.HALF_ASCENT_LEFT; - } - else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { - result = TextAnchor.BOTTOM_RIGHT; - } - else if (anchor.equals(RectangleAnchor.BOTTOM)) { - result = TextAnchor.BOTTOM_CENTER; - } - else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { - result = TextAnchor.BOTTOM_LEFT; - } - return result; - } - - /** - * Returns the text anchor that is used to align a label to its anchor - * point. - * - * @param anchor the anchor. - * - * @return The text alignment point. - */ - private TextAnchor textAlignPtForLabelAnchorH(RectangleAnchor anchor) { - TextAnchor result = TextAnchor.CENTER; - if (anchor.equals(RectangleAnchor.TOP_LEFT)) { - result = TextAnchor.BOTTOM_LEFT; - } - else if (anchor.equals(RectangleAnchor.TOP)) { - result = TextAnchor.BOTTOM_CENTER; - } - else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { - result = TextAnchor.BOTTOM_RIGHT; - } - else if (anchor.equals(RectangleAnchor.LEFT)) { - result = TextAnchor.HALF_ASCENT_LEFT; - } - else if (anchor.equals(RectangleAnchor.RIGHT)) { - result = TextAnchor.HALF_ASCENT_RIGHT; - } - else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { - result = TextAnchor.TOP_LEFT; - } - else if (anchor.equals(RectangleAnchor.BOTTOM)) { - result = TextAnchor.TOP_CENTER; - } - else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { - result = TextAnchor.TOP_RIGHT; - } - return result; - } - - private RectangleAnchor flipAnchorH(RectangleAnchor anchor) { - RectangleAnchor result = anchor; - if (anchor.equals(RectangleAnchor.TOP_LEFT)) { - result = RectangleAnchor.TOP_RIGHT; - } - else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { - result = RectangleAnchor.TOP_LEFT; - } - else if (anchor.equals(RectangleAnchor.LEFT)) { - result = RectangleAnchor.RIGHT; - } - else if (anchor.equals(RectangleAnchor.RIGHT)) { - result = RectangleAnchor.LEFT; - } - else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { - result = RectangleAnchor.BOTTOM_RIGHT; - } - else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { - result = RectangleAnchor.BOTTOM_LEFT; - } - return result; - } - - private RectangleAnchor flipAnchorV(RectangleAnchor anchor) { - RectangleAnchor result = anchor; - if (anchor.equals(RectangleAnchor.TOP_LEFT)) { - result = RectangleAnchor.BOTTOM_LEFT; - } - else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { - result = RectangleAnchor.BOTTOM_RIGHT; - } - else if (anchor.equals(RectangleAnchor.TOP)) { - result = RectangleAnchor.BOTTOM; - } - else if (anchor.equals(RectangleAnchor.BOTTOM)) { - result = RectangleAnchor.TOP; - } - else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { - result = RectangleAnchor.TOP_LEFT; - } - else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { - result = RectangleAnchor.TOP_RIGHT; - } - return result; - } - - /** - * Tests this overlay for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof CrosshairOverlay)) { - return false; - } - CrosshairOverlay that = (CrosshairOverlay) obj; - if (!this.xCrosshairs.equals(that.xCrosshairs)) { - return false; - } - if (!this.yCrosshairs.equals(that.yCrosshairs)) { - return false; - } - return true; - } - - /** - * Returns a clone of this instance. - * - * @return A clone of this instance. - * - * @throws java.lang.CloneNotSupportedException if there is some problem - * with the cloning. - */ - @Override - public Object clone() throws CloneNotSupportedException { - CrosshairOverlay clone = (CrosshairOverlay) super.clone(); - clone.xCrosshairs = (List) ObjectUtils.deepClone(this.xCrosshairs); - clone.yCrosshairs = (List) ObjectUtils.deepClone(this.yCrosshairs); - return clone; - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------------------- + * CrosshairOverlay.java + * --------------------- + * (C) Copyright 2011-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): John Matthews, Michal Wozniak; + * + */ + +package org.jfree.chart.panel; + +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.Crosshair; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.text.TextUtils; +import org.jfree.chart.ui.RectangleAnchor; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.TextAnchor; +import org.jfree.chart.util.ObjectUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; + +/** + * An overlay for a {@link ChartPanel} that draws crosshairs on a chart. If + * you are using the JavaFX extensions for JFreeChart, then you should use + * the {@code CrosshairOverlayFX} class. + */ +public class CrosshairOverlay extends AbstractOverlay implements Overlay, + PropertyChangeListener, PublicCloneable, Cloneable, Serializable { + + /** Storage for the crosshairs along the x-axis. */ + private List xCrosshairs; + + /** Storage for the crosshairs along the y-axis. */ + private List yCrosshairs; + + /** + * Creates a new overlay that initially contains no crosshairs. + */ + public CrosshairOverlay() { + super(); + this.xCrosshairs = new ArrayList<>(); + this.yCrosshairs = new ArrayList<>(); + } + + /** + * Adds a crosshair against the domain axis (x-axis) and calls {@link #fireOverlayChanged()}. + * + * @param crosshair the crosshair ({@code null} not permitted). + * + * @see #removeDomainCrosshair(org.jfree.chart.plot.Crosshair) + * @see #addRangeCrosshair(org.jfree.chart.plot.Crosshair) + */ + public void addDomainCrosshair(Crosshair crosshair) { + Args.nullNotPermitted(crosshair, "crosshair"); + this.xCrosshairs.add(crosshair); + crosshair.addPropertyChangeListener(this); + fireOverlayChanged(); + } + + /** + * Removes a domain axis crosshair and calls {@link #fireOverlayChanged()}. + * + * @param crosshair the crosshair ({@code null} not permitted). + * + * @see #addDomainCrosshair(org.jfree.chart.plot.Crosshair) + */ + public void removeDomainCrosshair(Crosshair crosshair) { + Args.nullNotPermitted(crosshair, "crosshair"); + if (this.xCrosshairs.remove(crosshair)) { + crosshair.removePropertyChangeListener(this); + fireOverlayChanged(); + } + } + + /** + * Clears all the domain crosshairs from the overlay and calls {@link #fireOverlayChanged()}. + */ + public void clearDomainCrosshairs() { + if (this.xCrosshairs.isEmpty()) { + return; // nothing to do - avoids firing change event + } + for (Crosshair c : getDomainCrosshairs()) { + this.xCrosshairs.remove(c); + c.removePropertyChangeListener(this); + } + fireOverlayChanged(); + } + + /** + * Returns a new list containing the domain crosshairs for this overlay. + * + * @return A list of crosshairs. + */ + public List getDomainCrosshairs() { + return new ArrayList<>(this.xCrosshairs); + } + + /** + * Adds a crosshair against the range axis and calls {@link #fireOverlayChanged()}. + * + * @param crosshair the crosshair ({@code null} not permitted). + */ + public void addRangeCrosshair(Crosshair crosshair) { + Args.nullNotPermitted(crosshair, "crosshair"); + this.yCrosshairs.add(crosshair); + crosshair.addPropertyChangeListener(this); + fireOverlayChanged(); + } + + /** + * Removes a range axis crosshair and calls {@link #fireOverlayChanged()}. + * + * @param crosshair the crosshair ({@code null} not permitted). + * + * @see #addRangeCrosshair(org.jfree.chart.plot.Crosshair) + */ + public void removeRangeCrosshair(Crosshair crosshair) { + Args.nullNotPermitted(crosshair, "crosshair"); + if (this.yCrosshairs.remove(crosshair)) { + crosshair.removePropertyChangeListener(this); + fireOverlayChanged(); + } + } + + /** + * Clears all the range crosshairs from the overlay and calls {@link #fireOverlayChanged()}. + */ + public void clearRangeCrosshairs() { + if (this.yCrosshairs.isEmpty()) { + return; // nothing to do - avoids change notification + } + for (Crosshair c : getRangeCrosshairs()) { + this.yCrosshairs.remove(c); + c.removePropertyChangeListener(this); + } + fireOverlayChanged(); + } + + /** + * Returns a new list containing the range crosshairs for this overlay. + * + * @return A list of crosshairs. + */ + public List getRangeCrosshairs() { + return new ArrayList<>(this.yCrosshairs); + } + + /** + * Receives a property change event (typically a change in one of the + * crosshairs). + * + * @param e the event. + */ + @Override + public void propertyChange(PropertyChangeEvent e) { + fireOverlayChanged(); + } + + /** + * Renders the crosshairs in the overlay on top of the chart that has just + * been rendered in the specified {@code chartPanel}. This method is + * called by the JFreeChart framework, you won't normally call it from + * user code. + * + * @param g2 the graphics target. + * @param chartPanel the chart panel. + */ + @Override + public void paintOverlay(Graphics2D g2, ChartPanel chartPanel) { + Shape savedClip = g2.getClip(); + Rectangle2D dataArea = chartPanel.getScreenDataArea(); + g2.clip(dataArea); + JFreeChart chart = chartPanel.getChart(); + XYPlot plot = (XYPlot) chart.getPlot(); + ValueAxis xAxis = plot.getDomainAxis(); + RectangleEdge xAxisEdge = plot.getDomainAxisEdge(); + for (Crosshair ch : this.xCrosshairs) { + if (ch.isVisible()) { + double x = ch.getValue(); + double xx = xAxis.valueToJava2D(x, dataArea, xAxisEdge); + if (plot.getOrientation() == PlotOrientation.VERTICAL) { + drawVerticalCrosshair(g2, dataArea, xx, ch); + } else { + drawHorizontalCrosshair(g2, dataArea, xx, ch); + } + } + } + ValueAxis yAxis = plot.getRangeAxis(); + RectangleEdge yAxisEdge = plot.getRangeAxisEdge(); + for (Crosshair ch : this.yCrosshairs) { + if (ch.isVisible()) { + double y = ch.getValue(); + double yy = yAxis.valueToJava2D(y, dataArea, yAxisEdge); + if (plot.getOrientation() == PlotOrientation.VERTICAL) { + drawHorizontalCrosshair(g2, dataArea, yy, ch); + } else { + drawVerticalCrosshair(g2, dataArea, yy, ch); + } + } + } + g2.setClip(savedClip); + } + + /** + * Draws a crosshair horizontally across the plot. + * + * @param g2 the graphics target. + * @param dataArea the data area. + * @param y the y-value in Java2D space. + * @param crosshair the crosshair. + */ + protected void drawHorizontalCrosshair(Graphics2D g2, Rectangle2D dataArea, + double y, Crosshair crosshair) { + + if (y >= dataArea.getMinY() && y <= dataArea.getMaxY()) { + Line2D line = new Line2D.Double(dataArea.getMinX(), y, + dataArea.getMaxX(), y); + Paint savedPaint = g2.getPaint(); + Stroke savedStroke = g2.getStroke(); + g2.setPaint(crosshair.getPaint()); + g2.setStroke(crosshair.getStroke()); + g2.draw(line); + if (crosshair.isLabelVisible()) { + String label = crosshair.getLabelGenerator().generateLabel( + crosshair); + if (label != null && !label.isEmpty()) { + Font savedFont = g2.getFont(); + g2.setFont(crosshair.getLabelFont()); + RectangleAnchor anchor = crosshair.getLabelAnchor(); + Point2D pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); + float xx = (float) pt.getX(); + float yy = (float) pt.getY(); + TextAnchor alignPt = textAlignPtForLabelAnchorH(anchor); + Shape hotspot = TextUtils.calculateRotatedStringBounds( + label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); + if (!dataArea.contains(hotspot.getBounds2D())) { + anchor = flipAnchorV(anchor); + pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); + xx = (float) pt.getX(); + yy = (float) pt.getY(); + alignPt = textAlignPtForLabelAnchorH(anchor); + hotspot = TextUtils.calculateRotatedStringBounds( + label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); + } + + g2.setPaint(crosshair.getLabelBackgroundPaint()); + g2.fill(hotspot); + if (crosshair.isLabelOutlineVisible()) { + g2.setPaint(crosshair.getLabelOutlinePaint()); + g2.setStroke(crosshair.getLabelOutlineStroke()); + g2.draw(hotspot); + } + g2.setPaint(crosshair.getLabelPaint()); + TextUtils.drawAlignedString(label, g2, xx, yy, alignPt); + g2.setFont(savedFont); + } + } + g2.setPaint(savedPaint); + g2.setStroke(savedStroke); + } + } + + /** + * Draws a crosshair vertically on the plot. + * + * @param g2 the graphics target. + * @param dataArea the data area. + * @param x the x-value in Java2D space. + * @param crosshair the crosshair. + */ + protected void drawVerticalCrosshair(Graphics2D g2, Rectangle2D dataArea, + double x, Crosshair crosshair) { + + if (x >= dataArea.getMinX() && x <= dataArea.getMaxX()) { + Line2D line = new Line2D.Double(x, dataArea.getMinY(), x, + dataArea.getMaxY()); + Paint savedPaint = g2.getPaint(); + Stroke savedStroke = g2.getStroke(); + g2.setPaint(crosshair.getPaint()); + g2.setStroke(crosshair.getStroke()); + g2.draw(line); + if (crosshair.isLabelVisible()) { + String label = crosshair.getLabelGenerator().generateLabel( + crosshair); + if (label != null && !label.isEmpty()) { + Font savedFont = g2.getFont(); + g2.setFont(crosshair.getLabelFont()); + RectangleAnchor anchor = crosshair.getLabelAnchor(); + Point2D pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); + float xx = (float) pt.getX(); + float yy = (float) pt.getY(); + TextAnchor alignPt = textAlignPtForLabelAnchorV(anchor); + Shape hotspot = TextUtils.calculateRotatedStringBounds( + label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); + if (!dataArea.contains(hotspot.getBounds2D())) { + anchor = flipAnchorH(anchor); + pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); + xx = (float) pt.getX(); + yy = (float) pt.getY(); + alignPt = textAlignPtForLabelAnchorV(anchor); + hotspot = TextUtils.calculateRotatedStringBounds( + label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); + } + g2.setPaint(crosshair.getLabelBackgroundPaint()); + g2.fill(hotspot); + if (crosshair.isLabelOutlineVisible()) { + g2.setPaint(crosshair.getLabelOutlinePaint()); + g2.setStroke(crosshair.getLabelOutlineStroke()); + g2.draw(hotspot); + } + g2.setPaint(crosshair.getLabelPaint()); + TextUtils.drawAlignedString(label, g2, xx, yy, alignPt); + g2.setFont(savedFont); + } + } + g2.setPaint(savedPaint); + g2.setStroke(savedStroke); + } + } + + /** + * Calculates the anchor point for a label. + * + * @param line the line for the crosshair. + * @param anchor the anchor point. + * @param deltaX the x-offset. + * @param deltaY the y-offset. + * + * @return The anchor point. + */ + private Point2D calculateLabelPoint(Line2D line, RectangleAnchor anchor, + double deltaX, double deltaY) { + double x, y; + boolean left = (anchor == RectangleAnchor.BOTTOM_LEFT + || anchor == RectangleAnchor.LEFT + || anchor == RectangleAnchor.TOP_LEFT); + boolean right = (anchor == RectangleAnchor.BOTTOM_RIGHT + || anchor == RectangleAnchor.RIGHT + || anchor == RectangleAnchor.TOP_RIGHT); + boolean top = (anchor == RectangleAnchor.TOP_LEFT + || anchor == RectangleAnchor.TOP + || anchor == RectangleAnchor.TOP_RIGHT); + boolean bottom = (anchor == RectangleAnchor.BOTTOM_LEFT + || anchor == RectangleAnchor.BOTTOM + || anchor == RectangleAnchor.BOTTOM_RIGHT); + Rectangle rect = line.getBounds(); + + // we expect the line to be vertical or horizontal + if (line.getX1() == line.getX2()) { // vertical + x = line.getX1(); + y = (line.getY1() + line.getY2()) / 2.0; + if (left) { + x = x - deltaX; + } + if (right) { + x = x + deltaX; + } + if (top) { + y = Math.min(line.getY1(), line.getY2()) + deltaY; + } + if (bottom) { + y = Math.max(line.getY1(), line.getY2()) - deltaY; + } + } + else { // horizontal + x = (line.getX1() + line.getX2()) / 2.0; + y = line.getY1(); + if (left) { + x = Math.min(line.getX1(), line.getX2()) + deltaX; + } + if (right) { + x = Math.max(line.getX1(), line.getX2()) - deltaX; + } + if (top) { + y = y - deltaY; + } + if (bottom) { + y = y + deltaY; + } + } + return new Point2D.Double(x, y); + } + + /** + * Returns the text anchor that is used to align a label to its anchor + * point. + * + * @param anchor the anchor. + * + * @return The text alignment point. + */ + private TextAnchor textAlignPtForLabelAnchorV(RectangleAnchor anchor) { + TextAnchor result = TextAnchor.CENTER; + if (anchor.equals(RectangleAnchor.TOP_LEFT)) { + result = TextAnchor.TOP_RIGHT; + } + else if (anchor.equals(RectangleAnchor.TOP)) { + result = TextAnchor.TOP_CENTER; + } + else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { + result = TextAnchor.TOP_LEFT; + } + else if (anchor.equals(RectangleAnchor.LEFT)) { + result = TextAnchor.HALF_ASCENT_RIGHT; + } + else if (anchor.equals(RectangleAnchor.RIGHT)) { + result = TextAnchor.HALF_ASCENT_LEFT; + } + else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { + result = TextAnchor.BOTTOM_RIGHT; + } + else if (anchor.equals(RectangleAnchor.BOTTOM)) { + result = TextAnchor.BOTTOM_CENTER; + } + else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { + result = TextAnchor.BOTTOM_LEFT; + } + return result; + } + + /** + * Returns the text anchor that is used to align a label to its anchor + * point. + * + * @param anchor the anchor. + * + * @return The text alignment point. + */ + private TextAnchor textAlignPtForLabelAnchorH(RectangleAnchor anchor) { + TextAnchor result = TextAnchor.CENTER; + if (anchor.equals(RectangleAnchor.TOP_LEFT)) { + result = TextAnchor.BOTTOM_LEFT; + } + else if (anchor.equals(RectangleAnchor.TOP)) { + result = TextAnchor.BOTTOM_CENTER; + } + else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { + result = TextAnchor.BOTTOM_RIGHT; + } + else if (anchor.equals(RectangleAnchor.LEFT)) { + result = TextAnchor.HALF_ASCENT_LEFT; + } + else if (anchor.equals(RectangleAnchor.RIGHT)) { + result = TextAnchor.HALF_ASCENT_RIGHT; + } + else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { + result = TextAnchor.TOP_LEFT; + } + else if (anchor.equals(RectangleAnchor.BOTTOM)) { + result = TextAnchor.TOP_CENTER; + } + else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { + result = TextAnchor.TOP_RIGHT; + } + return result; + } + + private RectangleAnchor flipAnchorH(RectangleAnchor anchor) { + RectangleAnchor result = anchor; + if (anchor.equals(RectangleAnchor.TOP_LEFT)) { + result = RectangleAnchor.TOP_RIGHT; + } + else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { + result = RectangleAnchor.TOP_LEFT; + } + else if (anchor.equals(RectangleAnchor.LEFT)) { + result = RectangleAnchor.RIGHT; + } + else if (anchor.equals(RectangleAnchor.RIGHT)) { + result = RectangleAnchor.LEFT; + } + else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { + result = RectangleAnchor.BOTTOM_RIGHT; + } + else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { + result = RectangleAnchor.BOTTOM_LEFT; + } + return result; + } + + private RectangleAnchor flipAnchorV(RectangleAnchor anchor) { + RectangleAnchor result = anchor; + if (anchor.equals(RectangleAnchor.TOP_LEFT)) { + result = RectangleAnchor.BOTTOM_LEFT; + } + else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { + result = RectangleAnchor.BOTTOM_RIGHT; + } + else if (anchor.equals(RectangleAnchor.TOP)) { + result = RectangleAnchor.BOTTOM; + } + else if (anchor.equals(RectangleAnchor.BOTTOM)) { + result = RectangleAnchor.TOP; + } + else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { + result = RectangleAnchor.TOP_LEFT; + } + else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { + result = RectangleAnchor.TOP_RIGHT; + } + return result; + } + + /** + * Tests this overlay for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof CrosshairOverlay)) { + return false; + } + CrosshairOverlay that = (CrosshairOverlay) obj; + if (!this.xCrosshairs.equals(that.xCrosshairs)) { + return false; + } + if (!this.yCrosshairs.equals(that.yCrosshairs)) { + return false; + } + return true; + } + + /** + * Returns a clone of this instance. + * + * @return A clone of this instance. + * + * @throws java.lang.CloneNotSupportedException if there is some problem + * with the cloning. + */ + @Override + public Object clone() throws CloneNotSupportedException { + CrosshairOverlay clone = (CrosshairOverlay) super.clone(); + clone.xCrosshairs = (List) ObjectUtils.deepClone(this.xCrosshairs); + clone.yCrosshairs = (List) ObjectUtils.deepClone(this.yCrosshairs); + return clone; + } + +} diff --git a/src/main/java/org/jfree/chart/plot/CategoryCrosshairState.java b/src/main/java/org/jfree/chart/plot/CategoryCrosshairState.java index c28c79f63..30a28d39e 100644 --- a/src/main/java/org/jfree/chart/plot/CategoryCrosshairState.java +++ b/src/main/java/org/jfree/chart/plot/CategoryCrosshairState.java @@ -38,8 +38,6 @@ import java.awt.geom.Point2D; -import org.jfree.chart.renderer.category.CategoryItemRenderer; - /** * Represents state information for the crosshairs in a {@link CategoryPlot}. * An instance of this class is created at the start of the rendering process, @@ -104,8 +102,9 @@ public void setColumnKey(Comparable key) { } /** - * Evaluates a data point from a {@link CategoryItemRenderer} and if it is - * the closest to the anchor point it becomes the new crosshair point. + * Evaluates a data point from a + * {@link org.jfree.chart.renderer.category.CategoryItemRenderer} and if it + * is the closest to the anchor point it becomes the new crosshair point. * * @param rowKey the row key. * @param columnKey the column key. diff --git a/src/main/java/org/jfree/chart/plot/CategoryPlot.java b/src/main/java/org/jfree/chart/plot/CategoryPlot.java index 7d069caa6..d8c8274c1 100644 --- a/src/main/java/org/jfree/chart/plot/CategoryPlot.java +++ b/src/main/java/org/jfree/chart/plot/CategoryPlot.java @@ -1,4924 +1,4922 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ----------------- - * CategoryPlot.java - * ----------------- - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): Jeremy Bowman; - * Arnaud Lelievre; - * Richard West, Advanced Micro Devices, Inc.; - * Ulrich Voigt - patch 2686040; - * Peter Kolb - patches 2603321 and 2809117; - * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); - * - */ - -package org.jfree.chart.plot; - -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Line2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.ResourceBundle; -import java.util.Set; -import java.util.TreeMap; -import org.jfree.chart.JFreeChart; -import org.jfree.chart.LegendItemCollection; -import org.jfree.chart.annotations.Annotation; -import org.jfree.chart.annotations.CategoryAnnotation; -import org.jfree.chart.axis.Axis; -import org.jfree.chart.axis.AxisCollection; -import org.jfree.chart.axis.AxisLocation; -import org.jfree.chart.axis.AxisSpace; -import org.jfree.chart.axis.AxisState; -import org.jfree.chart.axis.CategoryAnchor; -import org.jfree.chart.axis.CategoryAxis; -import org.jfree.chart.axis.TickType; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.axis.ValueTick; -import org.jfree.chart.event.AnnotationChangeEvent; -import org.jfree.chart.event.AnnotationChangeListener; -import org.jfree.chart.event.ChartChangeEventType; -import org.jfree.chart.event.PlotChangeEvent; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.event.RendererChangeListener; -import org.jfree.chart.renderer.category.CategoryItemRenderer; -import org.jfree.chart.renderer.category.CategoryItemRendererState; -import org.jfree.chart.ui.Layer; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.RectangleInsets; -import org.jfree.chart.util.CloneUtils; -import org.jfree.chart.util.ObjectUtils; -import org.jfree.chart.util.PaintUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.ResourceBundleWrapper; -import org.jfree.chart.util.SerialUtils; -import org.jfree.chart.util.ShadowGenerator; -import org.jfree.chart.util.ShapeUtils; -import org.jfree.chart.util.SortOrder; -import org.jfree.data.Range; -import org.jfree.data.category.CategoryDataset; -import org.jfree.data.general.DatasetChangeEvent; -import org.jfree.data.general.DatasetUtils; - -/** - * A general plotting class that uses data from a {@link CategoryDataset} and - * renders each data item using a {@link CategoryItemRenderer}. - */ -public class CategoryPlot extends Plot implements ValueAxisPlot, Pannable, - Zoomable, AnnotationChangeListener, RendererChangeListener, - Cloneable, PublicCloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = -3537691700434728188L; - - /** - * The default visibility of the grid lines plotted against the domain - * axis. - */ - public static final boolean DEFAULT_DOMAIN_GRIDLINES_VISIBLE = false; - - /** - * The default visibility of the grid lines plotted against the range - * axis. - */ - public static final boolean DEFAULT_RANGE_GRIDLINES_VISIBLE = true; - - /** The default grid line stroke. */ - public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, - BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[] - {2.0f, 2.0f}, 0.0f); - - /** The default grid line paint. */ - public static final Paint DEFAULT_GRIDLINE_PAINT = Color.LIGHT_GRAY; - - /** The default value label font. */ - public static final Font DEFAULT_VALUE_LABEL_FONT = new Font("SansSerif", - Font.PLAIN, 10); - - /** - * The default crosshair visibility. - */ - public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false; - - /** - * The default crosshair stroke. - */ - public static final Stroke DEFAULT_CROSSHAIR_STROKE - = DEFAULT_GRIDLINE_STROKE; - - /** - * The default crosshair paint. - */ - public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.BLUE; - - /** The resourceBundle for the localization. */ - protected static ResourceBundle localizationResources - = ResourceBundleWrapper.getBundle( - "org.jfree.chart.plot.LocalizationBundle"); - - /** The plot orientation. */ - private PlotOrientation orientation; - - /** The offset between the data area and the axes. */ - private RectangleInsets axisOffset; - - /** Storage for the domain axes. */ - private Map domainAxes; - - /** Storage for the domain axis locations. */ - private Map domainAxisLocations; - - /** - * A flag that controls whether or not the shared domain axis is drawn - * (only relevant when the plot is being used as a subplot). - */ - private boolean drawSharedDomainAxis; - - /** Storage for the range axes. */ - private Map rangeAxes; - - /** Storage for the range axis locations. */ - private Map rangeAxisLocations; - - /** Storage for the datasets. */ - private Map datasets; - - /** - * Storage for keys that map each dataset to one or more domain axes. - * Typically a dataset is rendered using the scale of a single axis, but - * a dataset can contribute to the "auto-range" of any number of axes. - */ - private TreeMap> datasetToDomainAxesMap; - - /** - * Storage for keys that map each dataset to one or more range axes. - * Typically a dataset is rendered using the scale of a single axis, but - * a dataset can contribute to the "auto-range" of any number of axes. - */ - private TreeMap> datasetToRangeAxesMap; - - /** Storage for the renderers. */ - private Map renderers; - - /** The dataset rendering order. */ - private DatasetRenderingOrder renderingOrder - = DatasetRenderingOrder.REVERSE; - - /** - * Controls the order in which the columns are traversed when rendering the - * data items. - */ - private SortOrder columnRenderingOrder = SortOrder.ASCENDING; - - /** - * Controls the order in which the rows are traversed when rendering the - * data items. - */ - private SortOrder rowRenderingOrder = SortOrder.ASCENDING; - - /** - * A flag that controls whether the grid-lines for the domain axis are - * visible. - */ - private boolean domainGridlinesVisible; - - /** The position of the domain gridlines relative to the category. */ - private CategoryAnchor domainGridlinePosition; - - /** The stroke used to draw the domain grid-lines. */ - private transient Stroke domainGridlineStroke; - - /** The paint used to draw the domain grid-lines. */ - private transient Paint domainGridlinePaint; - - /** - * A flag that controls whether or not the zero baseline against the range - * axis is visible. - */ - private boolean rangeZeroBaselineVisible; - - /** - * The stroke used for the zero baseline against the range axis. - */ - private transient Stroke rangeZeroBaselineStroke; - - /** - * The paint used for the zero baseline against the range axis. - */ - private transient Paint rangeZeroBaselinePaint; - - /** - * A flag that controls whether the grid-lines for the range axis are - * visible. - */ - private boolean rangeGridlinesVisible; - - /** The stroke used to draw the range axis grid-lines. */ - private transient Stroke rangeGridlineStroke; - - /** The paint used to draw the range axis grid-lines. */ - private transient Paint rangeGridlinePaint; - - /** - * A flag that controls whether or not gridlines are shown for the minor - * tick values on the primary range axis. - */ - private boolean rangeMinorGridlinesVisible; - - /** - * The stroke used to draw the range minor grid-lines. - */ - private transient Stroke rangeMinorGridlineStroke; - - /** - * The paint used to draw the range minor grid-lines. - */ - private transient Paint rangeMinorGridlinePaint; - - /** The anchor value. */ - private double anchorValue; - - /** - * The index for the dataset that the crosshairs are linked to (this - * determines which axes the crosshairs are plotted against). - */ - private int crosshairDatasetIndex; - - /** - * A flag that controls the visibility of the domain crosshair. - */ - private boolean domainCrosshairVisible; - - /** - * The row key for the crosshair point. - */ - private Comparable domainCrosshairRowKey; - - /** - * The column key for the crosshair point. - */ - private Comparable domainCrosshairColumnKey; - - /** - * The stroke used to draw the domain crosshair if it is visible. - */ - private transient Stroke domainCrosshairStroke; - - /** - * The paint used to draw the domain crosshair if it is visible. - */ - private transient Paint domainCrosshairPaint; - - /** A flag that controls whether or not a range crosshair is drawn. */ - private boolean rangeCrosshairVisible; - - /** The range crosshair value. */ - private double rangeCrosshairValue; - - /** The pen/brush used to draw the crosshair (if any). */ - private transient Stroke rangeCrosshairStroke; - - /** The color used to draw the crosshair (if any). */ - private transient Paint rangeCrosshairPaint; - - /** - * A flag that controls whether or not the crosshair locks onto actual - * data points. - */ - private boolean rangeCrosshairLockedOnData = true; - - /** A map containing lists of markers for the domain axes. */ - private Map> foregroundDomainMarkers; - - /** A map containing lists of markers for the domain axes. */ - private Map> backgroundDomainMarkers; - - /** A map containing lists of markers for the range axes. */ - private Map> foregroundRangeMarkers; - - /** A map containing lists of markers for the range axes. */ - private Map> backgroundRangeMarkers; - - /** - * A (possibly empty) list of annotations for the plot. The list should - * be initialised in the constructor and never allowed to be - * {@code null}. - */ - private List annotations; - - /** - * The weight for the plot (only relevant when the plot is used as a subplot - * within a combined plot). - */ - private int weight; - - /** The fixed space for the domain axis. */ - private AxisSpace fixedDomainAxisSpace; - - /** The fixed space for the range axis. */ - private AxisSpace fixedRangeAxisSpace; - - /** - * An optional collection of legend items that can be returned by the - * getLegendItems() method. - */ - private LegendItemCollection fixedLegendItems; - - /** - * A flag that controls whether or not panning is enabled for the - * range axis/axes. - */ - private boolean rangePannable; - - /** - * The shadow generator for the plot ({@code null} permitted). - */ - private ShadowGenerator shadowGenerator; - - /** - * Default constructor. - */ - public CategoryPlot() { - this(null, null, null, null); - } - - /** - * Creates a new plot. - * - * @param dataset the dataset ({@code null} permitted). - * @param domainAxis the domain axis ({@code null} permitted). - * @param rangeAxis the range axis ({@code null} permitted). - * @param renderer the item renderer ({@code null} permitted). - * - */ - public CategoryPlot(CategoryDataset dataset, CategoryAxis domainAxis, - ValueAxis rangeAxis, CategoryItemRenderer renderer) { - - super(); - - this.orientation = PlotOrientation.VERTICAL; - - // allocate storage for dataset, axes and renderers - this.domainAxes = new HashMap<>(); - this.domainAxisLocations = new HashMap<>(); - this.rangeAxes = new HashMap<>(); - this.rangeAxisLocations = new HashMap<>(); - - this.datasetToDomainAxesMap = new TreeMap<>(); - this.datasetToRangeAxesMap = new TreeMap<>(); - - this.renderers = new HashMap<>(); - - this.datasets = new HashMap<>(); - this.datasets.put(0, dataset); - if (dataset != null) { - dataset.addChangeListener(this); - } - - this.axisOffset = RectangleInsets.ZERO_INSETS; - this.domainAxisLocations.put(0, AxisLocation.BOTTOM_OR_LEFT); - this.rangeAxisLocations.put(0, AxisLocation.TOP_OR_LEFT); - - this.renderers.put(0, renderer); - if (renderer != null) { - renderer.setPlot(this); - renderer.addChangeListener(this); - } - - this.domainAxes.put(0, domainAxis); - mapDatasetToDomainAxis(0, 0); - if (domainAxis != null) { - domainAxis.setPlot(this); - domainAxis.addChangeListener(this); - } - this.drawSharedDomainAxis = false; - - this.rangeAxes.put(0, rangeAxis); - mapDatasetToRangeAxis(0, 0); - if (rangeAxis != null) { - rangeAxis.setPlot(this); - rangeAxis.addChangeListener(this); - } - - configureDomainAxes(); - configureRangeAxes(); - - this.domainGridlinesVisible = DEFAULT_DOMAIN_GRIDLINES_VISIBLE; - this.domainGridlinePosition = CategoryAnchor.MIDDLE; - this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE; - this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT; - - this.rangeZeroBaselineVisible = false; - this.rangeZeroBaselinePaint = Color.BLACK; - this.rangeZeroBaselineStroke = new BasicStroke(0.5f); - - this.rangeGridlinesVisible = DEFAULT_RANGE_GRIDLINES_VISIBLE; - this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE; - this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT; - - this.rangeMinorGridlinesVisible = false; - this.rangeMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE; - this.rangeMinorGridlinePaint = Color.WHITE; - - this.foregroundDomainMarkers = new HashMap<>(); - this.backgroundDomainMarkers = new HashMap<>(); - this.foregroundRangeMarkers = new HashMap<>(); - this.backgroundRangeMarkers = new HashMap<>(); - - this.anchorValue = 0.0; - - this.domainCrosshairVisible = false; - this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; - this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; - - this.rangeCrosshairVisible = DEFAULT_CROSSHAIR_VISIBLE; - this.rangeCrosshairValue = 0.0; - this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; - this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; - - this.annotations = new ArrayList<>(); - - this.rangePannable = false; - this.shadowGenerator = null; - } - - /** - * Returns a string describing the type of plot. - * - * @return The type. - */ - @Override - public String getPlotType() { - return localizationResources.getString("Category_Plot"); - } - - /** - * Returns the orientation of the plot. - * - * @return The orientation of the plot (never {@code null}). - * - * @see #setOrientation(PlotOrientation) - */ - @Override - public PlotOrientation getOrientation() { - return this.orientation; - } - - /** - * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param orientation the orientation ({@code null} not permitted). - * - * @see #getOrientation() - */ - public void setOrientation(PlotOrientation orientation) { - Args.nullNotPermitted(orientation, "orientation"); - this.orientation = orientation; - fireChangeEvent(); - } - - /** - * Returns the axis offset. - * - * @return The axis offset (never {@code null}). - * - * @see #setAxisOffset(RectangleInsets) - */ - public RectangleInsets getAxisOffset() { - return this.axisOffset; - } - - /** - * Sets the axis offsets (gap between the data area and the axes) and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param offset the offset ({@code null} not permitted). - * - * @see #getAxisOffset() - */ - public void setAxisOffset(RectangleInsets offset) { - Args.nullNotPermitted(offset, "offset"); - this.axisOffset = offset; - fireChangeEvent(); - } - - /** - * Returns the domain axis for the plot. If the domain axis for this plot - * is {@code null}, then the method will return the parent plot's - * domain axis (if there is a parent plot). - * - * @return The domain axis ({@code null} permitted). - * - * @see #setDomainAxis(CategoryAxis) - */ - public CategoryAxis getDomainAxis() { - return getDomainAxis(0); - } - - /** - * Returns a domain axis. - * - * @param index the axis index. - * - * @return The axis ({@code null} possible). - * - * @see #setDomainAxis(int, CategoryAxis) - */ - public CategoryAxis getDomainAxis(int index) { - CategoryAxis result = this.domainAxes.get(index); - if (result == null) { - Plot parent = getParent(); - if (parent instanceof CategoryPlot) { - CategoryPlot cp = (CategoryPlot) parent; - result = cp.getDomainAxis(index); - } - } - return result; - } - - /** - * Returns a map containing the domain axes that are assigned to this plot. - * The map is unmodifiable. - * - * @return A map containing the domain axes that are assigned to the plot - * (never {@code null}). - * - * @since 1.5.4 - */ - public Map getDomainAxes() { - return Collections.unmodifiableMap(this.domainAxes); - } - - /** - * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param axis the axis ({@code null} permitted). - * - * @see #getDomainAxis() - */ - public void setDomainAxis(CategoryAxis axis) { - setDomainAxis(0, axis); - } - - /** - * Sets a domain axis and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param index the axis index. - * @param axis the axis ({@code null} permitted). - * - * @see #getDomainAxis(int) - */ - public void setDomainAxis(int index, CategoryAxis axis) { - setDomainAxis(index, axis, true); - } - - /** - * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param index the axis index. - * @param axis the axis ({@code null} permitted). - * @param notify notify listeners? - */ - public void setDomainAxis(int index, CategoryAxis axis, boolean notify) { - CategoryAxis existing = this.domainAxes.get(index); - if (existing != null) { - existing.removeChangeListener(this); - } - if (axis != null) { - axis.setPlot(this); - } - this.domainAxes.put(index, axis); - if (axis != null) { - axis.configure(); - axis.addChangeListener(this); - } - if (notify) { - fireChangeEvent(); - } - } - - /** - * Sets the domain axes for this plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param axes the axes ({@code null} not permitted). - * - * @see #setRangeAxes(ValueAxis[]) - */ - public void setDomainAxes(CategoryAxis[] axes) { - for (int i = 0; i < axes.length; i++) { - setDomainAxis(i, axes[i], false); - } - fireChangeEvent(); - } - - /** - * Returns the index of the specified axis, or {@code -1} if the axis - * is not assigned to the plot. - * - * @param axis the axis ({@code null} not permitted). - * - * @return The axis index. - * - * @see #getDomainAxis(int) - * @see #getRangeAxisIndex(ValueAxis) - */ - public int getDomainAxisIndex(CategoryAxis axis) { - Args.nullNotPermitted(axis, "axis"); - for (Entry entry : this.domainAxes.entrySet()) { - if (entry.getValue() == axis) { - return entry.getKey(); - } - } - return -1; - } - - /** - * Returns the domain axis location for the primary domain axis. - * - * @return The location (never {@code null}). - * - * @see #getRangeAxisLocation() - */ - public AxisLocation getDomainAxisLocation() { - return getDomainAxisLocation(0); - } - - /** - * Returns the location for a domain axis. - * - * @param index the axis index. - * - * @return The location. - * - * @see #setDomainAxisLocation(int, AxisLocation) - */ - public AxisLocation getDomainAxisLocation(int index) { - AxisLocation result = this.domainAxisLocations.get(index); - if (result == null) { - result = AxisLocation.getOpposite(getDomainAxisLocation(0)); - } - return result; - } - - /** - * Sets the location of the domain axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param location the axis location ({@code null} not permitted). - * - * @see #getDomainAxisLocation() - * @see #setDomainAxisLocation(int, AxisLocation) - */ - public void setDomainAxisLocation(AxisLocation location) { - // delegate... - setDomainAxisLocation(0, location, true); - } - - /** - * Sets the location of the domain axis and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param location the axis location ({@code null} not permitted). - * @param notify a flag that controls whether listeners are notified. - */ - public void setDomainAxisLocation(AxisLocation location, boolean notify) { - // delegate... - setDomainAxisLocation(0, location, notify); - } - - /** - * Sets the location for a domain axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param index the axis index. - * @param location the location. - * - * @see #getDomainAxisLocation(int) - * @see #setRangeAxisLocation(int, AxisLocation) - */ - public void setDomainAxisLocation(int index, AxisLocation location) { - // delegate... - setDomainAxisLocation(index, location, true); - } - - /** - * Sets the location for a domain axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param index the axis index. - * @param location the location. - * @param notify notify listeners? - * - * @see #getDomainAxisLocation(int) - * @see #setRangeAxisLocation(int, AxisLocation, boolean) - */ - public void setDomainAxisLocation(int index, AxisLocation location, - boolean notify) { - if (index == 0 && location == null) { - throw new IllegalArgumentException( - "Null 'location' for index 0 not permitted."); - } - this.domainAxisLocations.put(index, location); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the domain axis edge. This is derived from the axis location - * and the plot orientation. - * - * @return The edge (never {@code null}). - */ - public RectangleEdge getDomainAxisEdge() { - return getDomainAxisEdge(0); - } - - /** - * Returns the edge for a domain axis. - * - * @param index the axis index. - * - * @return The edge (never {@code null}). - */ - public RectangleEdge getDomainAxisEdge(int index) { - RectangleEdge result; - AxisLocation location = getDomainAxisLocation(index); - if (location != null) { - result = Plot.resolveDomainAxisLocation(location, this.orientation); - } else { - result = RectangleEdge.opposite(getDomainAxisEdge(0)); - } - return result; - } - - /** - * Returns the number of domain axes. - * - * @return The axis count. - */ - public int getDomainAxisCount() { - return this.domainAxes.size(); - } - - /** - * Clears the domain axes from the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - */ - public void clearDomainAxes() { - for (CategoryAxis xAxis : this.domainAxes.values()) { - if (xAxis != null) { - xAxis.removeChangeListener(this); - } - } - this.domainAxes.clear(); - fireChangeEvent(); - } - - /** - * Configures the domain axes. - */ - public void configureDomainAxes() { - for (CategoryAxis xAxis : this.domainAxes.values()) { - if (xAxis != null) { - xAxis.configure(); - } - } - } - - /** - * Returns the range axis for the plot. If the range axis for this plot is - * null, then the method will return the parent plot's range axis (if there - * is a parent plot). - * - * @return The range axis (possibly {@code null}). - */ - public ValueAxis getRangeAxis() { - return getRangeAxis(0); - } - - /** - * Returns a range axis. - * - * @param index the axis index. - * - * @return The axis ({@code null} possible). - */ - public ValueAxis getRangeAxis(int index) { - ValueAxis result = this.rangeAxes.get(index); - if (result == null) { - Plot parent = getParent(); - if (parent instanceof CategoryPlot) { - CategoryPlot cp = (CategoryPlot) parent; - result = cp.getRangeAxis(index); - } - } - return result; - } - - /** - * Returns a map containing the range axes that are assigned to this plot. - * The map is unmodifiable. - * - * @return A map containing the domain axes that are assigned to the plot - * (never {@code null}). - * - * @since 1.5.4 - */ - public Map getRangeAxes() { - return Collections.unmodifiableMap(this.rangeAxes); - } - - /** - * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param axis the axis ({@code null} permitted). - */ - public void setRangeAxis(ValueAxis axis) { - setRangeAxis(0, axis); - } - - /** - * Sets a range axis and sends a {@link PlotChangeEvent} to all registered - * listeners. - * - * @param index the axis index. - * @param axis the axis. - */ - public void setRangeAxis(int index, ValueAxis axis) { - setRangeAxis(index, axis, true); - } - - /** - * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param index the axis index. - * @param axis the axis. - * @param notify notify listeners? - */ - public void setRangeAxis(int index, ValueAxis axis, boolean notify) { - ValueAxis existing = this.rangeAxes.get(index); - if (existing != null) { - existing.removeChangeListener(this); - } - if (axis != null) { - axis.setPlot(this); - } - this.rangeAxes.put(index, axis); - if (axis != null) { - axis.configure(); - axis.addChangeListener(this); - } - if (notify) { - fireChangeEvent(); - } - } - - /** - * Sets the range axes for this plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param axes the axes ({@code null} not permitted). - * - * @see #setDomainAxes(CategoryAxis[]) - */ - public void setRangeAxes(ValueAxis[] axes) { - for (int i = 0; i < axes.length; i++) { - setRangeAxis(i, axes[i], false); - } - fireChangeEvent(); - } - - /** - * Returns the index of the specified axis, or {@code -1} if the axis - * is not assigned to the plot. - * - * @param axis the axis ({@code null} not permitted). - * - * @return The axis index. - * - * @see #getRangeAxis(int) - * @see #getDomainAxisIndex(CategoryAxis) - */ - public int getRangeAxisIndex(ValueAxis axis) { - Args.nullNotPermitted(axis, "axis"); - int result = findRangeAxisIndex(axis); - if (result < 0) { // try the parent plot - Plot parent = getParent(); - if (parent instanceof CategoryPlot) { - CategoryPlot p = (CategoryPlot) parent; - result = p.getRangeAxisIndex(axis); - } - } - return result; - } - - private int findRangeAxisIndex(ValueAxis axis) { - for (Entry entry : this.rangeAxes.entrySet()) { - if (entry.getValue() == axis) { - return entry.getKey(); - } - } - return -1; - } - - /** - * Returns the range axis location. - * - * @return The location (never {@code null}). - */ - public AxisLocation getRangeAxisLocation() { - return getRangeAxisLocation(0); - } - - /** - * Returns the location for a range axis. - * - * @param index the axis index. - * - * @return The location. - * - * @see #setRangeAxisLocation(int, AxisLocation) - */ - public AxisLocation getRangeAxisLocation(int index) { - AxisLocation result = this.rangeAxisLocations.get(index); - if (result == null) { - result = AxisLocation.getOpposite(getRangeAxisLocation(0)); - } - return result; - } - - /** - * Sets the location of the range axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param location the location ({@code null} not permitted). - * - * @see #setRangeAxisLocation(AxisLocation, boolean) - * @see #setDomainAxisLocation(AxisLocation) - */ - public void setRangeAxisLocation(AxisLocation location) { - // defer argument checking... - setRangeAxisLocation(location, true); - } - - /** - * Sets the location of the range axis and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param location the location ({@code null} not permitted). - * @param notify notify listeners? - * - * @see #setDomainAxisLocation(AxisLocation, boolean) - */ - public void setRangeAxisLocation(AxisLocation location, boolean notify) { - setRangeAxisLocation(0, location, notify); - } - - /** - * Sets the location for a range axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param index the axis index. - * @param location the location. - * - * @see #getRangeAxisLocation(int) - * @see #setRangeAxisLocation(int, AxisLocation, boolean) - */ - public void setRangeAxisLocation(int index, AxisLocation location) { - setRangeAxisLocation(index, location, true); - } - - /** - * Sets the location for a range axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param index the axis index. - * @param location the location. - * @param notify notify listeners? - * - * @see #getRangeAxisLocation(int) - * @see #setDomainAxisLocation(int, AxisLocation, boolean) - */ - public void setRangeAxisLocation(int index, AxisLocation location, - boolean notify) { - if (index == 0 && location == null) { - throw new IllegalArgumentException( - "Null 'location' for index 0 not permitted."); - } - this.rangeAxisLocations.put(index, location); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the edge where the primary range axis is located. - * - * @return The edge (never {@code null}). - */ - public RectangleEdge getRangeAxisEdge() { - return getRangeAxisEdge(0); - } - - /** - * Returns the edge for a range axis. - * - * @param index the axis index. - * - * @return The edge. - */ - public RectangleEdge getRangeAxisEdge(int index) { - AxisLocation location = getRangeAxisLocation(index); - return Plot.resolveRangeAxisLocation(location, this.orientation); - } - - /** - * Returns the number of range axes. - * - * @return The axis count. - */ - public int getRangeAxisCount() { - return this.rangeAxes.size(); - } - - /** - * Clears the range axes from the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - */ - public void clearRangeAxes() { - for (ValueAxis yAxis : this.rangeAxes.values()) { - if (yAxis != null) { - yAxis.removeChangeListener(this); - } - } - this.rangeAxes.clear(); - fireChangeEvent(); - } - - /** - * Configures the range axes. - */ - public void configureRangeAxes() { - for (ValueAxis yAxis : this.rangeAxes.values()) { - if (yAxis != null) { - yAxis.configure(); - } - } - } - - /** - * Returns the primary dataset for the plot. - * - * @return The primary dataset (possibly {@code null}). - * - * @see #setDataset(CategoryDataset) - */ - public CategoryDataset getDataset() { - return getDataset(0); - } - - /** - * Returns the dataset with the given index, or {@code null} if there is - * no dataset. - * - * @param index the dataset index (must be >= 0). - * - * @return The dataset (possibly {@code null}). - * - * @see #setDataset(int, CategoryDataset) - */ - public CategoryDataset getDataset(int index) { - return this.datasets.get(index); - } - - /** - * Returns a map containing the datasets that are assigned to this plot. - * The map is unmodifiable. - * - * @return A map containing the datasets that are assigned to the plot - * (never {@code null}). - * - * @since 1.5.4 - */ - public Map getDatasets() { - return Collections.unmodifiableMap(this.datasets); - } - - /** - * Sets the dataset for the plot, replacing the existing dataset, if there - * is one. This method also calls the - * {@link #datasetChanged(DatasetChangeEvent)} method, which adjusts the - * axis ranges if necessary and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param dataset the dataset ({@code null} permitted). - * - * @see #getDataset() - */ - public void setDataset(CategoryDataset dataset) { - setDataset(0, dataset); - } - - /** - * Sets a dataset for the plot and sends a change notification to all - * registered listeners. - * - * @param index the dataset index (must be >= 0). - * @param dataset the dataset ({@code null} permitted). - * - * @see #getDataset(int) - */ - public void setDataset(int index, CategoryDataset dataset) { - CategoryDataset existing = this.datasets.get(index); - if (existing != null) { - existing.removeChangeListener(this); - } - this.datasets.put(index, dataset); - if (dataset != null) { - dataset.addChangeListener(this); - } - // send a dataset change event to self... - DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); - datasetChanged(event); - } - - /** - * Returns the number of datasets. - * - * @return The number of datasets. - */ - public int getDatasetCount() { - return this.datasets.size(); - } - - /** - * Returns the index of the specified dataset, or {@code -1} if the - * dataset does not belong to the plot. - * - * @param dataset the dataset ({@code null} not permitted). - * - * @return The index. - */ - public int indexOf(CategoryDataset dataset) { - for (Entry entry: this.datasets.entrySet()) { - if (entry.getValue() == dataset) { - return entry.getKey(); - } - } - return -1; - } - - /** - * Maps a dataset to a particular domain axis. - * - * @param index the dataset index (zero-based). - * @param axisIndex the axis index (zero-based). - * - * @see #getDomainAxisForDataset(int) - */ - public void mapDatasetToDomainAxis(int index, int axisIndex) { - List axisIndices = new ArrayList<>(1); - axisIndices.add(axisIndex); - mapDatasetToDomainAxes(index, axisIndices); - } - - /** - * Maps the specified dataset to the axes in the list. Note that the - * conversion of data values into Java2D space is always performed using - * the first axis in the list. - * - * @param index the dataset index (zero-based). - * @param axisIndices the axis indices ({@code null} permitted). - */ - public void mapDatasetToDomainAxes(int index, List axisIndices) { - Args.requireNonNegative(index, "index"); - checkAxisIndices(axisIndices); - this.datasetToDomainAxesMap.put(index, new ArrayList<>(axisIndices)); - // fake a dataset change event to update axes... - datasetChanged(new DatasetChangeEvent(this, getDataset(index))); - } - - /** - * This method is used to perform argument checking on the list of - * axis indices passed to mapDatasetToDomainAxes() and - * mapDatasetToRangeAxes(). - * - * @param indices the list of indices ({@code null} permitted). - */ - private void checkAxisIndices(List indices) { - // axisIndices can be: - // 1. null; - // 2. non-empty, containing only Integer objects that are unique. - if (indices == null) { - return; // OK - } - int count = indices.size(); - if (count == 0) { - throw new IllegalArgumentException("Empty list not permitted."); - } - HashSet set = new HashSet<>(); - for (int i = 0; i < count; i++) { - Integer item = indices.get(i); - if (set.contains(item)) { - throw new IllegalArgumentException("Indices must be unique."); - } - set.add(item); - } - } - - /** - * Returns the domain axis for a dataset. You can change the axis for a - * dataset using the {@link #mapDatasetToDomainAxis(int, int)} method. - * - * @param index the dataset index (must be >= 0). - * - * @return The domain axis. - * - * @see #mapDatasetToDomainAxis(int, int) - */ - public CategoryAxis getDomainAxisForDataset(int index) { - Args.requireNonNegative(index, "index"); - CategoryAxis axis; - List axisIndices = this.datasetToDomainAxesMap.get(index); - if (axisIndices != null) { - // the first axis in the list is used for data <--> Java2D - Integer axisIndex = axisIndices.get(0); - axis = getDomainAxis(axisIndex); - } else { - axis = getDomainAxis(0); - } - return axis; - } - - /** - * Maps a dataset to a particular range axis. - * - * @param index the dataset index (zero-based). - * @param axisIndex the axis index (zero-based). - * - * @see #getRangeAxisForDataset(int) - */ - public void mapDatasetToRangeAxis(int index, int axisIndex) { - List axisIndices = new ArrayList<>(1); - axisIndices.add(axisIndex); - mapDatasetToRangeAxes(index, axisIndices); - } - - /** - * Maps the specified dataset to the axes in the list. Note that the - * conversion of data values into Java2D space is always performed using - * the first axis in the list. - * - * @param index the dataset index (zero-based). - * @param axisIndices the axis indices ({@code null} permitted). - */ - public void mapDatasetToRangeAxes(int index, List axisIndices) { - Args.requireNonNegative(index, "index"); - checkAxisIndices(axisIndices); - this.datasetToRangeAxesMap.put(index, new ArrayList<>(axisIndices)); - // fake a dataset change event to update axes... - datasetChanged(new DatasetChangeEvent(this, getDataset(index))); - } - - /** - * Returns the range axis for a dataset. You can change the axis for a - * dataset using the {@link #mapDatasetToRangeAxis(int, int)} method. - * - * @param index the dataset index (must be >= 0). - * - * @return The range axis. - * - * @see #mapDatasetToRangeAxis(int, int) - */ - public ValueAxis getRangeAxisForDataset(int index) { - Args.requireNonNegative(index, "index"); - ValueAxis axis; - List axisIndices = this.datasetToRangeAxesMap.get(index); - if (axisIndices != null) { - // the first axis in the list is used for data <--> Java2D - axis = getRangeAxis(axisIndices.get(0)); - } else { - axis = getRangeAxis(0); - } - return axis; - } - - /** - * Returns the number of renderer slots for this plot. - * - * @return The number of renderer slots. - */ - public int getRendererCount() { - return this.renderers.size(); - } - - /** - * Returns a reference to the renderer for the plot. - * - * @return The renderer. - * - * @see #setRenderer(CategoryItemRenderer) - */ - public CategoryItemRenderer getRenderer() { - return getRenderer(0); - } - - /** - * Returns the renderer at the given index. - * - * @param index the renderer index. - * - * @return The renderer (possibly {@code null}). - * - * @see #setRenderer(int, CategoryItemRenderer) - */ - public CategoryItemRenderer getRenderer(int index) { - CategoryItemRenderer renderer = this.renderers.get(index); - if (renderer == null) { - return this.renderers.get(0); - } - return renderer; - } - - /** - * Returns a map containing the renderers that are assigned to this plot. - * The map is unmodifiable. - * - * @return A map containing the renderers that are assigned to the plot - * (never {@code null}). - * - * @since 1.5.4 - */ - public Map getRenderers() { - return Collections.unmodifiableMap(this.renderers); - } - - /** - * Sets the renderer at index 0 (sometimes referred to as the "primary" - * renderer) and sends a change event to all registered listeners. - * - * @param renderer the renderer ({@code null} permitted. - * - * @see #getRenderer() - */ - public void setRenderer(CategoryItemRenderer renderer) { - setRenderer(0, renderer, true); - } - - /** - * Sets the renderer at index 0 (sometimes referred to as the "primary" - * renderer) and, if requested, sends a change event to all registered - * listeners. - *

- * You can set the renderer to {@code null}, but this is not - * recommended because: - *

- * - * @param renderer the renderer ({@code null} permitted). - * @param notify notify listeners? - * - * @see #getRenderer() - */ - public void setRenderer(CategoryItemRenderer renderer, boolean notify) { - setRenderer(0, renderer, notify); - } - - /** - * Sets the renderer to use for the dataset with the specified index and - * sends a change event to all registered listeners. Note that each - * dataset should have its own renderer, you should not use one renderer - * for multiple datasets. - * - * @param index the index. - * @param renderer the renderer ({@code null} permitted). - * - * @see #getRenderer(int) - * @see #setRenderer(int, CategoryItemRenderer, boolean) - */ - public void setRenderer(int index, CategoryItemRenderer renderer) { - setRenderer(index, renderer, true); - } - - /** - * Sets the renderer to use for the dataset with the specified index and, - * if requested, sends a change event to all registered listeners. Note - * that each dataset should have its own renderer, you should not use one - * renderer for multiple datasets. - * - * @param index the index. - * @param renderer the renderer ({@code null} permitted). - * @param notify notify listeners? - * - * @see #getRenderer(int) - */ - public void setRenderer(int index, CategoryItemRenderer renderer, - boolean notify) { - CategoryItemRenderer existing = this.renderers.get(index); - if (existing != null) { - existing.removeChangeListener(this); - } - this.renderers.put(index, renderer); - if (renderer != null) { - renderer.setPlot(this); - renderer.addChangeListener(this); - } - configureDomainAxes(); - configureRangeAxes(); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Sets the renderers for this plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param renderers the renderers. - */ - public void setRenderers(CategoryItemRenderer[] renderers) { - for (int i = 0; i < renderers.length; i++) { - setRenderer(i, renderers[i], false); - } - fireChangeEvent(); - } - - /** - * Returns the renderer for the specified dataset. If the dataset doesn't - * belong to the plot, this method will return {@code null}. - * - * @param dataset the dataset ({@code null} permitted). - * - * @return The renderer (possibly {@code null}). - */ - public CategoryItemRenderer getRendererForDataset(CategoryDataset dataset) { - int datasetIndex = indexOf(dataset); - if (datasetIndex < 0) { - return null; - } - CategoryItemRenderer renderer = this.renderers.get(datasetIndex); - if (renderer == null) { - return getRenderer(); - } - return renderer; - } - - /** - * Returns the index of the specified renderer, or {@code -1} if the - * renderer is not assigned to this plot. - * - * @param renderer the renderer ({@code null} permitted). - * - * @return The renderer index. - */ - public int getIndexOf(CategoryItemRenderer renderer) { - for (Entry entry - : this.renderers.entrySet()) { - if (entry.getValue() == renderer) { - return entry.getKey(); - } - } - return -1; - } - - /** - * Returns the dataset rendering order. - * - * @return The order (never {@code null}). - * - * @see #setDatasetRenderingOrder(DatasetRenderingOrder) - */ - public DatasetRenderingOrder getDatasetRenderingOrder() { - return this.renderingOrder; - } - - /** - * Sets the rendering order and sends a {@link PlotChangeEvent} to all - * registered listeners. By default, the plot renders the primary dataset - * last (so that the primary dataset overlays the secondary datasets). You - * can reverse this if you want to. - * - * @param order the rendering order ({@code null} not permitted). - * - * @see #getDatasetRenderingOrder() - */ - public void setDatasetRenderingOrder(DatasetRenderingOrder order) { - Args.nullNotPermitted(order, "order"); - this.renderingOrder = order; - fireChangeEvent(); - } - - /** - * Returns the order in which the columns are rendered. The default value - * is {@code SortOrder.ASCENDING}. - * - * @return The column rendering order (never {@code null}). - * - * @see #setColumnRenderingOrder(SortOrder) - */ - public SortOrder getColumnRenderingOrder() { - return this.columnRenderingOrder; - } - - /** - * Sets the column order in which the items in each dataset should be - * rendered and sends a {@link PlotChangeEvent} to all registered - * listeners. Note that this affects the order in which items are drawn, - * NOT their position in the chart. - * - * @param order the order ({@code null} not permitted). - * - * @see #getColumnRenderingOrder() - * @see #setRowRenderingOrder(SortOrder) - */ - public void setColumnRenderingOrder(SortOrder order) { - Args.nullNotPermitted(order, "order"); - this.columnRenderingOrder = order; - fireChangeEvent(); - } - - /** - * Returns the order in which the rows should be rendered. The default - * value is {@code SortOrder.ASCENDING}. - * - * @return The order (never {@code null}). - * - * @see #setRowRenderingOrder(SortOrder) - */ - public SortOrder getRowRenderingOrder() { - return this.rowRenderingOrder; - } - - /** - * Sets the row order in which the items in each dataset should be - * rendered and sends a {@link PlotChangeEvent} to all registered - * listeners. Note that this affects the order in which items are drawn, - * NOT their position in the chart. - * - * @param order the order ({@code null} not permitted). - * - * @see #getRowRenderingOrder() - * @see #setColumnRenderingOrder(SortOrder) - */ - public void setRowRenderingOrder(SortOrder order) { - Args.nullNotPermitted(order, "order"); - this.rowRenderingOrder = order; - fireChangeEvent(); - } - - /** - * Returns the flag that controls whether the domain grid-lines are visible. - * - * @return The {@code true} or {@code false}. - * - * @see #setDomainGridlinesVisible(boolean) - */ - public boolean isDomainGridlinesVisible() { - return this.domainGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not grid-lines are drawn against - * the domain axis. - *

- * If the flag value changes, a {@link PlotChangeEvent} is sent to all - * registered listeners. - * - * @param visible the new value of the flag. - * - * @see #isDomainGridlinesVisible() - */ - public void setDomainGridlinesVisible(boolean visible) { - if (this.domainGridlinesVisible != visible) { - this.domainGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the position used for the domain gridlines. - * - * @return The gridline position (never {@code null}). - * - * @see #setDomainGridlinePosition(CategoryAnchor) - */ - public CategoryAnchor getDomainGridlinePosition() { - return this.domainGridlinePosition; - } - - /** - * Sets the position used for the domain gridlines and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param position the position ({@code null} not permitted). - * - * @see #getDomainGridlinePosition() - */ - public void setDomainGridlinePosition(CategoryAnchor position) { - Args.nullNotPermitted(position, "position"); - this.domainGridlinePosition = position; - fireChangeEvent(); - } - - /** - * Returns the stroke used to draw grid-lines against the domain axis. - * - * @return The stroke (never {@code null}). - * - * @see #setDomainGridlineStroke(Stroke) - */ - public Stroke getDomainGridlineStroke() { - return this.domainGridlineStroke; - } - - /** - * Sets the stroke used to draw grid-lines against the domain axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getDomainGridlineStroke() - */ - public void setDomainGridlineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.domainGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint used to draw grid-lines against the domain axis. - * - * @return The paint (never {@code null}). - * - * @see #setDomainGridlinePaint(Paint) - */ - public Paint getDomainGridlinePaint() { - return this.domainGridlinePaint; - } - - /** - * Sets the paint used to draw the grid-lines (if any) against the domain - * axis and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getDomainGridlinePaint() - */ - public void setDomainGridlinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.domainGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns a flag that controls whether or not a zero baseline is - * displayed for the range axis. - * - * @return A boolean. - * - * @see #setRangeZeroBaselineVisible(boolean) - */ - public boolean isRangeZeroBaselineVisible() { - return this.rangeZeroBaselineVisible; - } - - /** - * Sets the flag that controls whether or not the zero baseline is - * displayed for the range axis, and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param visible the flag. - * - * @see #isRangeZeroBaselineVisible() - */ - public void setRangeZeroBaselineVisible(boolean visible) { - this.rangeZeroBaselineVisible = visible; - fireChangeEvent(); - } - - /** - * Returns the stroke used for the zero baseline against the range axis. - * - * @return The stroke (never {@code null}). - * - * @see #setRangeZeroBaselineStroke(Stroke) - */ - public Stroke getRangeZeroBaselineStroke() { - return this.rangeZeroBaselineStroke; - } - - /** - * Sets the stroke for the zero baseline for the range axis, - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getRangeZeroBaselineStroke() - */ - public void setRangeZeroBaselineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.rangeZeroBaselineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the zero baseline (if any) plotted against the - * range axis. - * - * @return The paint (never {@code null}). - * - * @see #setRangeZeroBaselinePaint(Paint) - */ - public Paint getRangeZeroBaselinePaint() { - return this.rangeZeroBaselinePaint; - } - - /** - * Sets the paint for the zero baseline plotted against the range axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getRangeZeroBaselinePaint() - */ - public void setRangeZeroBaselinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.rangeZeroBaselinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns the flag that controls whether the range grid-lines are visible. - * - * @return The flag. - * - * @see #setRangeGridlinesVisible(boolean) - */ - public boolean isRangeGridlinesVisible() { - return this.rangeGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not grid-lines are drawn against - * the range axis. If the flag changes value, a {@link PlotChangeEvent} is - * sent to all registered listeners. - * - * @param visible the new value of the flag. - * - * @see #isRangeGridlinesVisible() - */ - public void setRangeGridlinesVisible(boolean visible) { - if (this.rangeGridlinesVisible != visible) { - this.rangeGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the stroke used to draw the grid-lines against the range axis. - * - * @return The stroke (never {@code null}). - * - * @see #setRangeGridlineStroke(Stroke) - */ - public Stroke getRangeGridlineStroke() { - return this.rangeGridlineStroke; - } - - /** - * Sets the stroke used to draw the grid-lines against the range axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getRangeGridlineStroke() - */ - public void setRangeGridlineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.rangeGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint used to draw the grid-lines against the range axis. - * - * @return The paint (never {@code null}). - * - * @see #setRangeGridlinePaint(Paint) - */ - public Paint getRangeGridlinePaint() { - return this.rangeGridlinePaint; - } - - /** - * Sets the paint used to draw the grid lines against the range axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getRangeGridlinePaint() - */ - public void setRangeGridlinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.rangeGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns {@code true} if the range axis minor grid is visible, and - * {@code false} otherwise. - * - * @return A boolean. - * - * @see #setRangeMinorGridlinesVisible(boolean) - */ - public boolean isRangeMinorGridlinesVisible() { - return this.rangeMinorGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not the range axis minor grid - * lines are visible. - *

- * If the flag value is changed, a {@link PlotChangeEvent} is sent to all - * registered listeners. - * - * @param visible the new value of the flag. - * - * @see #isRangeMinorGridlinesVisible() - */ - public void setRangeMinorGridlinesVisible(boolean visible) { - if (this.rangeMinorGridlinesVisible != visible) { - this.rangeMinorGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the stroke for the minor grid lines (if any) plotted against the - * range axis. - * - * @return The stroke (never {@code null}). - * - * @see #setRangeMinorGridlineStroke(Stroke) - */ - public Stroke getRangeMinorGridlineStroke() { - return this.rangeMinorGridlineStroke; - } - - /** - * Sets the stroke for the minor grid lines plotted against the range axis, - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getRangeMinorGridlineStroke() - */ - public void setRangeMinorGridlineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.rangeMinorGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the minor grid lines (if any) plotted against the - * range axis. - * - * @return The paint (never {@code null}). - * - * @see #setRangeMinorGridlinePaint(Paint) - */ - public Paint getRangeMinorGridlinePaint() { - return this.rangeMinorGridlinePaint; - } - - /** - * Sets the paint for the minor grid lines plotted against the range axis - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getRangeMinorGridlinePaint() - */ - public void setRangeMinorGridlinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.rangeMinorGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns the fixed legend items, if any. - * - * @return The legend items (possibly {@code null}). - * - * @see #setFixedLegendItems(LegendItemCollection) - */ - public LegendItemCollection getFixedLegendItems() { - return this.fixedLegendItems; - } - - /** - * Sets the fixed legend items for the plot. Leave this set to - * {@code null} if you prefer the legend items to be created - * automatically. - * - * @param items the legend items ({@code null} permitted). - * - * @see #getFixedLegendItems() - */ - public void setFixedLegendItems(LegendItemCollection items) { - this.fixedLegendItems = items; - fireChangeEvent(); - } - - /** - * Returns the legend items for the plot. By default, this method creates - * a legend item for each series in each of the datasets. You can change - * this behaviour by overriding this method. - * - * @return The legend items. - */ - @Override - public LegendItemCollection getLegendItems() { - if (this.fixedLegendItems != null) { - return this.fixedLegendItems; - } - LegendItemCollection result = new LegendItemCollection(); - // get the legend items for the datasets... - for (CategoryDataset dataset: this.datasets.values()) { - if (dataset != null) { - int datasetIndex = indexOf(dataset); - CategoryItemRenderer renderer = getRenderer(datasetIndex); - if (renderer != null) { - result.addAll(renderer.getLegendItems()); - } - } - } - return result; - } - - /** - * Handles a 'click' on the plot by updating the anchor value. - * - * @param x x-coordinate of the click (in Java2D space). - * @param y y-coordinate of the click (in Java2D space). - * @param info information about the plot's dimensions. - * - */ - @Override - public void handleClick(int x, int y, PlotRenderingInfo info) { - - Rectangle2D dataArea = info.getDataArea(); - if (dataArea.contains(x, y)) { - // set the anchor value for the range axis... - double java2D = 0.0; - if (this.orientation == PlotOrientation.HORIZONTAL) { - java2D = x; - } else if (this.orientation == PlotOrientation.VERTICAL) { - java2D = y; - } - RectangleEdge edge = Plot.resolveRangeAxisLocation( - getRangeAxisLocation(), this.orientation); - double value = getRangeAxis().java2DToValue( - java2D, info.getDataArea(), edge); - setAnchorValue(value); - setRangeCrosshairValue(value); - } - - } - - /** - * Zooms (in or out) on the plot's value axis. - *

- * If the value 0.0 is passed in as the zoom percent, the auto-range - * calculation for the axis is restored (which sets the range to include - * the minimum and maximum data values, thus displaying all the data). - * - * @param percent the zoom amount. - */ - @Override - public void zoom(double percent) { - if (percent > 0.0) { - double range = getRangeAxis().getRange().getLength(); - double scaledRange = range * percent; - getRangeAxis().setRange(this.anchorValue - scaledRange / 2.0, - this.anchorValue + scaledRange / 2.0); - } - else { - getRangeAxis().setAutoRange(true); - } - } - - /** - * Receives notification of a change to an {@link Annotation} added to - * this plot. - * - * @param event information about the event (not used here). - */ - @Override - public void annotationChanged(AnnotationChangeEvent event) { - if (getParent() != null) { - getParent().annotationChanged(event); - } else { - PlotChangeEvent e = new PlotChangeEvent(this); - notifyListeners(e); - } - } - - /** - * Receives notification of a change to the plot's dataset. - *

- * The range axis bounds will be recalculated if necessary. - * - * @param event information about the event (not used here). - */ - @Override - public void datasetChanged(DatasetChangeEvent event) { - for (ValueAxis yAxis : this.rangeAxes.values()) { - if (yAxis != null) { - yAxis.configure(); - } - } - if (getParent() != null) { - getParent().datasetChanged(event); - } else { - PlotChangeEvent e = new PlotChangeEvent(this); - e.setType(ChartChangeEventType.DATASET_UPDATED); - notifyListeners(e); - } - - } - - /** - * Receives notification of a renderer change event. - * - * @param event the event. - */ - @Override - public void rendererChanged(RendererChangeEvent event) { - Plot parent = getParent(); - if (parent != null) { - if (parent instanceof RendererChangeListener) { - RendererChangeListener rcl = (RendererChangeListener) parent; - rcl.rendererChanged(event); - } else { - // this should never happen with the existing code, but throw - // an exception in case future changes make it possible... - throw new RuntimeException( - "The renderer has changed and I don't know what to do!"); - } - } else { - configureRangeAxes(); - PlotChangeEvent e = new PlotChangeEvent(this); - notifyListeners(e); - } - } - - /** - * Adds a marker for display (in the foreground) against the domain axis and - * sends a {@link PlotChangeEvent} to all registered listeners. Typically a - * marker will be drawn by the renderer as a line perpendicular to the - * domain axis, however this is entirely up to the renderer. - * - * @param marker the marker ({@code null} not permitted). - * - * @see #removeDomainMarker(Marker) - */ - public void addDomainMarker(CategoryMarker marker) { - addDomainMarker(marker, Layer.FOREGROUND); - } - - /** - * Adds a marker for display against the domain axis and sends a - * {@link PlotChangeEvent} to all registered listeners. Typically a marker - * will be drawn by the renderer as a line perpendicular to the domain - * axis, however this is entirely up to the renderer. - * - * @param marker the marker ({@code null} not permitted). - * @param layer the layer (foreground or background) ({@code null} - * not permitted). - * - * @see #removeDomainMarker(Marker, Layer) - */ - public void addDomainMarker(CategoryMarker marker, Layer layer) { - addDomainMarker(0, marker, layer); - } - - /** - * Adds a marker for display by a particular renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to a domain axis, however this is entirely up to the renderer. - * - * @param index the renderer index. - * @param marker the marker ({@code null} not permitted). - * @param layer the layer ({@code null} not permitted). - * - * @see #removeDomainMarker(int, Marker, Layer) - */ - public void addDomainMarker(int index, CategoryMarker marker, Layer layer) { - addDomainMarker(index, marker, layer, true); - } - - /** - * Adds a marker for display by a particular renderer and, if requested, - * sends a {@link PlotChangeEvent} to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to a domain axis, however this is entirely up to the renderer. - * - * @param index the renderer index. - * @param marker the marker ({@code null} not permitted). - * @param layer the layer ({@code null} not permitted). - * @param notify notify listeners? - * - * @see #removeDomainMarker(int, Marker, Layer, boolean) - */ - public void addDomainMarker(int index, CategoryMarker marker, Layer layer, - boolean notify) { - Args.nullNotPermitted(marker, "marker"); - Args.nullNotPermitted(layer, "layer"); - Collection markers; - if (layer == Layer.FOREGROUND) { - markers = this.foregroundDomainMarkers.get(index); - if (markers == null) { - markers = new java.util.ArrayList(); - this.foregroundDomainMarkers.put(index, markers); - } - markers.add(marker); - } else if (layer == Layer.BACKGROUND) { - markers = this.backgroundDomainMarkers.get(index); - if (markers == null) { - markers = new java.util.ArrayList(); - this.backgroundDomainMarkers.put(index, markers); - } - markers.add(marker); - } - marker.addChangeListener(this); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Clears all the domain markers for the plot and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @see #clearRangeMarkers() - */ - public void clearDomainMarkers() { - if (this.backgroundDomainMarkers != null) { - Set keys = this.backgroundDomainMarkers.keySet(); - Iterator iterator = keys.iterator(); - while (iterator.hasNext()) { - Integer key = (Integer) iterator.next(); - clearDomainMarkers(key); - } - this.backgroundDomainMarkers.clear(); - } - if (this.foregroundDomainMarkers != null) { - Set keys = this.foregroundDomainMarkers.keySet(); - Iterator iterator = keys.iterator(); - while (iterator.hasNext()) { - Integer key = (Integer) iterator.next(); - clearDomainMarkers(key); - } - this.foregroundDomainMarkers.clear(); - } - fireChangeEvent(); - } - - /** - * Returns the list of domain markers (read only) for the specified layer. - * - * @param layer the layer (foreground or background). - * - * @return The list of domain markers. - */ - public Collection getDomainMarkers(Layer layer) { - return getDomainMarkers(0, layer); - } - - /** - * Returns a collection of domain markers for a particular renderer and - * layer. - * - * @param index the renderer index. - * @param layer the layer. - * - * @return A collection of markers (possibly {@code null}). - */ - public Collection getDomainMarkers(int index, Layer layer) { - Collection result = null; - Integer key = index; - if (layer == Layer.FOREGROUND) { - result = this.foregroundDomainMarkers.get(key); - } - else if (layer == Layer.BACKGROUND) { - result = this.backgroundDomainMarkers.get(key); - } - if (result != null) { - result = Collections.unmodifiableCollection(result); - } - return result; - } - - /** - * Clears all the domain markers for the specified renderer. - * - * @param index the renderer index. - * - * @see #clearRangeMarkers(int) - */ - public void clearDomainMarkers(int index) { - Integer key = index; - if (this.backgroundDomainMarkers != null) { - Collection markers = this.backgroundDomainMarkers.get(key); - if (markers != null) { - Iterator iterator = markers.iterator(); - while (iterator.hasNext()) { - Marker m = (Marker) iterator.next(); - m.removeChangeListener(this); - } - markers.clear(); - } - } - if (this.foregroundDomainMarkers != null) { - Collection markers = this.foregroundDomainMarkers.get(key); - if (markers != null) { - Iterator iterator = markers.iterator(); - while (iterator.hasNext()) { - Marker m = (Marker) iterator.next(); - m.removeChangeListener(this); - } - markers.clear(); - } - } - fireChangeEvent(); - } - - /** - * Removes a marker for the domain axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param marker the marker. - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeDomainMarker(Marker marker) { - return removeDomainMarker(marker, Layer.FOREGROUND); - } - - /** - * Removes a marker for the domain axis in the specified layer and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param marker the marker ({@code null} not permitted). - * @param layer the layer (foreground or background). - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeDomainMarker(Marker marker, Layer layer) { - return removeDomainMarker(0, marker, layer); - } - - /** - * Removes a marker for a specific dataset/renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the dataset/renderer index. - * @param marker the marker. - * @param layer the layer (foreground or background). - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeDomainMarker(int index, Marker marker, Layer layer) { - return removeDomainMarker(index, marker, layer, true); - } - - /** - * Removes a marker for a specific dataset/renderer and, if requested, - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param index the dataset/renderer index. - * @param marker the marker. - * @param layer the layer (foreground or background). - * @param notify notify listeners? - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeDomainMarker(int index, Marker marker, Layer layer, - boolean notify) { - ArrayList markers; - if (layer == Layer.FOREGROUND) { - markers = (ArrayList) this.foregroundDomainMarkers.get(index); - } else { - markers = (ArrayList) this.backgroundDomainMarkers.get(index); - } - if (markers == null) { - return false; - } - boolean removed = markers.remove(marker); - if (removed && notify) { - fireChangeEvent(); - } - return removed; - } - - /** - * Adds a marker for display (in the foreground) against the range axis and - * sends a {@link PlotChangeEvent} to all registered listeners. Typically a - * marker will be drawn by the renderer as a line perpendicular to the - * range axis, however this is entirely up to the renderer. - * - * @param marker the marker ({@code null} not permitted). - * - * @see #removeRangeMarker(Marker) - */ - public void addRangeMarker(Marker marker) { - addRangeMarker(marker, Layer.FOREGROUND); - } - - /** - * Adds a marker for display against the range axis and sends a - * {@link PlotChangeEvent} to all registered listeners. Typically a marker - * will be drawn by the renderer as a line perpendicular to the range axis, - * however this is entirely up to the renderer. - * - * @param marker the marker ({@code null} not permitted). - * @param layer the layer (foreground or background) ({@code null} - * not permitted). - * - * @see #removeRangeMarker(Marker, Layer) - */ - public void addRangeMarker(Marker marker, Layer layer) { - addRangeMarker(0, marker, layer); - } - - /** - * Adds a marker for display by a particular renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to a range axis, however this is entirely up to the renderer. - * - * @param index the renderer index. - * @param marker the marker. - * @param layer the layer. - * - * @see #removeRangeMarker(int, Marker, Layer) - */ - public void addRangeMarker(int index, Marker marker, Layer layer) { - addRangeMarker(index, marker, layer, true); - } - - /** - * Adds a marker for display by a particular renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to a range axis, however this is entirely up to the renderer. - * - * @param index the renderer index. - * @param marker the marker. - * @param layer the layer. - * @param notify notify listeners? - * - * @see #removeRangeMarker(int, Marker, Layer, boolean) - */ - public void addRangeMarker(int index, Marker marker, Layer layer, - boolean notify) { - Collection markers; - if (layer == Layer.FOREGROUND) { - markers = this.foregroundRangeMarkers.get(index); - if (markers == null) { - markers = new java.util.ArrayList(); - this.foregroundRangeMarkers.put(index, markers); - } - markers.add(marker); - } else if (layer == Layer.BACKGROUND) { - markers = this.backgroundRangeMarkers.get(index); - if (markers == null) { - markers = new java.util.ArrayList(); - this.backgroundRangeMarkers.put(index, markers); - } - markers.add(marker); - } - marker.addChangeListener(this); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Clears all the range markers for the plot and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @see #clearDomainMarkers() - */ - public void clearRangeMarkers() { - if (this.backgroundRangeMarkers != null) { - Set keys = this.backgroundRangeMarkers.keySet(); - Iterator iterator = keys.iterator(); - while (iterator.hasNext()) { - Integer key = (Integer) iterator.next(); - clearRangeMarkers(key); - } - this.backgroundRangeMarkers.clear(); - } - if (this.foregroundRangeMarkers != null) { - Set keys = this.foregroundRangeMarkers.keySet(); - Iterator iterator = keys.iterator(); - while (iterator.hasNext()) { - Integer key = (Integer) iterator.next(); - clearRangeMarkers(key); - } - this.foregroundRangeMarkers.clear(); - } - fireChangeEvent(); - } - - /** - * Returns the list of range markers (read only) for the specified layer. - * - * @param layer the layer (foreground or background). - * - * @return The list of range markers. - * - * @see #getRangeMarkers(int, Layer) - */ - public Collection getRangeMarkers(Layer layer) { - return getRangeMarkers(0, layer); - } - - /** - * Returns a collection of range markers for a particular renderer and - * layer. - * - * @param index the renderer index. - * @param layer the layer. - * - * @return A collection of markers (possibly {@code null}). - */ - public Collection getRangeMarkers(int index, Layer layer) { - Collection result = null; - if (layer == Layer.FOREGROUND) { - result = this.foregroundRangeMarkers.get(index); - } - else if (layer == Layer.BACKGROUND) { - result = this.backgroundRangeMarkers.get(index); - } - if (result != null) { - result = Collections.unmodifiableCollection(result); - } - return result; - } - - /** - * Clears all the range markers for the specified renderer. - * - * @param index the renderer index. - * - * @see #clearDomainMarkers(int) - */ - public void clearRangeMarkers(int index) { - Integer key = index; - if (this.backgroundRangeMarkers != null) { - Collection markers = this.backgroundRangeMarkers.get(key); - if (markers != null) { - Iterator iterator = markers.iterator(); - while (iterator.hasNext()) { - Marker m = (Marker) iterator.next(); - m.removeChangeListener(this); - } - markers.clear(); - } - } - if (this.foregroundRangeMarkers != null) { - Collection markers = this.foregroundRangeMarkers.get(key); - if (markers != null) { - Iterator iterator = markers.iterator(); - while (iterator.hasNext()) { - Marker m = (Marker) iterator.next(); - m.removeChangeListener(this); - } - markers.clear(); - } - } - fireChangeEvent(); - } - - /** - * Removes a marker for the range axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param marker the marker. - * - * @return A boolean indicating whether or not the marker was actually - * removed. - * - * @see #addRangeMarker(Marker) - */ - public boolean removeRangeMarker(Marker marker) { - return removeRangeMarker(marker, Layer.FOREGROUND); - } - - /** - * Removes a marker for the range axis in the specified layer and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param marker the marker ({@code null} not permitted). - * @param layer the layer (foreground or background). - * - * @return A boolean indicating whether or not the marker was actually - * removed. - * - * @see #addRangeMarker(Marker, Layer) - */ - public boolean removeRangeMarker(Marker marker, Layer layer) { - return removeRangeMarker(0, marker, layer); - } - - /** - * Removes a marker for a specific dataset/renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the dataset/renderer index. - * @param marker the marker. - * @param layer the layer (foreground or background). - * - * @return A boolean indicating whether or not the marker was actually - * removed. - * - * @see #addRangeMarker(int, Marker, Layer) - */ - public boolean removeRangeMarker(int index, Marker marker, Layer layer) { - return removeRangeMarker(index, marker, layer, true); - } - - /** - * Removes a marker for a specific dataset/renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the dataset/renderer index. - * @param marker the marker. - * @param layer the layer (foreground or background). - * @param notify notify listeners. - * - * @return A boolean indicating whether or not the marker was actually - * removed. - * - * @see #addRangeMarker(int, Marker, Layer, boolean) - */ - public boolean removeRangeMarker(int index, Marker marker, Layer layer, - boolean notify) { - Args.nullNotPermitted(marker, "marker"); - ArrayList markers; - if (layer == Layer.FOREGROUND) { - markers = (ArrayList) this.foregroundRangeMarkers.get(index); - } else { - markers = (ArrayList) this.backgroundRangeMarkers.get(index); - } - if (markers == null) { - return false; - } - boolean removed = markers.remove(marker); - if (removed && notify) { - fireChangeEvent(); - } - return removed; - } - - /** - * Returns the flag that controls whether or not the domain crosshair is - * displayed by the plot. - * - * @return A boolean. - * - * @see #setDomainCrosshairVisible(boolean) - */ - public boolean isDomainCrosshairVisible() { - return this.domainCrosshairVisible; - } - - /** - * Sets the flag that controls whether or not the domain crosshair is - * displayed by the plot, and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param flag the new flag value. - * - * @see #isDomainCrosshairVisible() - * @see #setRangeCrosshairVisible(boolean) - */ - public void setDomainCrosshairVisible(boolean flag) { - if (this.domainCrosshairVisible != flag) { - this.domainCrosshairVisible = flag; - fireChangeEvent(); - } - } - - /** - * Returns the row key for the domain crosshair. - * - * @return The row key. - */ - public Comparable getDomainCrosshairRowKey() { - return this.domainCrosshairRowKey; - } - - /** - * Sets the row key for the domain crosshair and sends a - * {PlotChangeEvent} to all registered listeners. - * - * @param key the key. - */ - public void setDomainCrosshairRowKey(Comparable key) { - setDomainCrosshairRowKey(key, true); - } - - /** - * Sets the row key for the domain crosshair and, if requested, sends a - * {PlotChangeEvent} to all registered listeners. - * - * @param key the key. - * @param notify notify listeners? - */ - public void setDomainCrosshairRowKey(Comparable key, boolean notify) { - this.domainCrosshairRowKey = key; - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the column key for the domain crosshair. - * - * @return The column key. - */ - public Comparable getDomainCrosshairColumnKey() { - return this.domainCrosshairColumnKey; - } - - /** - * Sets the column key for the domain crosshair and sends - * a {@link PlotChangeEvent} to all registered listeners. - * - * @param key the key. - */ - public void setDomainCrosshairColumnKey(Comparable key) { - setDomainCrosshairColumnKey(key, true); - } - - /** - * Sets the column key for the domain crosshair and, if requested, sends - * a {@link PlotChangeEvent} to all registered listeners. - * - * @param key the key. - * @param notify notify listeners? - */ - public void setDomainCrosshairColumnKey(Comparable key, boolean notify) { - this.domainCrosshairColumnKey = key; - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the dataset index for the crosshair. - * - * @return The dataset index. - */ - public int getCrosshairDatasetIndex() { - return this.crosshairDatasetIndex; - } - - /** - * Sets the dataset index for the crosshair and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the index. - */ - public void setCrosshairDatasetIndex(int index) { - setCrosshairDatasetIndex(index, true); - } - - /** - * Sets the dataset index for the crosshair and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the index. - * @param notify notify listeners? - */ - public void setCrosshairDatasetIndex(int index, boolean notify) { - this.crosshairDatasetIndex = index; - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the paint used to draw the domain crosshair. - * - * @return The paint (never {@code null}). - * - * @see #setDomainCrosshairPaint(Paint) - * @see #getDomainCrosshairStroke() - */ - public Paint getDomainCrosshairPaint() { - return this.domainCrosshairPaint; - } - - /** - * Sets the paint used to draw the domain crosshair. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getDomainCrosshairPaint() - */ - public void setDomainCrosshairPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.domainCrosshairPaint = paint; - fireChangeEvent(); - } - - /** - * Returns the stroke used to draw the domain crosshair. - * - * @return The stroke (never {@code null}). - * - * @see #setDomainCrosshairStroke(Stroke) - * @see #getDomainCrosshairPaint() - */ - public Stroke getDomainCrosshairStroke() { - return this.domainCrosshairStroke; - } - - /** - * Sets the stroke used to draw the domain crosshair, and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getDomainCrosshairStroke() - */ - public void setDomainCrosshairStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.domainCrosshairStroke = stroke; - } - - /** - * Returns a flag indicating whether or not the range crosshair is visible. - * - * @return The flag. - * - * @see #setRangeCrosshairVisible(boolean) - */ - public boolean isRangeCrosshairVisible() { - return this.rangeCrosshairVisible; - } - - /** - * Sets the flag indicating whether or not the range crosshair is visible. - * - * @param flag the new value of the flag. - * - * @see #isRangeCrosshairVisible() - */ - public void setRangeCrosshairVisible(boolean flag) { - if (this.rangeCrosshairVisible != flag) { - this.rangeCrosshairVisible = flag; - fireChangeEvent(); - } - } - - /** - * Returns a flag indicating whether or not the crosshair should "lock-on" - * to actual data values. - * - * @return The flag. - * - * @see #setRangeCrosshairLockedOnData(boolean) - */ - public boolean isRangeCrosshairLockedOnData() { - return this.rangeCrosshairLockedOnData; - } - - /** - * Sets the flag indicating whether or not the range crosshair should - * "lock-on" to actual data values, and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param flag the flag. - * - * @see #isRangeCrosshairLockedOnData() - */ - public void setRangeCrosshairLockedOnData(boolean flag) { - if (this.rangeCrosshairLockedOnData != flag) { - this.rangeCrosshairLockedOnData = flag; - fireChangeEvent(); - } - } - - /** - * Returns the range crosshair value. - * - * @return The value. - * - * @see #setRangeCrosshairValue(double) - */ - public double getRangeCrosshairValue() { - return this.rangeCrosshairValue; - } - - /** - * Sets the range crosshair value and, if the crosshair is visible, sends - * a {@link PlotChangeEvent} to all registered listeners. - * - * @param value the new value. - * - * @see #getRangeCrosshairValue() - */ - public void setRangeCrosshairValue(double value) { - setRangeCrosshairValue(value, true); - } - - /** - * Sets the range crosshair value and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners (but only if the - * crosshair is visible). - * - * @param value the new value. - * @param notify a flag that controls whether or not listeners are - * notified. - * - * @see #getRangeCrosshairValue() - */ - public void setRangeCrosshairValue(double value, boolean notify) { - this.rangeCrosshairValue = value; - if (isRangeCrosshairVisible() && notify) { - fireChangeEvent(); - } - } - - /** - * Returns the pen-style ({@code Stroke}) used to draw the crosshair - * (if visible). - * - * @return The crosshair stroke (never {@code null}). - * - * @see #setRangeCrosshairStroke(Stroke) - * @see #isRangeCrosshairVisible() - * @see #getRangeCrosshairPaint() - */ - public Stroke getRangeCrosshairStroke() { - return this.rangeCrosshairStroke; - } - - /** - * Sets the pen-style ({@code Stroke}) used to draw the range - * crosshair (if visible), and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param stroke the new crosshair stroke ({@code null} not - * permitted). - * - * @see #getRangeCrosshairStroke() - */ - public void setRangeCrosshairStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.rangeCrosshairStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint used to draw the range crosshair. - * - * @return The paint (never {@code null}). - * - * @see #setRangeCrosshairPaint(Paint) - * @see #isRangeCrosshairVisible() - * @see #getRangeCrosshairStroke() - */ - public Paint getRangeCrosshairPaint() { - return this.rangeCrosshairPaint; - } - - /** - * Sets the paint used to draw the range crosshair (if visible) and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getRangeCrosshairPaint() - */ - public void setRangeCrosshairPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.rangeCrosshairPaint = paint; - fireChangeEvent(); - } - - /** - * Returns the list of annotations. - * - * @return The list of annotations (never {@code null}). - * - * @see #addAnnotation(CategoryAnnotation) - * @see #clearAnnotations() - */ - public List getAnnotations() { - return this.annotations; - } - - /** - * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param annotation the annotation ({@code null} not permitted). - * - * @see #removeAnnotation(CategoryAnnotation) - */ - public void addAnnotation(CategoryAnnotation annotation) { - addAnnotation(annotation, true); - } - - /** - * Adds an annotation to the plot and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param annotation the annotation ({@code null} not permitted). - * @param notify notify listeners? - */ - public void addAnnotation(CategoryAnnotation annotation, boolean notify) { - Args.nullNotPermitted(annotation, "annotation"); - this.annotations.add(annotation); - annotation.addChangeListener(this); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Removes an annotation from the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param annotation the annotation ({@code null} not permitted). - * - * @return A boolean (indicates whether or not the annotation was removed). - * - * @see #addAnnotation(CategoryAnnotation) - */ - public boolean removeAnnotation(CategoryAnnotation annotation) { - return removeAnnotation(annotation, true); - } - - /** - * Removes an annotation from the plot and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param annotation the annotation ({@code null} not permitted). - * @param notify notify listeners? - * - * @return A boolean (indicates whether or not the annotation was removed). - */ - public boolean removeAnnotation(CategoryAnnotation annotation, - boolean notify) { - Args.nullNotPermitted(annotation, "annotation"); - boolean removed = this.annotations.remove(annotation); - annotation.removeChangeListener(this); - if (removed && notify) { - fireChangeEvent(); - } - return removed; - } - - /** - * Clears all the annotations and sends a {@link PlotChangeEvent} to all - * registered listeners. - */ - public void clearAnnotations() { - for (int i = 0; i < this.annotations.size(); i++) { - CategoryAnnotation annotation = this.annotations.get(i); - annotation.removeChangeListener(this); - } - this.annotations.clear(); - fireChangeEvent(); - } - - /** - * Returns the shadow generator for the plot, if any. - * - * @return The shadow generator (possibly {@code null}). - */ - public ShadowGenerator getShadowGenerator() { - return this.shadowGenerator; - } - - /** - * Sets the shadow generator for the plot and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - */ - public void setShadowGenerator(ShadowGenerator generator) { - this.shadowGenerator = generator; - fireChangeEvent(); - } - - /** - * Calculates the space required for the domain axis/axes. - * - * @param g2 the graphics device. - * @param plotArea the plot area. - * @param space a carrier for the result ({@code null} permitted). - * - * @return The required space. - */ - protected AxisSpace calculateDomainAxisSpace(Graphics2D g2, - Rectangle2D plotArea, AxisSpace space) { - - if (space == null) { - space = new AxisSpace(); - } - - // reserve some space for the domain axis... - if (this.fixedDomainAxisSpace != null) { - if (this.orientation.isHorizontal()) { - space.ensureAtLeast( - this.fixedDomainAxisSpace.getLeft(), RectangleEdge.LEFT); - space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(), - RectangleEdge.RIGHT); - } else if (this.orientation.isVertical()) { - space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(), - RectangleEdge.TOP); - space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(), - RectangleEdge.BOTTOM); - } - } - else { - // reserve space for the primary domain axis... - RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( - getDomainAxisLocation(), this.orientation); - if (this.drawSharedDomainAxis) { - space = getDomainAxis().reserveSpace(g2, this, plotArea, - domainEdge, space); - } - - // reserve space for any domain axes... - for (CategoryAxis xAxis : this.domainAxes.values()) { - if (xAxis != null) { - int i = getDomainAxisIndex(xAxis); - RectangleEdge edge = getDomainAxisEdge(i); - space = xAxis.reserveSpace(g2, this, plotArea, edge, space); - } - } - } - - return space; - - } - - /** - * Calculates the space required for the range axis/axes. - * - * @param g2 the graphics device. - * @param plotArea the plot area. - * @param space a carrier for the result ({@code null} permitted). - * - * @return The required space. - */ - protected AxisSpace calculateRangeAxisSpace(Graphics2D g2, - Rectangle2D plotArea, AxisSpace space) { - - if (space == null) { - space = new AxisSpace(); - } - - // reserve some space for the range axis... - if (this.fixedRangeAxisSpace != null) { - if (this.orientation.isHorizontal()) { - space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(), - RectangleEdge.TOP); - space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(), - RectangleEdge.BOTTOM); - } else if (this.orientation == PlotOrientation.VERTICAL) { - space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(), - RectangleEdge.LEFT); - space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(), - RectangleEdge.RIGHT); - } - } else { - // reserve space for the range axes (if any)... - for (ValueAxis yAxis : this.rangeAxes.values()) { - if (yAxis != null) { - int i = findRangeAxisIndex(yAxis); - RectangleEdge edge = getRangeAxisEdge(i); - space = yAxis.reserveSpace(g2, this, plotArea, edge, space); - } - } - } - return space; - - } - - /** - * Trims a rectangle to integer coordinates. - * - * @param rect the incoming rectangle. - * - * @return A rectangle with integer coordinates. - */ - private Rectangle integerise(Rectangle2D rect) { - int x0 = (int) Math.ceil(rect.getMinX()); - int y0 = (int) Math.ceil(rect.getMinY()); - int x1 = (int) Math.floor(rect.getMaxX()); - int y1 = (int) Math.floor(rect.getMaxY()); - return new Rectangle(x0, y0, (x1 - x0), (y1 - y0)); - } - - /** - * Calculates the space required for the axes. - * - * @param g2 the graphics device. - * @param plotArea the plot area. - * - * @return The space required for the axes. - */ - protected AxisSpace calculateAxisSpace(Graphics2D g2, - Rectangle2D plotArea) { - AxisSpace space = new AxisSpace(); - space = calculateRangeAxisSpace(g2, plotArea, space); - space = calculateDomainAxisSpace(g2, plotArea, space); - return space; - } - - /** - * Draws the plot on a Java 2D graphics device (such as the screen or a - * printer). - *

- * At your option, you may supply an instance of {@link PlotRenderingInfo}. - * If you do, it will be populated with information about the drawing, - * including various plot dimensions and tooltip info. - * - * @param g2 the graphics device. - * @param area the area within which the plot (including axes) should - * be drawn. - * @param anchor the anchor point ({@code null} permitted). - * @param parentState the state from the parent plot, if there is one. - * @param state collects info as the chart is drawn (possibly - * {@code null}). - */ - @Override - public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, - PlotState parentState, PlotRenderingInfo state) { - - // if the plot area is too small, just return... - boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); - boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); - if (b1 || b2) { - return; - } - - // record the plot area... - if (state == null) { - // if the incoming state is null, no information will be passed - // back to the caller - but we create a temporary state to record - // the plot area, since that is used later by the axes - state = new PlotRenderingInfo(null); - } - state.setPlotArea(area); - - // adjust the drawing area for the plot insets (if any)... - RectangleInsets insets = getInsets(); - insets.trim(area); - - // calculate the data area... - AxisSpace space = calculateAxisSpace(g2, area); - Rectangle2D dataArea = space.shrink(area, null); - this.axisOffset.trim(dataArea); - dataArea = integerise(dataArea); - if (dataArea.isEmpty()) { - return; - } - state.setDataArea(dataArea); - createAndAddEntity((Rectangle2D) dataArea.clone(), state, null, null); - - // if there is a renderer, it draws the background, otherwise use the - // default background... - if (getRenderer() != null) { - getRenderer().drawBackground(g2, this, dataArea); - } else { - drawBackground(g2, dataArea); - } - - Map axisStateMap = drawAxes(g2, area, dataArea, state); - - // the anchor point is typically the point where the mouse last - // clicked - the crosshairs will be driven off this point... - if (anchor != null && !dataArea.contains(anchor)) { - anchor = ShapeUtils.getPointInRectangle(anchor.getX(), - anchor.getY(), dataArea); - } - CategoryCrosshairState crosshairState = new CategoryCrosshairState(); - crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY); - crosshairState.setAnchor(anchor); - - // specify the anchor X and Y coordinates in Java2D space, for the - // cases where these are not updated during rendering (i.e. no lock - // on data) - crosshairState.setAnchorX(Double.NaN); - crosshairState.setAnchorY(Double.NaN); - if (anchor != null) { - ValueAxis rangeAxis = getRangeAxis(); - if (rangeAxis != null) { - double y; - if (getOrientation() == PlotOrientation.VERTICAL) { - y = rangeAxis.java2DToValue(anchor.getY(), dataArea, - getRangeAxisEdge()); - } - else { - y = rangeAxis.java2DToValue(anchor.getX(), dataArea, - getRangeAxisEdge()); - } - crosshairState.setAnchorY(y); - } - } - crosshairState.setRowKey(getDomainCrosshairRowKey()); - crosshairState.setColumnKey(getDomainCrosshairColumnKey()); - crosshairState.setCrosshairY(getRangeCrosshairValue()); - - // don't let anyone draw outside the data area - Shape savedClip = g2.getClip(); - g2.clip(dataArea); - - drawDomainGridlines(g2, dataArea); - - AxisState rangeAxisState = (AxisState) axisStateMap.get(getRangeAxis()); - if (rangeAxisState == null) { - if (parentState != null) { - rangeAxisState = (AxisState) parentState.getSharedAxisStates() - .get(getRangeAxis()); - } - } - if (rangeAxisState != null) { - drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks()); - drawZeroRangeBaseline(g2, dataArea); - } - - Graphics2D savedG2 = g2; - BufferedImage dataImage = null; - boolean suppressShadow = Boolean.TRUE.equals(g2.getRenderingHint( - JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION)); - if (this.shadowGenerator != null && !suppressShadow) { - dataImage = new BufferedImage((int) dataArea.getWidth(), - (int)dataArea.getHeight(), BufferedImage.TYPE_INT_ARGB); - g2 = dataImage.createGraphics(); - g2.translate(-dataArea.getX(), -dataArea.getY()); - g2.setRenderingHints(savedG2.getRenderingHints()); - } - - // draw the markers... - for (CategoryItemRenderer renderer : this.renderers.values()) { - int i = getIndexOf(renderer); - drawDomainMarkers(g2, dataArea, i, Layer.BACKGROUND); - } - for (CategoryItemRenderer renderer : this.renderers.values()) { - int i = getIndexOf(renderer); - drawRangeMarkers(g2, dataArea, i, Layer.BACKGROUND); - } - - // now render data items... - boolean foundData = false; - - // set up the alpha-transparency... - Composite originalComposite = g2.getComposite(); - g2.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, getForegroundAlpha())); - - DatasetRenderingOrder order = getDatasetRenderingOrder(); - List datasetIndices = getDatasetIndices(order); - for (int i : datasetIndices) { - foundData = render(g2, dataArea, i, state, crosshairState) - || foundData; - } - - // draw the foreground markers... - List rendererIndices = getRendererIndices(order); - for (int i : rendererIndices) { - drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND); - } - for (int i : rendererIndices) { - drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND); - } - - // draw the annotations (if any)... - drawAnnotations(g2, dataArea); - - if (this.shadowGenerator != null && !suppressShadow) { - BufferedImage shadowImage = this.shadowGenerator.createDropShadow( - dataImage); - g2 = savedG2; - g2.drawImage(shadowImage, (int) dataArea.getX() - + this.shadowGenerator.calculateOffsetX(), - (int) dataArea.getY() - + this.shadowGenerator.calculateOffsetY(), null); - g2.drawImage(dataImage, (int) dataArea.getX(), - (int) dataArea.getY(), null); - } - g2.setClip(savedClip); - g2.setComposite(originalComposite); - - if (!foundData) { - drawNoDataMessage(g2, dataArea); - } - - int datasetIndex = crosshairState.getDatasetIndex(); - setCrosshairDatasetIndex(datasetIndex, false); - - // draw domain crosshair if required... - Comparable rowKey = crosshairState.getRowKey(); - Comparable columnKey = crosshairState.getColumnKey(); - setDomainCrosshairRowKey(rowKey, false); - setDomainCrosshairColumnKey(columnKey, false); - if (isDomainCrosshairVisible() && columnKey != null) { - Paint paint = getDomainCrosshairPaint(); - Stroke stroke = getDomainCrosshairStroke(); - drawDomainCrosshair(g2, dataArea, this.orientation, - datasetIndex, rowKey, columnKey, stroke, paint); - } - - // draw range crosshair if required... - ValueAxis yAxis = getRangeAxisForDataset(datasetIndex); - RectangleEdge yAxisEdge = getRangeAxisEdge(); - if (!this.rangeCrosshairLockedOnData && anchor != null) { - double yy; - if (getOrientation() == PlotOrientation.VERTICAL) { - yy = yAxis.java2DToValue(anchor.getY(), dataArea, yAxisEdge); - } - else { - yy = yAxis.java2DToValue(anchor.getX(), dataArea, yAxisEdge); - } - crosshairState.setCrosshairY(yy); - } - setRangeCrosshairValue(crosshairState.getCrosshairY(), false); - if (isRangeCrosshairVisible()) { - double y = getRangeCrosshairValue(); - Paint paint = getRangeCrosshairPaint(); - Stroke stroke = getRangeCrosshairStroke(); - drawRangeCrosshair(g2, dataArea, getOrientation(), y, yAxis, - stroke, paint); - } - - // draw an outline around the plot area... - if (isOutlineVisible()) { - if (getRenderer() != null) { - getRenderer().drawOutline(g2, this, dataArea); - } - else { - drawOutline(g2, dataArea); - } - } - - } - - /** - * Returns the indices of the non-null datasets in the specified order. - * - * @param order the order ({@code null} not permitted). - * - * @return The list of indices. - */ - private List getDatasetIndices(DatasetRenderingOrder order) { - List result = new ArrayList<>(); - for (Map.Entry entry : - this.datasets.entrySet()) { - if (entry.getValue() != null) { - result.add(entry.getKey()); - } - } - Collections.sort(result); - if (order == DatasetRenderingOrder.REVERSE) { - Collections.reverse(result); - } - return result; - } - - /** - * Returns the indices of the non-null renderers for the plot, in the - * specified order. - * - * @param order the rendering order {@code null} not permitted). - * - * @return A list of indices. - */ - private List getRendererIndices(DatasetRenderingOrder order) { - List result = new ArrayList<>(); - for (Map.Entry entry: - this.renderers.entrySet()) { - if (entry.getValue() != null) { - result.add(entry.getKey()); - } - } - Collections.sort(result); - if (order == DatasetRenderingOrder.REVERSE) { - Collections.reverse(result); - } - return result; - } - - /** - * Draws the plot background (the background color and/or image). - *

- * This method will be called during the chart drawing process and is - * declared public so that it can be accessed by the renderers used by - * certain subclasses. You shouldn't need to call this method directly. - * - * @param g2 the graphics device. - * @param area the area within which the plot should be drawn. - */ - @Override - public void drawBackground(Graphics2D g2, Rectangle2D area) { - fillBackground(g2, area, this.orientation); - drawBackgroundImage(g2, area); - } - - /** - * A utility method for drawing the plot's axes. - * - * @param g2 the graphics device. - * @param plotArea the plot area. - * @param dataArea the data area. - * @param plotState collects information about the plot ({@code null} - * permitted). - * - * @return A map containing the axis states. - */ - protected Map drawAxes(Graphics2D g2, Rectangle2D plotArea, - Rectangle2D dataArea, PlotRenderingInfo plotState) { - - AxisCollection axisCollection = new AxisCollection(); - - // add domain axes to lists... - for (CategoryAxis xAxis : this.domainAxes.values()) { - if (xAxis != null) { - int index = getDomainAxisIndex(xAxis); - axisCollection.add(xAxis, getDomainAxisEdge(index)); - } - } - - // add range axes to lists... - for (ValueAxis yAxis : this.rangeAxes.values()) { - if (yAxis != null) { - int index = findRangeAxisIndex(yAxis); - axisCollection.add(yAxis, getRangeAxisEdge(index)); - } - } - - Map axisStateMap = new HashMap(); - - // draw the top axes - double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset( - dataArea.getHeight()); - Iterator iterator = axisCollection.getAxesAtTop().iterator(); - while (iterator.hasNext()) { - Axis axis = (Axis) iterator.next(); - if (axis != null) { - AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, - RectangleEdge.TOP, plotState); - cursor = axisState.getCursor(); - axisStateMap.put(axis, axisState); - } - } - - // draw the bottom axes - cursor = dataArea.getMaxY() - + this.axisOffset.calculateBottomOutset(dataArea.getHeight()); - iterator = axisCollection.getAxesAtBottom().iterator(); - while (iterator.hasNext()) { - Axis axis = (Axis) iterator.next(); - if (axis != null) { - AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, - RectangleEdge.BOTTOM, plotState); - cursor = axisState.getCursor(); - axisStateMap.put(axis, axisState); - } - } - - // draw the left axes - cursor = dataArea.getMinX() - - this.axisOffset.calculateLeftOutset(dataArea.getWidth()); - iterator = axisCollection.getAxesAtLeft().iterator(); - while (iterator.hasNext()) { - Axis axis = (Axis) iterator.next(); - if (axis != null) { - AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, - RectangleEdge.LEFT, plotState); - cursor = axisState.getCursor(); - axisStateMap.put(axis, axisState); - } - } - - // draw the right axes - cursor = dataArea.getMaxX() - + this.axisOffset.calculateRightOutset(dataArea.getWidth()); - iterator = axisCollection.getAxesAtRight().iterator(); - while (iterator.hasNext()) { - Axis axis = (Axis) iterator.next(); - if (axis != null) { - AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, - RectangleEdge.RIGHT, plotState); - cursor = axisState.getCursor(); - axisStateMap.put(axis, axisState); - } - } - - return axisStateMap; - - } - - /** - * Draws a representation of a dataset within the dataArea region using the - * appropriate renderer. - * - * @param g2 the graphics device. - * @param dataArea the region in which the data is to be drawn. - * @param index the dataset and renderer index. - * @param info an optional object for collection dimension information. - * @param crosshairState a state object for tracking crosshair info - * ({@code null} permitted). - * - * @return A boolean that indicates whether or not real data was found. - */ - public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, - PlotRenderingInfo info, CategoryCrosshairState crosshairState) { - - boolean foundData = false; - CategoryDataset currentDataset = getDataset(index); - CategoryItemRenderer renderer = getRenderer(index); - CategoryAxis domainAxis = getDomainAxisForDataset(index); - ValueAxis rangeAxis = getRangeAxisForDataset(index); - boolean hasData = !DatasetUtils.isEmptyOrNull(currentDataset); - if (hasData && renderer != null) { - - foundData = true; - CategoryItemRendererState state = renderer.initialise(g2, dataArea, - this, index, info); - state.setCrosshairState(crosshairState); - int columnCount = currentDataset.getColumnCount(); - int rowCount = currentDataset.getRowCount(); - int passCount = renderer.getPassCount(); - for (int pass = 0; pass < passCount; pass++) { - if (this.columnRenderingOrder == SortOrder.ASCENDING) { - for (int column = 0; column < columnCount; column++) { - if (this.rowRenderingOrder == SortOrder.ASCENDING) { - for (int row = 0; row < rowCount; row++) { - renderer.drawItem(g2, state, dataArea, this, - domainAxis, rangeAxis, currentDataset, - row, column, pass); - } - } - else { - for (int row = rowCount - 1; row >= 0; row--) { - renderer.drawItem(g2, state, dataArea, this, - domainAxis, rangeAxis, currentDataset, - row, column, pass); - } - } - } - } - else { - for (int column = columnCount - 1; column >= 0; column--) { - if (this.rowRenderingOrder == SortOrder.ASCENDING) { - for (int row = 0; row < rowCount; row++) { - renderer.drawItem(g2, state, dataArea, this, - domainAxis, rangeAxis, currentDataset, - row, column, pass); - } - } - else { - for (int row = rowCount - 1; row >= 0; row--) { - renderer.drawItem(g2, state, dataArea, this, - domainAxis, rangeAxis, currentDataset, - row, column, pass); - } - } - } - } - } - } - return foundData; - - } - - /** - * Draws the domain gridlines for the plot, if they are visible. - * - * @param g2 the graphics device. - * @param dataArea the area inside the axes. - * - * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List) - */ - protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea) { - - if (!isDomainGridlinesVisible()) { - return; - } - CategoryAnchor anchor = getDomainGridlinePosition(); - RectangleEdge domainAxisEdge = getDomainAxisEdge(); - CategoryDataset dataset = getDataset(); - if (dataset == null) { - return; - } - CategoryAxis axis = getDomainAxis(); - if (axis != null) { - int columnCount = dataset.getColumnCount(); - for (int c = 0; c < columnCount; c++) { - double xx = axis.getCategoryJava2DCoordinate(anchor, c, - columnCount, dataArea, domainAxisEdge); - CategoryItemRenderer renderer1 = getRenderer(); - if (renderer1 != null) { - renderer1.drawDomainGridline(g2, this, dataArea, xx); - } - } - } - } - - /** - * Draws the range gridlines for the plot, if they are visible. - * - * @param g2 the graphics device ({@code null} not permitted). - * @param dataArea the area inside the axes ({@code null} not permitted). - * @param ticks the ticks. - * - * @see #drawDomainGridlines(Graphics2D, Rectangle2D) - */ - protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea, - List ticks) { - // draw the range grid lines, if any... - if (!isRangeGridlinesVisible() && !isRangeMinorGridlinesVisible()) { - return; - } - // no axis, no gridlines... - ValueAxis axis = getRangeAxis(); - if (axis == null) { - return; - } - // no renderer, no gridlines... - CategoryItemRenderer r = getRenderer(); - if (r == null) { - return; - } - - Stroke gridStroke = null; - Paint gridPaint = null; - boolean paintLine; - Iterator iterator = ticks.iterator(); - while (iterator.hasNext()) { - paintLine = false; - ValueTick tick = (ValueTick) iterator.next(); - if ((tick.getTickType() == TickType.MINOR) - && isRangeMinorGridlinesVisible()) { - gridStroke = getRangeMinorGridlineStroke(); - gridPaint = getRangeMinorGridlinePaint(); - paintLine = true; - } - else if ((tick.getTickType() == TickType.MAJOR) - && isRangeGridlinesVisible()) { - gridStroke = getRangeGridlineStroke(); - gridPaint = getRangeGridlinePaint(); - paintLine = true; - } - if (((tick.getValue() != 0.0) - || !isRangeZeroBaselineVisible()) && paintLine) { - r .drawRangeLine(g2, this, axis, dataArea, - tick.getValue(), gridPaint, gridStroke); - } - } - } - - /** - * Draws a base line across the chart at value zero on the range axis. - * - * @param g2 the graphics device. - * @param area the data area. - * - * @see #setRangeZeroBaselineVisible(boolean) - */ - protected void drawZeroRangeBaseline(Graphics2D g2, Rectangle2D area) { - if (!isRangeZeroBaselineVisible()) { - return; - } - CategoryItemRenderer r = getRenderer(); - r.drawRangeLine(g2, this, getRangeAxis(), area, 0.0, - this.rangeZeroBaselinePaint, this.rangeZeroBaselineStroke); - } - - /** - * Draws the annotations. - * - * @param g2 the graphics device. - * @param dataArea the data area. - */ - protected void drawAnnotations(Graphics2D g2, Rectangle2D dataArea) { - - if (getAnnotations() != null) { - Iterator iterator = getAnnotations().iterator(); - while (iterator.hasNext()) { - CategoryAnnotation annotation - = (CategoryAnnotation) iterator.next(); - annotation.draw(g2, this, dataArea, getDomainAxis(), - getRangeAxis()); - } - } - - } - - /** - * Draws the domain markers (if any) for an axis and layer. This method is - * typically called from within the draw() method. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param index the renderer index. - * @param layer the layer (foreground or background). - * - * @see #drawRangeMarkers(Graphics2D, Rectangle2D, int, Layer) - */ - protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea, - int index, Layer layer) { - - CategoryItemRenderer r = getRenderer(index); - if (r == null) { - return; - } - - Collection markers = getDomainMarkers(index, layer); - CategoryAxis axis = getDomainAxisForDataset(index); - if (markers != null && axis != null) { - for (CategoryMarker marker : markers) { - r.drawDomainMarker(g2, this, axis, marker, dataArea); - } - } - - } - - /** - * Draws the range markers (if any) for an axis and layer. This method is - * typically called from within the draw() method. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param index the renderer index. - * @param layer the layer (foreground or background). - * - * @see #drawDomainMarkers(Graphics2D, Rectangle2D, int, Layer) - */ - protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea, - int index, Layer layer) { - - CategoryItemRenderer r = getRenderer(index); - if (r == null) { - return; - } - - Collection markers = getRangeMarkers(index, layer); - ValueAxis axis = getRangeAxisForDataset(index); - if (markers != null && axis != null) { - for (Marker marker : markers) { - r.drawRangeMarker(g2, this, axis, marker, dataArea); - } - } - - } - - /** - * Utility method for drawing a line perpendicular to the range axis (used - * for crosshairs). - * - * @param g2 the graphics device. - * @param dataArea the area defined by the axes. - * @param value the data value. - * @param stroke the line stroke ({@code null} not permitted). - * @param paint the line paint ({@code null} not permitted). - */ - protected void drawRangeLine(Graphics2D g2, Rectangle2D dataArea, - double value, Stroke stroke, Paint paint) { - - double java2D = getRangeAxis().valueToJava2D(value, dataArea, - getRangeAxisEdge()); - Line2D line = null; - if (this.orientation == PlotOrientation.HORIZONTAL) { - line = new Line2D.Double(java2D, dataArea.getMinY(), java2D, - dataArea.getMaxY()); - } - else if (this.orientation == PlotOrientation.VERTICAL) { - line = new Line2D.Double(dataArea.getMinX(), java2D, - dataArea.getMaxX(), java2D); - } - g2.setStroke(stroke); - g2.setPaint(paint); - g2.draw(line); - - } - - /** - * Draws a domain crosshair. - * - * @param g2 the graphics target. - * @param dataArea the data area. - * @param orientation the plot orientation. - * @param datasetIndex the dataset index. - * @param rowKey the row key. - * @param columnKey the column key. - * @param stroke the stroke used to draw the crosshair line. - * @param paint the paint used to draw the crosshair line. - * - * @see #drawRangeCrosshair(Graphics2D, Rectangle2D, PlotOrientation, - * double, ValueAxis, Stroke, Paint) - */ - protected void drawDomainCrosshair(Graphics2D g2, Rectangle2D dataArea, - PlotOrientation orientation, int datasetIndex, - Comparable rowKey, Comparable columnKey, Stroke stroke, - Paint paint) { - - CategoryDataset dataset = getDataset(datasetIndex); - CategoryAxis axis = getDomainAxisForDataset(datasetIndex); - CategoryItemRenderer renderer = getRenderer(datasetIndex); - Line2D line; - if (orientation == PlotOrientation.VERTICAL) { - double xx = renderer.getItemMiddle(rowKey, columnKey, dataset, axis, - dataArea, RectangleEdge.BOTTOM); - line = new Line2D.Double(xx, dataArea.getMinY(), xx, - dataArea.getMaxY()); - } - else { - double yy = renderer.getItemMiddle(rowKey, columnKey, dataset, axis, - dataArea, RectangleEdge.LEFT); - line = new Line2D.Double(dataArea.getMinX(), yy, - dataArea.getMaxX(), yy); - } - g2.setStroke(stroke); - g2.setPaint(paint); - g2.draw(line); - - } - - /** - * Draws a range crosshair. - * - * @param g2 the graphics target. - * @param dataArea the data area. - * @param orientation the plot orientation. - * @param value the crosshair value. - * @param axis the axis against which the value is measured. - * @param stroke the stroke used to draw the crosshair line. - * @param paint the paint used to draw the crosshair line. - * - * @see #drawDomainCrosshair(Graphics2D, Rectangle2D, PlotOrientation, int, - * Comparable, Comparable, Stroke, Paint) - */ - protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea, - PlotOrientation orientation, double value, ValueAxis axis, - Stroke stroke, Paint paint) { - - if (!axis.getRange().contains(value)) { - return; - } - Line2D line; - if (orientation == PlotOrientation.HORIZONTAL) { - double xx = axis.valueToJava2D(value, dataArea, - RectangleEdge.BOTTOM); - line = new Line2D.Double(xx, dataArea.getMinY(), xx, - dataArea.getMaxY()); - } - else { - double yy = axis.valueToJava2D(value, dataArea, - RectangleEdge.LEFT); - line = new Line2D.Double(dataArea.getMinX(), yy, - dataArea.getMaxX(), yy); - } - g2.setStroke(stroke); - g2.setPaint(paint); - g2.draw(line); - - } - - /** - * Returns the range of data values that will be plotted against the range - * axis. If the dataset is {@code null}, this method returns - * {@code null}. - * - * @param axis the axis. - * - * @return The data range. - */ - @Override - public Range getDataRange(ValueAxis axis) { - Range result = null; - List mappedDatasets = new ArrayList<>(); - int rangeIndex = findRangeAxisIndex(axis); - if (rangeIndex >= 0) { - mappedDatasets.addAll(datasetsMappedToRangeAxis(rangeIndex)); - } - else if (axis == getRangeAxis()) { - mappedDatasets.addAll(datasetsMappedToRangeAxis(0)); - } - - // iterate through the datasets that map to the axis and get the union - // of the ranges. - for (CategoryDataset d : mappedDatasets) { - CategoryItemRenderer r = getRendererForDataset(d); - if (r != null) { - result = Range.combine(result, r.findRangeBounds(d)); - } - } - return result; - } - - /** - * Returns a list of the datasets that are mapped to the axis with the - * specified index. - * - * @param axisIndex the axis index. - * - * @return The list (possibly empty, but never {@code null}). - */ - private List datasetsMappedToDomainAxis(int axisIndex) { - List result = new ArrayList<>(); - for (Entry entry : this.datasets.entrySet()) { - CategoryDataset dataset = entry.getValue(); - if (dataset == null) { - continue; - } - Integer datasetIndex = entry.getKey(); - List mappedAxes = this.datasetToDomainAxesMap.get(datasetIndex); - if (mappedAxes == null) { - if (axisIndex == 0) { - result.add(dataset); - } - } else { - if (mappedAxes.contains(axisIndex)) { - result.add(dataset); - } - } - } - return result; - } - - /** - * A utility method that returns a list of datasets that are mapped to a - * given range axis. - * - * @param axisIndex the axis index. - * - * @return The list (possibly empty, but never {@code null}). - */ - private List datasetsMappedToRangeAxis(int axisIndex) { - List result = new ArrayList<>(); - for (Entry entry : this.datasets.entrySet()) { - Integer datasetIndex = entry.getKey(); - CategoryDataset dataset = entry.getValue(); - List mappedAxes = this.datasetToRangeAxesMap.get(datasetIndex); - if (mappedAxes == null) { - if (axisIndex == 0) { - result.add(dataset); - } - } else { - if (mappedAxes.contains(axisIndex)) { - result.add(dataset); - } - } - } - return result; - } - - /** - * Returns the weight for this plot when it is used as a subplot within a - * combined plot. - * - * @return The weight. - * - * @see #setWeight(int) - */ - public int getWeight() { - return this.weight; - } - - /** - * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param weight the weight. - * - * @see #getWeight() - */ - public void setWeight(int weight) { - this.weight = weight; - fireChangeEvent(); - } - - /** - * Returns the fixed domain axis space. - * - * @return The fixed domain axis space (possibly {@code null}). - * - * @see #setFixedDomainAxisSpace(AxisSpace) - */ - public AxisSpace getFixedDomainAxisSpace() { - return this.fixedDomainAxisSpace; - } - - /** - * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param space the space ({@code null} permitted). - * - * @see #getFixedDomainAxisSpace() - */ - public void setFixedDomainAxisSpace(AxisSpace space) { - setFixedDomainAxisSpace(space, true); - } - - /** - * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param space the space ({@code null} permitted). - * @param notify notify listeners? - * - * @see #getFixedDomainAxisSpace() - */ - public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) { - this.fixedDomainAxisSpace = space; - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the fixed range axis space. - * - * @return The fixed range axis space (possibly {@code null}). - * - * @see #setFixedRangeAxisSpace(AxisSpace) - */ - public AxisSpace getFixedRangeAxisSpace() { - return this.fixedRangeAxisSpace; - } - - /** - * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param space the space ({@code null} permitted). - * - * @see #getFixedRangeAxisSpace() - */ - public void setFixedRangeAxisSpace(AxisSpace space) { - setFixedRangeAxisSpace(space, true); - } - - /** - * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param space the space ({@code null} permitted). - * @param notify notify listeners? - * - * @see #getFixedRangeAxisSpace() - */ - public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) { - this.fixedRangeAxisSpace = space; - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns a list of the categories in the plot's primary dataset. - * - * @return A list of the categories in the plot's primary dataset. - * - * @see #getCategoriesForAxis(CategoryAxis) - */ - public List getCategories() { - List result = null; - if (getDataset() != null) { - result = Collections.unmodifiableList(getDataset().getColumnKeys()); - } - return result; - } - - /** - * Returns a list of the categories that should be displayed for the - * specified axis. - * - * @param axis the axis ({@code null} not permitted) - * - * @return The categories. - */ - public List getCategoriesForAxis(CategoryAxis axis) { - List result = new ArrayList(); - int axisIndex = getDomainAxisIndex(axis); - for (CategoryDataset dataset : datasetsMappedToDomainAxis(axisIndex)) { - // add the unique categories from this dataset - for (int i = 0; i < dataset.getColumnCount(); i++) { - Comparable category = dataset.getColumnKey(i); - if (!result.contains(category)) { - result.add(category); - } - } - } - return result; - } - - /** - * Returns the flag that controls whether or not the shared domain axis is - * drawn for each subplot. - * - * @return A boolean. - * - * @see #setDrawSharedDomainAxis(boolean) - */ - public boolean getDrawSharedDomainAxis() { - return this.drawSharedDomainAxis; - } - - /** - * Sets the flag that controls whether the shared domain axis is drawn when - * this plot is being used as a subplot. - * - * @param draw a boolean. - * - * @see #getDrawSharedDomainAxis() - */ - public void setDrawSharedDomainAxis(boolean draw) { - this.drawSharedDomainAxis = draw; - fireChangeEvent(); - } - - /** - * Returns {@code false} always, because the plot cannot be panned - * along the domain axis/axes. - * - * @return A boolean. - * - * @see #isRangePannable() - */ - @Override - public boolean isDomainPannable() { - return false; - } - - /** - * Returns {@code true} if panning is enabled for the range axes, - * and {@code false} otherwise. - * - * @return A boolean. - * - * @see #setRangePannable(boolean) - * @see #isDomainPannable() - */ - @Override - public boolean isRangePannable() { - return this.rangePannable; - } - - /** - * Sets the flag that enables or disables panning of the plot along - * the range axes. - * - * @param pannable the new flag value. - * - * @see #isRangePannable() - */ - public void setRangePannable(boolean pannable) { - this.rangePannable = pannable; - } - - /** - * Pans the domain axes by the specified percentage. - * - * @param percent the distance to pan (as a percentage of the axis length). - * @param info the plot info - * @param source the source point where the pan action started. - */ - @Override - public void panDomainAxes(double percent, PlotRenderingInfo info, - Point2D source) { - // do nothing, because the plot is not pannable along the domain axes - } - - /** - * Pans the range axes by the specified percentage. - * - * @param percent the distance to pan (as a percentage of the axis length). - * @param info the plot info - * @param source the source point where the pan action started. - */ - @Override - public void panRangeAxes(double percent, PlotRenderingInfo info, - Point2D source) { - if (!isRangePannable()) { - return; - } - for (ValueAxis axis : this.rangeAxes.values()) { - if (axis == null) { - continue; - } - double length = axis.getRange().getLength(); - double adj = percent * length; - if (axis.isInverted()) { - adj = -adj; - } - axis.setRange(axis.getLowerBound() + adj, - axis.getUpperBound() + adj); - } - } - - /** - * Returns {@code false} to indicate that the domain axes are not - * zoomable. - * - * @return A boolean. - * - * @see #isRangeZoomable() - */ - @Override - public boolean isDomainZoomable() { - return false; - } - - /** - * Returns {@code true} to indicate that the range axes are zoomable. - * - * @return A boolean. - * - * @see #isDomainZoomable() - */ - @Override - public boolean isRangeZoomable() { - return true; - } - - /** - * This method does nothing, because {@code CategoryPlot} doesn't - * support zooming on the domain. - * - * @param factor the zoom factor. - * @param state the plot state. - * @param source the source point (in Java2D space) for the zoom. - */ - @Override - public void zoomDomainAxes(double factor, PlotRenderingInfo state, - Point2D source) { - // can't zoom domain axis - } - - /** - * This method does nothing, because {@code CategoryPlot} doesn't - * support zooming on the domain. - * - * @param lowerPercent the lower bound. - * @param upperPercent the upper bound. - * @param state the plot state. - * @param source the source point (in Java2D space) for the zoom. - */ - @Override - public void zoomDomainAxes(double lowerPercent, double upperPercent, - PlotRenderingInfo state, Point2D source) { - // can't zoom domain axis - } - - /** - * This method does nothing, because {@code CategoryPlot} doesn't - * support zooming on the domain. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point (in Java2D space). - * @param useAnchor use source point as zoom anchor? - * - * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean) - */ - @Override - public void zoomDomainAxes(double factor, PlotRenderingInfo info, - Point2D source, boolean useAnchor) { - // can't zoom domain axis - } - - /** - * Multiplies the range on the range axis/axes by the specified factor. - * - * @param factor the zoom factor. - * @param state the plot state. - * @param source the source point (in Java2D space) for the zoom. - */ - @Override - public void zoomRangeAxes(double factor, PlotRenderingInfo state, - Point2D source) { - // delegate to other method - zoomRangeAxes(factor, state, source, false); - } - - /** - * Multiplies the range on the range axis/axes by the specified factor. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point. - * @param useAnchor a flag that controls whether or not the source point - * is used for the zoom anchor. - * - * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) - */ - @Override - public void zoomRangeAxes(double factor, PlotRenderingInfo info, - Point2D source, boolean useAnchor) { - - // perform the zoom on each range axis - for (ValueAxis rangeAxis : this.rangeAxes.values()) { - if (rangeAxis == null) { - continue; - } - if (useAnchor) { - // get the relevant source coordinate given the plot orientation - double sourceY = source.getY(); - if (this.orientation.isHorizontal()) { - sourceY = source.getX(); - } - double anchorY = rangeAxis.java2DToValue(sourceY, - info.getDataArea(), getRangeAxisEdge()); - rangeAxis.resizeRange2(factor, anchorY); - } else { - rangeAxis.resizeRange(factor); - } - } - } - - /** - * Zooms in on the range axes. - * - * @param lowerPercent the lower bound. - * @param upperPercent the upper bound. - * @param state the plot state. - * @param source the source point (in Java2D space) for the zoom. - */ - @Override - public void zoomRangeAxes(double lowerPercent, double upperPercent, - PlotRenderingInfo state, Point2D source) { - for (ValueAxis yAxis : this.rangeAxes.values()) { - if (yAxis != null) { - yAxis.zoomRange(lowerPercent, upperPercent); - } - } - } - - /** - * Returns the anchor value. - * - * @return The anchor value. - * - * @see #setAnchorValue(double) - */ - public double getAnchorValue() { - return this.anchorValue; - } - - /** - * Sets the anchor value and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param value the anchor value. - * - * @see #getAnchorValue() - */ - public void setAnchorValue(double value) { - setAnchorValue(value, true); - } - - /** - * Sets the anchor value and, if requested, sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param value the value. - * @param notify notify listeners? - * - * @see #getAnchorValue() - */ - public void setAnchorValue(double value, boolean notify) { - this.anchorValue = value; - if (notify) { - fireChangeEvent(); - } - } - - /** - * Tests the plot for equality with an arbitrary object. - * - * @param obj the object to test against ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof CategoryPlot)) { - return false; - } - CategoryPlot that = (CategoryPlot) obj; - if (!that.canEqual(this)) { - return false; - } - if (!Objects.equals(this.orientation, that.orientation)) { - return false; - } - if (!Objects.equals(this.datasets, that.datasets)) { - return false; - } - if (!Objects.equals(this.axisOffset, that.axisOffset)) { - return false; - } - if (!Objects.equals(this.domainAxes, that.domainAxes)) { - return false; - } - if (!Objects.equals(this.domainAxisLocations, - that.domainAxisLocations)) { - return false; - } - if (this.drawSharedDomainAxis != that.drawSharedDomainAxis) { - return false; - } - if (!Objects.equals(this.rangeAxes, that.rangeAxes)) { - return false; - } - if (!Objects.equals(this.rangeAxisLocations, that.rangeAxisLocations)) { - return false; - } - if (!Objects.equals(this.datasetToDomainAxesMap, - that.datasetToDomainAxesMap)) { - return false; - } - if (!Objects.equals(this.datasetToRangeAxesMap, - that.datasetToRangeAxesMap)) { - return false; - } - if (!Objects.equals(this.renderers, that.renderers)) { - return false; - } - if (!Objects.equals(this.renderingOrder, that.renderingOrder)) { - return false; - } - if (!Objects.equals(this.columnRenderingOrder, - that.columnRenderingOrder)) { - return false; - } - if (!Objects.equals(this.rowRenderingOrder, that.rowRenderingOrder)) { - return false; - } - if (this.domainGridlinesVisible != that.domainGridlinesVisible) { - return false; - } - if (this.rangePannable != that.rangePannable) { - return false; - } - if (!Objects.equals(this.domainGridlinePosition, - that.domainGridlinePosition)) { - return false; - } - if (!Objects.equals(this.domainGridlineStroke, - that.domainGridlineStroke)) { - return false; - } - if (!PaintUtils.equal(this.domainGridlinePaint, - that.domainGridlinePaint)) { - return false; - } - if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) { - return false; - } - if (!Objects.equals(this.rangeGridlineStroke, - that.rangeGridlineStroke)) { - return false; - } - if (!PaintUtils.equal(this.rangeGridlinePaint, - that.rangeGridlinePaint)) { - return false; - } - if (Double.compare(this.anchorValue, that.anchorValue) != 0) { - return false; - } - if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) { - return false; - } - if (Double.doubleToLongBits(this.rangeCrosshairValue) != - Double.doubleToLongBits(that.rangeCrosshairValue)) { - return false; - } - if (!Objects.equals(this.rangeCrosshairStroke, - that.rangeCrosshairStroke)) { - return false; - } - if (!PaintUtils.equal(this.rangeCrosshairPaint, - that.rangeCrosshairPaint)) { - return false; - } - if (this.rangeCrosshairLockedOnData != that.rangeCrosshairLockedOnData) { - return false; - } - if (!Objects.equals(this.foregroundDomainMarkers, - that.foregroundDomainMarkers)) { - return false; - } - if (!Objects.equals(this.backgroundDomainMarkers, - that.backgroundDomainMarkers)) { - return false; - } - if (!Objects.equals(this.foregroundRangeMarkers, - that.foregroundRangeMarkers)) { - return false; - } - if (!Objects.equals(this.backgroundRangeMarkers, - that.backgroundRangeMarkers)) { - return false; - } - if (!Objects.equals(this.annotations, that.annotations)) { - return false; - } - if (this.weight != that.weight) { - return false; - } - if (!Objects.equals(this.fixedDomainAxisSpace, - that.fixedDomainAxisSpace)) { - return false; - } - if (!Objects.equals(this.fixedRangeAxisSpace, - that.fixedRangeAxisSpace)) { - return false; - } - if (!Objects.equals(this.fixedLegendItems, that.fixedLegendItems)) { - return false; - } - if (this.domainCrosshairVisible != that.domainCrosshairVisible) { - return false; - } - if (this.crosshairDatasetIndex != that.crosshairDatasetIndex) { - return false; - } - if (!Objects.equals(this.domainCrosshairColumnKey, - that.domainCrosshairColumnKey)) { - return false; - } - if (!Objects.equals(this.domainCrosshairRowKey, - that.domainCrosshairRowKey)) { - return false; - } - if (!PaintUtils.equal(this.domainCrosshairPaint, - that.domainCrosshairPaint)) { - return false; - } - if (!Objects.equals(this.domainCrosshairStroke, - that.domainCrosshairStroke)) { - return false; - } - if (this.rangeMinorGridlinesVisible != that.rangeMinorGridlinesVisible) { - return false; - } - if (!PaintUtils.equal(this.rangeMinorGridlinePaint, - that.rangeMinorGridlinePaint)) { - return false; - } - if (!Objects.equals(this.rangeMinorGridlineStroke, - that.rangeMinorGridlineStroke)) { - return false; - } - if (this.rangeZeroBaselineVisible != that.rangeZeroBaselineVisible) { - return false; - } - if (!PaintUtils.equal(this.rangeZeroBaselinePaint, - that.rangeZeroBaselinePaint)) { - return false; - } - if (!Objects.equals(this.rangeZeroBaselineStroke, - that.rangeZeroBaselineStroke)) { - return false; - } - if (!Objects.equals(this.shadowGenerator, that.shadowGenerator)) { - return false; - } - return super.equals(obj); - } - - /** - * Ensures symmetry between super/subclass implementations of equals. For - * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. - * - * @param other Object - * - * @return true ONLY if the parameter is THIS class type - */ - @Override - public boolean canEqual(Object other) { - // Solves Problem: equals not symmetric - return (other instanceof CategoryPlot); - } - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 71 * hash + Objects.hashCode(this.orientation); - hash = 71 * hash + Objects.hashCode(this.axisOffset); - hash = 71 * hash + Objects.hashCode(this.domainAxes); - hash = 71 * hash + Objects.hashCode(this.domainAxisLocations); - hash = 71 * hash + (this.drawSharedDomainAxis ? 1 : 0); - hash = 71 * hash + Objects.hashCode(this.rangeAxes); - hash = 71 * hash + Objects.hashCode(this.rangeAxisLocations); - hash = 71 * hash + Objects.hashCode(this.datasets); - hash = 71 * hash + Objects.hashCode(this.datasetToDomainAxesMap); - hash = 71 * hash + Objects.hashCode(this.datasetToRangeAxesMap); - hash = 71 * hash + Objects.hashCode(this.renderers); - hash = 71 * hash + Objects.hashCode(this.renderingOrder); - hash = 71 * hash + Objects.hashCode(this.columnRenderingOrder); - hash = 71 * hash + Objects.hashCode(this.rowRenderingOrder); - hash = 71 * hash + (this.domainGridlinesVisible ? 1 : 0); - hash = 71 * hash + Objects.hashCode(this.domainGridlinePosition); - hash = 71 * hash + Objects.hashCode(this.domainGridlineStroke); - hash = 71 * hash + Objects.hashCode(this.domainGridlinePaint); - hash = 71 * hash + (this.rangeZeroBaselineVisible ? 1 : 0); - hash = 71 * hash + Objects.hashCode(this.rangeZeroBaselineStroke); - hash = 71 * hash + Objects.hashCode(this.rangeZeroBaselinePaint); - hash = 71 * hash + (this.rangeGridlinesVisible ? 1 : 0); - hash = 71 * hash + Objects.hashCode(this.rangeGridlineStroke); - hash = 71 * hash + Objects.hashCode(this.rangeGridlinePaint); - hash = 71 * hash + (this.rangeMinorGridlinesVisible ? 1 : 0); - hash = 71 * hash + Objects.hashCode(this.rangeMinorGridlineStroke); - hash = 71 * hash + Objects.hashCode(this.rangeMinorGridlinePaint); - hash = 71 * hash + (int) (Double.doubleToLongBits(this.anchorValue) ^ - (Double.doubleToLongBits(this.anchorValue) >>> 32)); - hash = 71 * hash + this.crosshairDatasetIndex; - hash = 71 * hash + (this.domainCrosshairVisible ? 1 : 0); - hash = 71 * hash + Objects.hashCode(this.domainCrosshairRowKey); - hash = 71 * hash + Objects.hashCode(this.domainCrosshairColumnKey); - hash = 71 * hash + Objects.hashCode(this.domainCrosshairStroke); - hash = 71 * hash + Objects.hashCode(this.domainCrosshairPaint); - hash = 71 * hash + (this.rangeCrosshairVisible ? 1 : 0); - hash = 71 * hash + (int) (Double.doubleToLongBits(this.rangeCrosshairValue) ^ - (Double.doubleToLongBits(this.rangeCrosshairValue) >>> 32)); - hash = 71 * hash + Objects.hashCode(this.rangeCrosshairStroke); - hash = 71 * hash + Objects.hashCode(this.rangeCrosshairPaint); - hash = 71 * hash + (this.rangeCrosshairLockedOnData ? 1 : 0); - hash = 71 * hash + Objects.hashCode(this.foregroundDomainMarkers); - hash = 71 * hash + Objects.hashCode(this.backgroundDomainMarkers); - hash = 71 * hash + Objects.hashCode(this.foregroundRangeMarkers); - hash = 71 * hash + Objects.hashCode(this.backgroundRangeMarkers); - hash = 71 * hash + Objects.hashCode(this.annotations); - hash = 71 * hash + this.weight; - hash = 71 * hash + Objects.hashCode(this.fixedDomainAxisSpace); - hash = 71 * hash + Objects.hashCode(this.fixedRangeAxisSpace); - hash = 71 * hash + Objects.hashCode(this.fixedLegendItems); - hash = 71 * hash + (this.rangePannable ? 1 : 0); - hash = 71 * hash + Objects.hashCode(this.shadowGenerator); - return hash; - } - - /** - * Returns a clone of the plot. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the cloning is not supported. - */ - @Override - public Object clone() throws CloneNotSupportedException { - CategoryPlot clone = (CategoryPlot) super.clone(); - clone.domainAxes = CloneUtils.cloneMapValues(this.domainAxes); - for (CategoryAxis axis : clone.domainAxes.values()) { - if (axis != null) { - axis.setPlot(clone); - axis.addChangeListener(clone); - } - } - clone.rangeAxes = CloneUtils.cloneMapValues(this.rangeAxes); - for (ValueAxis axis : clone.rangeAxes.values()) { - if (axis != null) { - axis.setPlot(clone); - axis.addChangeListener(clone); - } - } - - // AxisLocation is immutable, so we can just copy the maps - clone.domainAxisLocations = new HashMap<>( - this.domainAxisLocations); - clone.rangeAxisLocations = new HashMap<>( - this.rangeAxisLocations); - - clone.datasets = new HashMap<>(this.datasets); - for (CategoryDataset dataset : clone.datasets.values()) { - if (dataset != null) { - dataset.addChangeListener(clone); - } - } - clone.datasetToDomainAxesMap = new TreeMap(); - clone.datasetToDomainAxesMap.putAll(this.datasetToDomainAxesMap); - clone.datasetToRangeAxesMap = new TreeMap(); - clone.datasetToRangeAxesMap.putAll(this.datasetToRangeAxesMap); - - clone.renderers = CloneUtils.cloneMapValues(this.renderers); - for (CategoryItemRenderer renderer : clone.renderers.values()) { - if (renderer != null) { - renderer.setPlot(clone); - renderer.addChangeListener(clone); - } - } - if (this.fixedDomainAxisSpace != null) { - clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtils.clone( - this.fixedDomainAxisSpace); - } - if (this.fixedRangeAxisSpace != null) { - clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtils.clone( - this.fixedRangeAxisSpace); - } - - clone.annotations = (List) ObjectUtils.deepClone(this.annotations); - clone.foregroundDomainMarkers = cloneMarkerMap( - this.foregroundDomainMarkers); - clone.backgroundDomainMarkers = cloneMarkerMap( - this.backgroundDomainMarkers); - clone.foregroundRangeMarkers = cloneMarkerMap( - this.foregroundRangeMarkers); - clone.backgroundRangeMarkers = cloneMarkerMap( - this.backgroundRangeMarkers); - if (this.fixedLegendItems != null) { - clone.fixedLegendItems - = (LegendItemCollection) this.fixedLegendItems.clone(); - } - return clone; - } - - /** - * A utility method to clone the marker maps. - * - * @param map the map to clone. - * - * @return A clone of the map. - * - * @throws CloneNotSupportedException if there is some problem cloning the - * map. - */ - private Map cloneMarkerMap(Map map) throws CloneNotSupportedException { - Map clone = new HashMap(); - Set keys = map.keySet(); - Iterator iterator = keys.iterator(); - while (iterator.hasNext()) { - Object key = iterator.next(); - List entry = (List) map.get(key); - Object toAdd = ObjectUtils.deepClone(entry); - clone.put(key, toAdd); - } - return clone; - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writeStroke(this.domainGridlineStroke, stream); - SerialUtils.writePaint(this.domainGridlinePaint, stream); - SerialUtils.writeStroke(this.rangeGridlineStroke, stream); - SerialUtils.writePaint(this.rangeGridlinePaint, stream); - SerialUtils.writeStroke(this.rangeCrosshairStroke, stream); - SerialUtils.writePaint(this.rangeCrosshairPaint, stream); - SerialUtils.writeStroke(this.domainCrosshairStroke, stream); - SerialUtils.writePaint(this.domainCrosshairPaint, stream); - SerialUtils.writeStroke(this.rangeMinorGridlineStroke, stream); - SerialUtils.writePaint(this.rangeMinorGridlinePaint, stream); - SerialUtils.writeStroke(this.rangeZeroBaselineStroke, stream); - SerialUtils.writePaint(this.rangeZeroBaselinePaint, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - - stream.defaultReadObject(); - this.domainGridlineStroke = SerialUtils.readStroke(stream); - this.domainGridlinePaint = SerialUtils.readPaint(stream); - this.rangeGridlineStroke = SerialUtils.readStroke(stream); - this.rangeGridlinePaint = SerialUtils.readPaint(stream); - this.rangeCrosshairStroke = SerialUtils.readStroke(stream); - this.rangeCrosshairPaint = SerialUtils.readPaint(stream); - this.domainCrosshairStroke = SerialUtils.readStroke(stream); - this.domainCrosshairPaint = SerialUtils.readPaint(stream); - this.rangeMinorGridlineStroke = SerialUtils.readStroke(stream); - this.rangeMinorGridlinePaint = SerialUtils.readPaint(stream); - this.rangeZeroBaselineStroke = SerialUtils.readStroke(stream); - this.rangeZeroBaselinePaint = SerialUtils.readPaint(stream); - - for (CategoryAxis xAxis : this.domainAxes.values()) { - if (xAxis != null) { - xAxis.setPlot(this); - xAxis.addChangeListener(this); - } - } - for (ValueAxis yAxis : this.rangeAxes.values()) { - if (yAxis != null) { - yAxis.setPlot(this); - yAxis.addChangeListener(this); - } - } - for (CategoryDataset dataset : this.datasets.values()) { - if (dataset != null) { - dataset.addChangeListener(this); - } - } - for (CategoryItemRenderer renderer : this.renderers.values()) { - if (renderer != null) { - renderer.addChangeListener(this); - } - } - - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ----------------- + * CategoryPlot.java + * ----------------- + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): Jeremy Bowman; + * Arnaud Lelievre; + * Richard West, Advanced Micro Devices, Inc.; + * Ulrich Voigt - patch 2686040; + * Peter Kolb - patches 2603321 and 2809117; + * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); + * + */ + +package org.jfree.chart.plot; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.TreeMap; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.LegendItemCollection; +import org.jfree.chart.annotations.CategoryAnnotation; +import org.jfree.chart.axis.Axis; +import org.jfree.chart.axis.AxisCollection; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.AxisSpace; +import org.jfree.chart.axis.AxisState; +import org.jfree.chart.axis.CategoryAnchor; +import org.jfree.chart.axis.CategoryAxis; +import org.jfree.chart.axis.TickType; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.axis.ValueTick; +import org.jfree.chart.event.AnnotationChangeEvent; +import org.jfree.chart.event.AnnotationChangeListener; +import org.jfree.chart.event.ChartChangeEventType; +import org.jfree.chart.event.PlotChangeEvent; +import org.jfree.chart.event.RendererChangeEvent; +import org.jfree.chart.event.RendererChangeListener; +import org.jfree.chart.renderer.category.CategoryItemRenderer; +import org.jfree.chart.renderer.category.CategoryItemRendererState; +import org.jfree.chart.ui.Layer; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.chart.util.CloneUtils; +import org.jfree.chart.util.ObjectUtils; +import org.jfree.chart.util.PaintUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.ResourceBundleWrapper; +import org.jfree.chart.util.SerialUtils; +import org.jfree.chart.util.ShadowGenerator; +import org.jfree.chart.util.ShapeUtils; +import org.jfree.chart.util.SortOrder; +import org.jfree.data.Range; +import org.jfree.data.category.CategoryDataset; +import org.jfree.data.general.DatasetChangeEvent; +import org.jfree.data.general.DatasetUtils; + +/** + * A general plotting class that uses data from a {@link CategoryDataset} and + * renders each data item using a {@link CategoryItemRenderer}. + */ +public class CategoryPlot extends Plot implements ValueAxisPlot, Pannable, + Zoomable, AnnotationChangeListener, RendererChangeListener, + Cloneable, PublicCloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = -3537691700434728188L; + + /** + * The default visibility of the grid lines plotted against the domain + * axis. + */ + public static final boolean DEFAULT_DOMAIN_GRIDLINES_VISIBLE = false; + + /** + * The default visibility of the grid lines plotted against the range + * axis. + */ + public static final boolean DEFAULT_RANGE_GRIDLINES_VISIBLE = true; + + /** The default grid line stroke. */ + public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, + BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[] + {2.0f, 2.0f}, 0.0f); + + /** The default grid line paint. */ + public static final Paint DEFAULT_GRIDLINE_PAINT = Color.LIGHT_GRAY; + + /** The default value label font. */ + public static final Font DEFAULT_VALUE_LABEL_FONT = new Font("SansSerif", + Font.PLAIN, 10); + + /** + * The default crosshair visibility. + */ + public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false; + + /** + * The default crosshair stroke. + */ + public static final Stroke DEFAULT_CROSSHAIR_STROKE + = DEFAULT_GRIDLINE_STROKE; + + /** + * The default crosshair paint. + */ + public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.BLUE; + + /** The resourceBundle for the localization. */ + protected static ResourceBundle localizationResources + = ResourceBundleWrapper.getBundle( + "org.jfree.chart.plot.LocalizationBundle"); + + /** The plot orientation. */ + private PlotOrientation orientation; + + /** The offset between the data area and the axes. */ + private RectangleInsets axisOffset; + + /** Storage for the domain axes. */ + private Map domainAxes; + + /** Storage for the domain axis locations. */ + private Map domainAxisLocations; + + /** + * A flag that controls whether or not the shared domain axis is drawn + * (only relevant when the plot is being used as a subplot). + */ + private boolean drawSharedDomainAxis; + + /** Storage for the range axes. */ + private Map rangeAxes; + + /** Storage for the range axis locations. */ + private Map rangeAxisLocations; + + /** Storage for the datasets. */ + private Map datasets; + + /** + * Storage for keys that map each dataset to one or more domain axes. + * Typically a dataset is rendered using the scale of a single axis, but + * a dataset can contribute to the "auto-range" of any number of axes. + */ + private TreeMap> datasetToDomainAxesMap; + + /** + * Storage for keys that map each dataset to one or more range axes. + * Typically a dataset is rendered using the scale of a single axis, but + * a dataset can contribute to the "auto-range" of any number of axes. + */ + private TreeMap> datasetToRangeAxesMap; + + /** Storage for the renderers. */ + private Map renderers; + + /** The dataset rendering order. */ + private DatasetRenderingOrder renderingOrder + = DatasetRenderingOrder.REVERSE; + + /** + * Controls the order in which the columns are traversed when rendering the + * data items. + */ + private SortOrder columnRenderingOrder = SortOrder.ASCENDING; + + /** + * Controls the order in which the rows are traversed when rendering the + * data items. + */ + private SortOrder rowRenderingOrder = SortOrder.ASCENDING; + + /** + * A flag that controls whether the grid-lines for the domain axis are + * visible. + */ + private boolean domainGridlinesVisible; + + /** The position of the domain gridlines relative to the category. */ + private CategoryAnchor domainGridlinePosition; + + /** The stroke used to draw the domain grid-lines. */ + private transient Stroke domainGridlineStroke; + + /** The paint used to draw the domain grid-lines. */ + private transient Paint domainGridlinePaint; + + /** + * A flag that controls whether or not the zero baseline against the range + * axis is visible. + */ + private boolean rangeZeroBaselineVisible; + + /** + * The stroke used for the zero baseline against the range axis. + */ + private transient Stroke rangeZeroBaselineStroke; + + /** + * The paint used for the zero baseline against the range axis. + */ + private transient Paint rangeZeroBaselinePaint; + + /** + * A flag that controls whether the grid-lines for the range axis are + * visible. + */ + private boolean rangeGridlinesVisible; + + /** The stroke used to draw the range axis grid-lines. */ + private transient Stroke rangeGridlineStroke; + + /** The paint used to draw the range axis grid-lines. */ + private transient Paint rangeGridlinePaint; + + /** + * A flag that controls whether or not gridlines are shown for the minor + * tick values on the primary range axis. + */ + private boolean rangeMinorGridlinesVisible; + + /** + * The stroke used to draw the range minor grid-lines. + */ + private transient Stroke rangeMinorGridlineStroke; + + /** + * The paint used to draw the range minor grid-lines. + */ + private transient Paint rangeMinorGridlinePaint; + + /** The anchor value. */ + private double anchorValue; + + /** + * The index for the dataset that the crosshairs are linked to (this + * determines which axes the crosshairs are plotted against). + */ + private int crosshairDatasetIndex; + + /** + * A flag that controls the visibility of the domain crosshair. + */ + private boolean domainCrosshairVisible; + + /** + * The row key for the crosshair point. + */ + private Comparable domainCrosshairRowKey; + + /** + * The column key for the crosshair point. + */ + private Comparable domainCrosshairColumnKey; + + /** + * The stroke used to draw the domain crosshair if it is visible. + */ + private transient Stroke domainCrosshairStroke; + + /** + * The paint used to draw the domain crosshair if it is visible. + */ + private transient Paint domainCrosshairPaint; + + /** A flag that controls whether or not a range crosshair is drawn. */ + private boolean rangeCrosshairVisible; + + /** The range crosshair value. */ + private double rangeCrosshairValue; + + /** The pen/brush used to draw the crosshair (if any). */ + private transient Stroke rangeCrosshairStroke; + + /** The color used to draw the crosshair (if any). */ + private transient Paint rangeCrosshairPaint; + + /** + * A flag that controls whether or not the crosshair locks onto actual + * data points. + */ + private boolean rangeCrosshairLockedOnData = true; + + /** A map containing lists of markers for the domain axes. */ + private Map> foregroundDomainMarkers; + + /** A map containing lists of markers for the domain axes. */ + private Map> backgroundDomainMarkers; + + /** A map containing lists of markers for the range axes. */ + private Map> foregroundRangeMarkers; + + /** A map containing lists of markers for the range axes. */ + private Map> backgroundRangeMarkers; + + /** + * A (possibly empty) list of annotations for the plot. The list should + * be initialised in the constructor and never allowed to be + * {@code null}. + */ + private List annotations; + + /** + * The weight for the plot (only relevant when the plot is used as a subplot + * within a combined plot). + */ + private int weight; + + /** The fixed space for the domain axis. */ + private AxisSpace fixedDomainAxisSpace; + + /** The fixed space for the range axis. */ + private AxisSpace fixedRangeAxisSpace; + + /** + * An optional collection of legend items that can be returned by the + * getLegendItems() method. + */ + private LegendItemCollection fixedLegendItems; + + /** + * A flag that controls whether or not panning is enabled for the + * range axis/axes. + */ + private boolean rangePannable; + + /** + * The shadow generator for the plot ({@code null} permitted). + */ + private ShadowGenerator shadowGenerator; + + /** + * Default constructor. + */ + public CategoryPlot() { + this(null, null, null, null); + } + + /** + * Creates a new plot. + * + * @param dataset the dataset ({@code null} permitted). + * @param domainAxis the domain axis ({@code null} permitted). + * @param rangeAxis the range axis ({@code null} permitted). + * @param renderer the item renderer ({@code null} permitted). + * + */ + public CategoryPlot(CategoryDataset dataset, CategoryAxis domainAxis, + ValueAxis rangeAxis, CategoryItemRenderer renderer) { + + super(); + + this.orientation = PlotOrientation.VERTICAL; + + // allocate storage for dataset, axes and renderers + this.domainAxes = new HashMap<>(); + this.domainAxisLocations = new HashMap<>(); + this.rangeAxes = new HashMap<>(); + this.rangeAxisLocations = new HashMap<>(); + + this.datasetToDomainAxesMap = new TreeMap<>(); + this.datasetToRangeAxesMap = new TreeMap<>(); + + this.renderers = new HashMap<>(); + + this.datasets = new HashMap<>(); + this.datasets.put(0, dataset); + if (dataset != null) { + dataset.addChangeListener(this); + } + + this.axisOffset = RectangleInsets.ZERO_INSETS; + this.domainAxisLocations.put(0, AxisLocation.BOTTOM_OR_LEFT); + this.rangeAxisLocations.put(0, AxisLocation.TOP_OR_LEFT); + + this.renderers.put(0, renderer); + if (renderer != null) { + renderer.setPlot(this); + renderer.addChangeListener(this); + } + + this.domainAxes.put(0, domainAxis); + mapDatasetToDomainAxis(0, 0); + if (domainAxis != null) { + domainAxis.setPlot(this); + domainAxis.addChangeListener(this); + } + this.drawSharedDomainAxis = false; + + this.rangeAxes.put(0, rangeAxis); + mapDatasetToRangeAxis(0, 0); + if (rangeAxis != null) { + rangeAxis.setPlot(this); + rangeAxis.addChangeListener(this); + } + + configureDomainAxes(); + configureRangeAxes(); + + this.domainGridlinesVisible = DEFAULT_DOMAIN_GRIDLINES_VISIBLE; + this.domainGridlinePosition = CategoryAnchor.MIDDLE; + this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE; + this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT; + + this.rangeZeroBaselineVisible = false; + this.rangeZeroBaselinePaint = Color.BLACK; + this.rangeZeroBaselineStroke = new BasicStroke(0.5f); + + this.rangeGridlinesVisible = DEFAULT_RANGE_GRIDLINES_VISIBLE; + this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE; + this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT; + + this.rangeMinorGridlinesVisible = false; + this.rangeMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE; + this.rangeMinorGridlinePaint = Color.WHITE; + + this.foregroundDomainMarkers = new HashMap<>(); + this.backgroundDomainMarkers = new HashMap<>(); + this.foregroundRangeMarkers = new HashMap<>(); + this.backgroundRangeMarkers = new HashMap<>(); + + this.anchorValue = 0.0; + + this.domainCrosshairVisible = false; + this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; + this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; + + this.rangeCrosshairVisible = DEFAULT_CROSSHAIR_VISIBLE; + this.rangeCrosshairValue = 0.0; + this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; + this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; + + this.annotations = new ArrayList<>(); + + this.rangePannable = false; + this.shadowGenerator = null; + } + + /** + * Returns a string describing the type of plot. + * + * @return The type. + */ + @Override + public String getPlotType() { + return localizationResources.getString("Category_Plot"); + } + + /** + * Returns the orientation of the plot. + * + * @return The orientation of the plot (never {@code null}). + * + * @see #setOrientation(PlotOrientation) + */ + @Override + public PlotOrientation getOrientation() { + return this.orientation; + } + + /** + * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param orientation the orientation ({@code null} not permitted). + * + * @see #getOrientation() + */ + public void setOrientation(PlotOrientation orientation) { + Args.nullNotPermitted(orientation, "orientation"); + this.orientation = orientation; + fireChangeEvent(); + } + + /** + * Returns the axis offset. + * + * @return The axis offset (never {@code null}). + * + * @see #setAxisOffset(RectangleInsets) + */ + public RectangleInsets getAxisOffset() { + return this.axisOffset; + } + + /** + * Sets the axis offsets (gap between the data area and the axes) and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param offset the offset ({@code null} not permitted). + * + * @see #getAxisOffset() + */ + public void setAxisOffset(RectangleInsets offset) { + Args.nullNotPermitted(offset, "offset"); + this.axisOffset = offset; + fireChangeEvent(); + } + + /** + * Returns the domain axis for the plot. If the domain axis for this plot + * is {@code null}, then the method will return the parent plot's + * domain axis (if there is a parent plot). + * + * @return The domain axis ({@code null} permitted). + * + * @see #setDomainAxis(CategoryAxis) + */ + public CategoryAxis getDomainAxis() { + return getDomainAxis(0); + } + + /** + * Returns a domain axis. + * + * @param index the axis index. + * + * @return The axis ({@code null} possible). + * + * @see #setDomainAxis(int, CategoryAxis) + */ + public CategoryAxis getDomainAxis(int index) { + CategoryAxis result = this.domainAxes.get(index); + if (result == null) { + Plot parent = getParent(); + if (parent instanceof CategoryPlot) { + CategoryPlot cp = (CategoryPlot) parent; + result = cp.getDomainAxis(index); + } + } + return result; + } + + /** + * Returns a map containing the domain axes that are assigned to this plot. + * The map is unmodifiable. + * + * @return A map containing the domain axes that are assigned to the plot + * (never {@code null}). + * + * @since 1.5.4 + */ + public Map getDomainAxes() { + return Collections.unmodifiableMap(this.domainAxes); + } + + /** + * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param axis the axis ({@code null} permitted). + * + * @see #getDomainAxis() + */ + public void setDomainAxis(CategoryAxis axis) { + setDomainAxis(0, axis); + } + + /** + * Sets a domain axis and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param index the axis index. + * @param axis the axis ({@code null} permitted). + * + * @see #getDomainAxis(int) + */ + public void setDomainAxis(int index, CategoryAxis axis) { + setDomainAxis(index, axis, true); + } + + /** + * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param index the axis index. + * @param axis the axis ({@code null} permitted). + * @param notify notify listeners? + */ + public void setDomainAxis(int index, CategoryAxis axis, boolean notify) { + CategoryAxis existing = this.domainAxes.get(index); + if (existing != null) { + existing.removeChangeListener(this); + } + if (axis != null) { + axis.setPlot(this); + } + this.domainAxes.put(index, axis); + if (axis != null) { + axis.configure(); + axis.addChangeListener(this); + } + if (notify) { + fireChangeEvent(); + } + } + + /** + * Sets the domain axes for this plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param axes the axes ({@code null} not permitted). + * + * @see #setRangeAxes(ValueAxis[]) + */ + public void setDomainAxes(CategoryAxis[] axes) { + for (int i = 0; i < axes.length; i++) { + setDomainAxis(i, axes[i], false); + } + fireChangeEvent(); + } + + /** + * Returns the index of the specified axis, or {@code -1} if the axis + * is not assigned to the plot. + * + * @param axis the axis ({@code null} not permitted). + * + * @return The axis index. + * + * @see #getDomainAxis(int) + * @see #getRangeAxisIndex(ValueAxis) + */ + public int getDomainAxisIndex(CategoryAxis axis) { + Args.nullNotPermitted(axis, "axis"); + for (Entry entry : this.domainAxes.entrySet()) { + if (entry.getValue() == axis) { + return entry.getKey(); + } + } + return -1; + } + + /** + * Returns the domain axis location for the primary domain axis. + * + * @return The location (never {@code null}). + * + * @see #getRangeAxisLocation() + */ + public AxisLocation getDomainAxisLocation() { + return getDomainAxisLocation(0); + } + + /** + * Returns the location for a domain axis. + * + * @param index the axis index. + * + * @return The location. + * + * @see #setDomainAxisLocation(int, AxisLocation) + */ + public AxisLocation getDomainAxisLocation(int index) { + AxisLocation result = this.domainAxisLocations.get(index); + if (result == null) { + result = AxisLocation.getOpposite(getDomainAxisLocation(0)); + } + return result; + } + + /** + * Sets the location of the domain axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param location the axis location ({@code null} not permitted). + * + * @see #getDomainAxisLocation() + * @see #setDomainAxisLocation(int, AxisLocation) + */ + public void setDomainAxisLocation(AxisLocation location) { + // delegate... + setDomainAxisLocation(0, location, true); + } + + /** + * Sets the location of the domain axis and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param location the axis location ({@code null} not permitted). + * @param notify a flag that controls whether listeners are notified. + */ + public void setDomainAxisLocation(AxisLocation location, boolean notify) { + // delegate... + setDomainAxisLocation(0, location, notify); + } + + /** + * Sets the location for a domain axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param index the axis index. + * @param location the location. + * + * @see #getDomainAxisLocation(int) + * @see #setRangeAxisLocation(int, AxisLocation) + */ + public void setDomainAxisLocation(int index, AxisLocation location) { + // delegate... + setDomainAxisLocation(index, location, true); + } + + /** + * Sets the location for a domain axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param index the axis index. + * @param location the location. + * @param notify notify listeners? + * + * @see #getDomainAxisLocation(int) + * @see #setRangeAxisLocation(int, AxisLocation, boolean) + */ + public void setDomainAxisLocation(int index, AxisLocation location, + boolean notify) { + if (index == 0 && location == null) { + throw new IllegalArgumentException( + "Null 'location' for index 0 not permitted."); + } + this.domainAxisLocations.put(index, location); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the domain axis edge. This is derived from the axis location + * and the plot orientation. + * + * @return The edge (never {@code null}). + */ + public RectangleEdge getDomainAxisEdge() { + return getDomainAxisEdge(0); + } + + /** + * Returns the edge for a domain axis. + * + * @param index the axis index. + * + * @return The edge (never {@code null}). + */ + public RectangleEdge getDomainAxisEdge(int index) { + RectangleEdge result; + AxisLocation location = getDomainAxisLocation(index); + if (location != null) { + result = Plot.resolveDomainAxisLocation(location, this.orientation); + } else { + result = RectangleEdge.opposite(getDomainAxisEdge(0)); + } + return result; + } + + /** + * Returns the number of domain axes. + * + * @return The axis count. + */ + public int getDomainAxisCount() { + return this.domainAxes.size(); + } + + /** + * Clears the domain axes from the plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + */ + public void clearDomainAxes() { + for (CategoryAxis xAxis : this.domainAxes.values()) { + if (xAxis != null) { + xAxis.removeChangeListener(this); + } + } + this.domainAxes.clear(); + fireChangeEvent(); + } + + /** + * Configures the domain axes. + */ + public void configureDomainAxes() { + for (CategoryAxis xAxis : this.domainAxes.values()) { + if (xAxis != null) { + xAxis.configure(); + } + } + } + + /** + * Returns the range axis for the plot. If the range axis for this plot is + * null, then the method will return the parent plot's range axis (if there + * is a parent plot). + * + * @return The range axis (possibly {@code null}). + */ + public ValueAxis getRangeAxis() { + return getRangeAxis(0); + } + + /** + * Returns a range axis. + * + * @param index the axis index. + * + * @return The axis ({@code null} possible). + */ + public ValueAxis getRangeAxis(int index) { + ValueAxis result = this.rangeAxes.get(index); + if (result == null) { + Plot parent = getParent(); + if (parent instanceof CategoryPlot) { + CategoryPlot cp = (CategoryPlot) parent; + result = cp.getRangeAxis(index); + } + } + return result; + } + + /** + * Returns a map containing the range axes that are assigned to this plot. + * The map is unmodifiable. + * + * @return A map containing the domain axes that are assigned to the plot + * (never {@code null}). + * + * @since 1.5.4 + */ + public Map getRangeAxes() { + return Collections.unmodifiableMap(this.rangeAxes); + } + + /** + * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param axis the axis ({@code null} permitted). + */ + public void setRangeAxis(ValueAxis axis) { + setRangeAxis(0, axis); + } + + /** + * Sets a range axis and sends a {@link PlotChangeEvent} to all registered + * listeners. + * + * @param index the axis index. + * @param axis the axis. + */ + public void setRangeAxis(int index, ValueAxis axis) { + setRangeAxis(index, axis, true); + } + + /** + * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param index the axis index. + * @param axis the axis. + * @param notify notify listeners? + */ + public void setRangeAxis(int index, ValueAxis axis, boolean notify) { + ValueAxis existing = this.rangeAxes.get(index); + if (existing != null) { + existing.removeChangeListener(this); + } + if (axis != null) { + axis.setPlot(this); + } + this.rangeAxes.put(index, axis); + if (axis != null) { + axis.configure(); + axis.addChangeListener(this); + } + if (notify) { + fireChangeEvent(); + } + } + + /** + * Sets the range axes for this plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param axes the axes ({@code null} not permitted). + * + * @see #setDomainAxes(CategoryAxis[]) + */ + public void setRangeAxes(ValueAxis[] axes) { + for (int i = 0; i < axes.length; i++) { + setRangeAxis(i, axes[i], false); + } + fireChangeEvent(); + } + + /** + * Returns the index of the specified axis, or {@code -1} if the axis + * is not assigned to the plot. + * + * @param axis the axis ({@code null} not permitted). + * + * @return The axis index. + * + * @see #getRangeAxis(int) + * @see #getDomainAxisIndex(CategoryAxis) + */ + public int getRangeAxisIndex(ValueAxis axis) { + Args.nullNotPermitted(axis, "axis"); + int result = findRangeAxisIndex(axis); + if (result < 0) { // try the parent plot + Plot parent = getParent(); + if (parent instanceof CategoryPlot) { + CategoryPlot p = (CategoryPlot) parent; + result = p.getRangeAxisIndex(axis); + } + } + return result; + } + + private int findRangeAxisIndex(ValueAxis axis) { + for (Entry entry : this.rangeAxes.entrySet()) { + if (entry.getValue() == axis) { + return entry.getKey(); + } + } + return -1; + } + + /** + * Returns the range axis location. + * + * @return The location (never {@code null}). + */ + public AxisLocation getRangeAxisLocation() { + return getRangeAxisLocation(0); + } + + /** + * Returns the location for a range axis. + * + * @param index the axis index. + * + * @return The location. + * + * @see #setRangeAxisLocation(int, AxisLocation) + */ + public AxisLocation getRangeAxisLocation(int index) { + AxisLocation result = this.rangeAxisLocations.get(index); + if (result == null) { + result = AxisLocation.getOpposite(getRangeAxisLocation(0)); + } + return result; + } + + /** + * Sets the location of the range axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param location the location ({@code null} not permitted). + * + * @see #setRangeAxisLocation(AxisLocation, boolean) + * @see #setDomainAxisLocation(AxisLocation) + */ + public void setRangeAxisLocation(AxisLocation location) { + // defer argument checking... + setRangeAxisLocation(location, true); + } + + /** + * Sets the location of the range axis and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param location the location ({@code null} not permitted). + * @param notify notify listeners? + * + * @see #setDomainAxisLocation(AxisLocation, boolean) + */ + public void setRangeAxisLocation(AxisLocation location, boolean notify) { + setRangeAxisLocation(0, location, notify); + } + + /** + * Sets the location for a range axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param index the axis index. + * @param location the location. + * + * @see #getRangeAxisLocation(int) + * @see #setRangeAxisLocation(int, AxisLocation, boolean) + */ + public void setRangeAxisLocation(int index, AxisLocation location) { + setRangeAxisLocation(index, location, true); + } + + /** + * Sets the location for a range axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param index the axis index. + * @param location the location. + * @param notify notify listeners? + * + * @see #getRangeAxisLocation(int) + * @see #setDomainAxisLocation(int, AxisLocation, boolean) + */ + public void setRangeAxisLocation(int index, AxisLocation location, + boolean notify) { + if (index == 0 && location == null) { + throw new IllegalArgumentException( + "Null 'location' for index 0 not permitted."); + } + this.rangeAxisLocations.put(index, location); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the edge where the primary range axis is located. + * + * @return The edge (never {@code null}). + */ + public RectangleEdge getRangeAxisEdge() { + return getRangeAxisEdge(0); + } + + /** + * Returns the edge for a range axis. + * + * @param index the axis index. + * + * @return The edge. + */ + public RectangleEdge getRangeAxisEdge(int index) { + AxisLocation location = getRangeAxisLocation(index); + return Plot.resolveRangeAxisLocation(location, this.orientation); + } + + /** + * Returns the number of range axes. + * + * @return The axis count. + */ + public int getRangeAxisCount() { + return this.rangeAxes.size(); + } + + /** + * Clears the range axes from the plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + */ + public void clearRangeAxes() { + for (ValueAxis yAxis : this.rangeAxes.values()) { + if (yAxis != null) { + yAxis.removeChangeListener(this); + } + } + this.rangeAxes.clear(); + fireChangeEvent(); + } + + /** + * Configures the range axes. + */ + public void configureRangeAxes() { + for (ValueAxis yAxis : this.rangeAxes.values()) { + if (yAxis != null) { + yAxis.configure(); + } + } + } + + /** + * Returns the primary dataset for the plot. + * + * @return The primary dataset (possibly {@code null}). + * + * @see #setDataset(CategoryDataset) + */ + public CategoryDataset getDataset() { + return getDataset(0); + } + + /** + * Returns the dataset with the given index, or {@code null} if there is + * no dataset. + * + * @param index the dataset index (must be >= 0). + * + * @return The dataset (possibly {@code null}). + * + * @see #setDataset(int, CategoryDataset) + */ + public CategoryDataset getDataset(int index) { + return this.datasets.get(index); + } + + /** + * Returns a map containing the datasets that are assigned to this plot. + * The map is unmodifiable. + * + * @return A map containing the datasets that are assigned to the plot + * (never {@code null}). + * + * @since 1.5.4 + */ + public Map getDatasets() { + return Collections.unmodifiableMap(this.datasets); + } + + /** + * Sets the dataset for the plot, replacing the existing dataset, if there + * is one. This method also calls the + * {@link #datasetChanged(DatasetChangeEvent)} method, which adjusts the + * axis ranges if necessary and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param dataset the dataset ({@code null} permitted). + * + * @see #getDataset() + */ + public void setDataset(CategoryDataset dataset) { + setDataset(0, dataset); + } + + /** + * Sets a dataset for the plot and sends a change notification to all + * registered listeners. + * + * @param index the dataset index (must be >= 0). + * @param dataset the dataset ({@code null} permitted). + * + * @see #getDataset(int) + */ + public void setDataset(int index, CategoryDataset dataset) { + CategoryDataset existing = this.datasets.get(index); + if (existing != null) { + existing.removeChangeListener(this); + } + this.datasets.put(index, dataset); + if (dataset != null) { + dataset.addChangeListener(this); + } + // send a dataset change event to self... + DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); + datasetChanged(event); + } + + /** + * Returns the number of datasets. + * + * @return The number of datasets. + */ + public int getDatasetCount() { + return this.datasets.size(); + } + + /** + * Returns the index of the specified dataset, or {@code -1} if the + * dataset does not belong to the plot. + * + * @param dataset the dataset ({@code null} not permitted). + * + * @return The index. + */ + public int indexOf(CategoryDataset dataset) { + for (Entry entry: this.datasets.entrySet()) { + if (entry.getValue() == dataset) { + return entry.getKey(); + } + } + return -1; + } + + /** + * Maps a dataset to a particular domain axis. + * + * @param index the dataset index (zero-based). + * @param axisIndex the axis index (zero-based). + * + * @see #getDomainAxisForDataset(int) + */ + public void mapDatasetToDomainAxis(int index, int axisIndex) { + List axisIndices = new ArrayList<>(1); + axisIndices.add(axisIndex); + mapDatasetToDomainAxes(index, axisIndices); + } + + /** + * Maps the specified dataset to the axes in the list. Note that the + * conversion of data values into Java2D space is always performed using + * the first axis in the list. + * + * @param index the dataset index (zero-based). + * @param axisIndices the axis indices ({@code null} permitted). + */ + public void mapDatasetToDomainAxes(int index, List axisIndices) { + Args.requireNonNegative(index, "index"); + checkAxisIndices(axisIndices); + this.datasetToDomainAxesMap.put(index, new ArrayList<>(axisIndices)); + // fake a dataset change event to update axes... + datasetChanged(new DatasetChangeEvent(this, getDataset(index))); + } + + /** + * This method is used to perform argument checking on the list of + * axis indices passed to mapDatasetToDomainAxes() and + * mapDatasetToRangeAxes(). + * + * @param indices the list of indices ({@code null} permitted). + */ + private void checkAxisIndices(List indices) { + // axisIndices can be: + // 1. null; + // 2. non-empty, containing only Integer objects that are unique. + if (indices == null) { + return; // OK + } + int count = indices.size(); + if (count == 0) { + throw new IllegalArgumentException("Empty list not permitted."); + } + HashSet set = new HashSet<>(); + for (int i = 0; i < count; i++) { + Integer item = indices.get(i); + if (set.contains(item)) { + throw new IllegalArgumentException("Indices must be unique."); + } + set.add(item); + } + } + + /** + * Returns the domain axis for a dataset. You can change the axis for a + * dataset using the {@link #mapDatasetToDomainAxis(int, int)} method. + * + * @param index the dataset index (must be >= 0). + * + * @return The domain axis. + * + * @see #mapDatasetToDomainAxis(int, int) + */ + public CategoryAxis getDomainAxisForDataset(int index) { + Args.requireNonNegative(index, "index"); + CategoryAxis axis; + List axisIndices = this.datasetToDomainAxesMap.get(index); + if (axisIndices != null) { + // the first axis in the list is used for data <--> Java2D + Integer axisIndex = axisIndices.get(0); + axis = getDomainAxis(axisIndex); + } else { + axis = getDomainAxis(0); + } + return axis; + } + + /** + * Maps a dataset to a particular range axis. + * + * @param index the dataset index (zero-based). + * @param axisIndex the axis index (zero-based). + * + * @see #getRangeAxisForDataset(int) + */ + public void mapDatasetToRangeAxis(int index, int axisIndex) { + List axisIndices = new ArrayList<>(1); + axisIndices.add(axisIndex); + mapDatasetToRangeAxes(index, axisIndices); + } + + /** + * Maps the specified dataset to the axes in the list. Note that the + * conversion of data values into Java2D space is always performed using + * the first axis in the list. + * + * @param index the dataset index (zero-based). + * @param axisIndices the axis indices ({@code null} permitted). + */ + public void mapDatasetToRangeAxes(int index, List axisIndices) { + Args.requireNonNegative(index, "index"); + checkAxisIndices(axisIndices); + this.datasetToRangeAxesMap.put(index, new ArrayList<>(axisIndices)); + // fake a dataset change event to update axes... + datasetChanged(new DatasetChangeEvent(this, getDataset(index))); + } + + /** + * Returns the range axis for a dataset. You can change the axis for a + * dataset using the {@link #mapDatasetToRangeAxis(int, int)} method. + * + * @param index the dataset index (must be >= 0). + * + * @return The range axis. + * + * @see #mapDatasetToRangeAxis(int, int) + */ + public ValueAxis getRangeAxisForDataset(int index) { + Args.requireNonNegative(index, "index"); + ValueAxis axis; + List axisIndices = this.datasetToRangeAxesMap.get(index); + if (axisIndices != null) { + // the first axis in the list is used for data <--> Java2D + axis = getRangeAxis(axisIndices.get(0)); + } else { + axis = getRangeAxis(0); + } + return axis; + } + + /** + * Returns the number of renderer slots for this plot. + * + * @return The number of renderer slots. + */ + public int getRendererCount() { + return this.renderers.size(); + } + + /** + * Returns a reference to the renderer for the plot. + * + * @return The renderer. + * + * @see #setRenderer(CategoryItemRenderer) + */ + public CategoryItemRenderer getRenderer() { + return getRenderer(0); + } + + /** + * Returns the renderer at the given index. + * + * @param index the renderer index. + * + * @return The renderer (possibly {@code null}). + * + * @see #setRenderer(int, CategoryItemRenderer) + */ + public CategoryItemRenderer getRenderer(int index) { + CategoryItemRenderer renderer = this.renderers.get(index); + if (renderer == null) { + return this.renderers.get(0); + } + return renderer; + } + + /** + * Returns a map containing the renderers that are assigned to this plot. + * The map is unmodifiable. + * + * @return A map containing the renderers that are assigned to the plot + * (never {@code null}). + * + * @since 1.5.4 + */ + public Map getRenderers() { + return Collections.unmodifiableMap(this.renderers); + } + + /** + * Sets the renderer at index 0 (sometimes referred to as the "primary" + * renderer) and sends a change event to all registered listeners. + * + * @param renderer the renderer ({@code null} permitted. + * + * @see #getRenderer() + */ + public void setRenderer(CategoryItemRenderer renderer) { + setRenderer(0, renderer, true); + } + + /** + * Sets the renderer at index 0 (sometimes referred to as the "primary" + * renderer) and, if requested, sends a change event to all registered + * listeners. + *

+ * You can set the renderer to {@code null}, but this is not + * recommended because: + *

+ * + * @param renderer the renderer ({@code null} permitted). + * @param notify notify listeners? + * + * @see #getRenderer() + */ + public void setRenderer(CategoryItemRenderer renderer, boolean notify) { + setRenderer(0, renderer, notify); + } + + /** + * Sets the renderer to use for the dataset with the specified index and + * sends a change event to all registered listeners. Note that each + * dataset should have its own renderer, you should not use one renderer + * for multiple datasets. + * + * @param index the index. + * @param renderer the renderer ({@code null} permitted). + * + * @see #getRenderer(int) + * @see #setRenderer(int, CategoryItemRenderer, boolean) + */ + public void setRenderer(int index, CategoryItemRenderer renderer) { + setRenderer(index, renderer, true); + } + + /** + * Sets the renderer to use for the dataset with the specified index and, + * if requested, sends a change event to all registered listeners. Note + * that each dataset should have its own renderer, you should not use one + * renderer for multiple datasets. + * + * @param index the index. + * @param renderer the renderer ({@code null} permitted). + * @param notify notify listeners? + * + * @see #getRenderer(int) + */ + public void setRenderer(int index, CategoryItemRenderer renderer, + boolean notify) { + CategoryItemRenderer existing = this.renderers.get(index); + if (existing != null) { + existing.removeChangeListener(this); + } + this.renderers.put(index, renderer); + if (renderer != null) { + renderer.setPlot(this); + renderer.addChangeListener(this); + } + configureDomainAxes(); + configureRangeAxes(); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Sets the renderers for this plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param renderers the renderers. + */ + public void setRenderers(CategoryItemRenderer[] renderers) { + for (int i = 0; i < renderers.length; i++) { + setRenderer(i, renderers[i], false); + } + fireChangeEvent(); + } + + /** + * Returns the renderer for the specified dataset. If the dataset doesn't + * belong to the plot, this method will return {@code null}. + * + * @param dataset the dataset ({@code null} permitted). + * + * @return The renderer (possibly {@code null}). + */ + public CategoryItemRenderer getRendererForDataset(CategoryDataset dataset) { + int datasetIndex = indexOf(dataset); + if (datasetIndex < 0) { + return null; + } + CategoryItemRenderer renderer = this.renderers.get(datasetIndex); + if (renderer == null) { + return getRenderer(); + } + return renderer; + } + + /** + * Returns the index of the specified renderer, or {@code -1} if the + * renderer is not assigned to this plot. + * + * @param renderer the renderer ({@code null} permitted). + * + * @return The renderer index. + */ + public int getIndexOf(CategoryItemRenderer renderer) { + for (Entry entry + : this.renderers.entrySet()) { + if (entry.getValue() == renderer) { + return entry.getKey(); + } + } + return -1; + } + + /** + * Returns the dataset rendering order. + * + * @return The order (never {@code null}). + * + * @see #setDatasetRenderingOrder(DatasetRenderingOrder) + */ + public DatasetRenderingOrder getDatasetRenderingOrder() { + return this.renderingOrder; + } + + /** + * Sets the rendering order and sends a {@link PlotChangeEvent} to all + * registered listeners. By default, the plot renders the primary dataset + * last (so that the primary dataset overlays the secondary datasets). You + * can reverse this if you want to. + * + * @param order the rendering order ({@code null} not permitted). + * + * @see #getDatasetRenderingOrder() + */ + public void setDatasetRenderingOrder(DatasetRenderingOrder order) { + Args.nullNotPermitted(order, "order"); + this.renderingOrder = order; + fireChangeEvent(); + } + + /** + * Returns the order in which the columns are rendered. The default value + * is {@code SortOrder.ASCENDING}. + * + * @return The column rendering order (never {@code null}). + * + * @see #setColumnRenderingOrder(SortOrder) + */ + public SortOrder getColumnRenderingOrder() { + return this.columnRenderingOrder; + } + + /** + * Sets the column order in which the items in each dataset should be + * rendered and sends a {@link PlotChangeEvent} to all registered + * listeners. Note that this affects the order in which items are drawn, + * NOT their position in the chart. + * + * @param order the order ({@code null} not permitted). + * + * @see #getColumnRenderingOrder() + * @see #setRowRenderingOrder(SortOrder) + */ + public void setColumnRenderingOrder(SortOrder order) { + Args.nullNotPermitted(order, "order"); + this.columnRenderingOrder = order; + fireChangeEvent(); + } + + /** + * Returns the order in which the rows should be rendered. The default + * value is {@code SortOrder.ASCENDING}. + * + * @return The order (never {@code null}). + * + * @see #setRowRenderingOrder(SortOrder) + */ + public SortOrder getRowRenderingOrder() { + return this.rowRenderingOrder; + } + + /** + * Sets the row order in which the items in each dataset should be + * rendered and sends a {@link PlotChangeEvent} to all registered + * listeners. Note that this affects the order in which items are drawn, + * NOT their position in the chart. + * + * @param order the order ({@code null} not permitted). + * + * @see #getRowRenderingOrder() + * @see #setColumnRenderingOrder(SortOrder) + */ + public void setRowRenderingOrder(SortOrder order) { + Args.nullNotPermitted(order, "order"); + this.rowRenderingOrder = order; + fireChangeEvent(); + } + + /** + * Returns the flag that controls whether the domain grid-lines are visible. + * + * @return The {@code true} or {@code false}. + * + * @see #setDomainGridlinesVisible(boolean) + */ + public boolean isDomainGridlinesVisible() { + return this.domainGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not grid-lines are drawn against + * the domain axis. + *

+ * If the flag value changes, a {@link PlotChangeEvent} is sent to all + * registered listeners. + * + * @param visible the new value of the flag. + * + * @see #isDomainGridlinesVisible() + */ + public void setDomainGridlinesVisible(boolean visible) { + if (this.domainGridlinesVisible != visible) { + this.domainGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the position used for the domain gridlines. + * + * @return The gridline position (never {@code null}). + * + * @see #setDomainGridlinePosition(CategoryAnchor) + */ + public CategoryAnchor getDomainGridlinePosition() { + return this.domainGridlinePosition; + } + + /** + * Sets the position used for the domain gridlines and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param position the position ({@code null} not permitted). + * + * @see #getDomainGridlinePosition() + */ + public void setDomainGridlinePosition(CategoryAnchor position) { + Args.nullNotPermitted(position, "position"); + this.domainGridlinePosition = position; + fireChangeEvent(); + } + + /** + * Returns the stroke used to draw grid-lines against the domain axis. + * + * @return The stroke (never {@code null}). + * + * @see #setDomainGridlineStroke(Stroke) + */ + public Stroke getDomainGridlineStroke() { + return this.domainGridlineStroke; + } + + /** + * Sets the stroke used to draw grid-lines against the domain axis and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getDomainGridlineStroke() + */ + public void setDomainGridlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.domainGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint used to draw grid-lines against the domain axis. + * + * @return The paint (never {@code null}). + * + * @see #setDomainGridlinePaint(Paint) + */ + public Paint getDomainGridlinePaint() { + return this.domainGridlinePaint; + } + + /** + * Sets the paint used to draw the grid-lines (if any) against the domain + * axis and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getDomainGridlinePaint() + */ + public void setDomainGridlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.domainGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns a flag that controls whether or not a zero baseline is + * displayed for the range axis. + * + * @return A boolean. + * + * @see #setRangeZeroBaselineVisible(boolean) + */ + public boolean isRangeZeroBaselineVisible() { + return this.rangeZeroBaselineVisible; + } + + /** + * Sets the flag that controls whether or not the zero baseline is + * displayed for the range axis, and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param visible the flag. + * + * @see #isRangeZeroBaselineVisible() + */ + public void setRangeZeroBaselineVisible(boolean visible) { + this.rangeZeroBaselineVisible = visible; + fireChangeEvent(); + } + + /** + * Returns the stroke used for the zero baseline against the range axis. + * + * @return The stroke (never {@code null}). + * + * @see #setRangeZeroBaselineStroke(Stroke) + */ + public Stroke getRangeZeroBaselineStroke() { + return this.rangeZeroBaselineStroke; + } + + /** + * Sets the stroke for the zero baseline for the range axis, + * and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getRangeZeroBaselineStroke() + */ + public void setRangeZeroBaselineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.rangeZeroBaselineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the zero baseline (if any) plotted against the + * range axis. + * + * @return The paint (never {@code null}). + * + * @see #setRangeZeroBaselinePaint(Paint) + */ + public Paint getRangeZeroBaselinePaint() { + return this.rangeZeroBaselinePaint; + } + + /** + * Sets the paint for the zero baseline plotted against the range axis and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getRangeZeroBaselinePaint() + */ + public void setRangeZeroBaselinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.rangeZeroBaselinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns the flag that controls whether the range grid-lines are visible. + * + * @return The flag. + * + * @see #setRangeGridlinesVisible(boolean) + */ + public boolean isRangeGridlinesVisible() { + return this.rangeGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not grid-lines are drawn against + * the range axis. If the flag changes value, a {@link PlotChangeEvent} is + * sent to all registered listeners. + * + * @param visible the new value of the flag. + * + * @see #isRangeGridlinesVisible() + */ + public void setRangeGridlinesVisible(boolean visible) { + if (this.rangeGridlinesVisible != visible) { + this.rangeGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the stroke used to draw the grid-lines against the range axis. + * + * @return The stroke (never {@code null}). + * + * @see #setRangeGridlineStroke(Stroke) + */ + public Stroke getRangeGridlineStroke() { + return this.rangeGridlineStroke; + } + + /** + * Sets the stroke used to draw the grid-lines against the range axis and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getRangeGridlineStroke() + */ + public void setRangeGridlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.rangeGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint used to draw the grid-lines against the range axis. + * + * @return The paint (never {@code null}). + * + * @see #setRangeGridlinePaint(Paint) + */ + public Paint getRangeGridlinePaint() { + return this.rangeGridlinePaint; + } + + /** + * Sets the paint used to draw the grid lines against the range axis and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getRangeGridlinePaint() + */ + public void setRangeGridlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.rangeGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns {@code true} if the range axis minor grid is visible, and + * {@code false} otherwise. + * + * @return A boolean. + * + * @see #setRangeMinorGridlinesVisible(boolean) + */ + public boolean isRangeMinorGridlinesVisible() { + return this.rangeMinorGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not the range axis minor grid + * lines are visible. + *

+ * If the flag value is changed, a {@link PlotChangeEvent} is sent to all + * registered listeners. + * + * @param visible the new value of the flag. + * + * @see #isRangeMinorGridlinesVisible() + */ + public void setRangeMinorGridlinesVisible(boolean visible) { + if (this.rangeMinorGridlinesVisible != visible) { + this.rangeMinorGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the stroke for the minor grid lines (if any) plotted against the + * range axis. + * + * @return The stroke (never {@code null}). + * + * @see #setRangeMinorGridlineStroke(Stroke) + */ + public Stroke getRangeMinorGridlineStroke() { + return this.rangeMinorGridlineStroke; + } + + /** + * Sets the stroke for the minor grid lines plotted against the range axis, + * and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getRangeMinorGridlineStroke() + */ + public void setRangeMinorGridlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.rangeMinorGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the minor grid lines (if any) plotted against the + * range axis. + * + * @return The paint (never {@code null}). + * + * @see #setRangeMinorGridlinePaint(Paint) + */ + public Paint getRangeMinorGridlinePaint() { + return this.rangeMinorGridlinePaint; + } + + /** + * Sets the paint for the minor grid lines plotted against the range axis + * and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getRangeMinorGridlinePaint() + */ + public void setRangeMinorGridlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.rangeMinorGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns the fixed legend items, if any. + * + * @return The legend items (possibly {@code null}). + * + * @see #setFixedLegendItems(LegendItemCollection) + */ + public LegendItemCollection getFixedLegendItems() { + return this.fixedLegendItems; + } + + /** + * Sets the fixed legend items for the plot. Leave this set to + * {@code null} if you prefer the legend items to be created + * automatically. + * + * @param items the legend items ({@code null} permitted). + * + * @see #getFixedLegendItems() + */ + public void setFixedLegendItems(LegendItemCollection items) { + this.fixedLegendItems = items; + fireChangeEvent(); + } + + /** + * Returns the legend items for the plot. By default, this method creates + * a legend item for each series in each of the datasets. You can change + * this behaviour by overriding this method. + * + * @return The legend items. + */ + @Override + public LegendItemCollection getLegendItems() { + if (this.fixedLegendItems != null) { + return this.fixedLegendItems; + } + LegendItemCollection result = new LegendItemCollection(); + // get the legend items for the datasets... + for (CategoryDataset dataset: this.datasets.values()) { + if (dataset != null) { + int datasetIndex = indexOf(dataset); + CategoryItemRenderer renderer = getRenderer(datasetIndex); + if (renderer != null) { + result.addAll(renderer.getLegendItems()); + } + } + } + return result; + } + + /** + * Handles a 'click' on the plot by updating the anchor value. + * + * @param x x-coordinate of the click (in Java2D space). + * @param y y-coordinate of the click (in Java2D space). + * @param info information about the plot's dimensions. + * + */ + @Override + public void handleClick(int x, int y, PlotRenderingInfo info) { + + Rectangle2D dataArea = info.getDataArea(); + if (dataArea.contains(x, y)) { + // set the anchor value for the range axis... + double java2D = 0.0; + if (this.orientation == PlotOrientation.HORIZONTAL) { + java2D = x; + } else if (this.orientation == PlotOrientation.VERTICAL) { + java2D = y; + } + RectangleEdge edge = Plot.resolveRangeAxisLocation( + getRangeAxisLocation(), this.orientation); + double value = getRangeAxis().java2DToValue( + java2D, info.getDataArea(), edge); + setAnchorValue(value); + setRangeCrosshairValue(value); + } + + } + + /** + * Zooms (in or out) on the plot's value axis. + *

+ * If the value 0.0 is passed in as the zoom percent, the auto-range + * calculation for the axis is restored (which sets the range to include + * the minimum and maximum data values, thus displaying all the data). + * + * @param percent the zoom amount. + */ + @Override + public void zoom(double percent) { + if (percent > 0.0) { + double range = getRangeAxis().getRange().getLength(); + double scaledRange = range * percent; + getRangeAxis().setRange(this.anchorValue - scaledRange / 2.0, + this.anchorValue + scaledRange / 2.0); + } + else { + getRangeAxis().setAutoRange(true); + } + } + + /** + * Receives notification of a change to an annotation on this plot. + * + * @param event information about the event (not used here). + */ + @Override + public void annotationChanged(AnnotationChangeEvent event) { + if (getParent() != null) { + getParent().annotationChanged(event); + } else { + PlotChangeEvent e = new PlotChangeEvent(this); + notifyListeners(e); + } + } + + /** + * Receives notification of a change to the plot's dataset. + *

+ * The range axis bounds will be recalculated if necessary. + * + * @param event information about the event (not used here). + */ + @Override + public void datasetChanged(DatasetChangeEvent event) { + for (ValueAxis yAxis : this.rangeAxes.values()) { + if (yAxis != null) { + yAxis.configure(); + } + } + if (getParent() != null) { + getParent().datasetChanged(event); + } else { + PlotChangeEvent e = new PlotChangeEvent(this); + e.setType(ChartChangeEventType.DATASET_UPDATED); + notifyListeners(e); + } + + } + + /** + * Receives notification of a renderer change event. + * + * @param event the event. + */ + @Override + public void rendererChanged(RendererChangeEvent event) { + Plot parent = getParent(); + if (parent != null) { + if (parent instanceof RendererChangeListener) { + RendererChangeListener rcl = (RendererChangeListener) parent; + rcl.rendererChanged(event); + } else { + // this should never happen with the existing code, but throw + // an exception in case future changes make it possible... + throw new RuntimeException( + "The renderer has changed and I don't know what to do!"); + } + } else { + configureRangeAxes(); + PlotChangeEvent e = new PlotChangeEvent(this); + notifyListeners(e); + } + } + + /** + * Adds a marker for display (in the foreground) against the domain axis and + * sends a {@link PlotChangeEvent} to all registered listeners. Typically a + * marker will be drawn by the renderer as a line perpendicular to the + * domain axis, however this is entirely up to the renderer. + * + * @param marker the marker ({@code null} not permitted). + * + * @see #removeDomainMarker(Marker) + */ + public void addDomainMarker(CategoryMarker marker) { + addDomainMarker(marker, Layer.FOREGROUND); + } + + /** + * Adds a marker for display against the domain axis and sends a + * {@link PlotChangeEvent} to all registered listeners. Typically a marker + * will be drawn by the renderer as a line perpendicular to the domain + * axis, however this is entirely up to the renderer. + * + * @param marker the marker ({@code null} not permitted). + * @param layer the layer (foreground or background) ({@code null} + * not permitted). + * + * @see #removeDomainMarker(Marker, Layer) + */ + public void addDomainMarker(CategoryMarker marker, Layer layer) { + addDomainMarker(0, marker, layer); + } + + /** + * Adds a marker for display by a particular renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to a domain axis, however this is entirely up to the renderer. + * + * @param index the renderer index. + * @param marker the marker ({@code null} not permitted). + * @param layer the layer ({@code null} not permitted). + * + * @see #removeDomainMarker(int, Marker, Layer) + */ + public void addDomainMarker(int index, CategoryMarker marker, Layer layer) { + addDomainMarker(index, marker, layer, true); + } + + /** + * Adds a marker for display by a particular renderer and, if requested, + * sends a {@link PlotChangeEvent} to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to a domain axis, however this is entirely up to the renderer. + * + * @param index the renderer index. + * @param marker the marker ({@code null} not permitted). + * @param layer the layer ({@code null} not permitted). + * @param notify notify listeners? + * + * @see #removeDomainMarker(int, Marker, Layer, boolean) + */ + public void addDomainMarker(int index, CategoryMarker marker, Layer layer, + boolean notify) { + Args.nullNotPermitted(marker, "marker"); + Args.nullNotPermitted(layer, "layer"); + Collection markers; + if (layer == Layer.FOREGROUND) { + markers = this.foregroundDomainMarkers.get(index); + if (markers == null) { + markers = new java.util.ArrayList(); + this.foregroundDomainMarkers.put(index, markers); + } + markers.add(marker); + } else if (layer == Layer.BACKGROUND) { + markers = this.backgroundDomainMarkers.get(index); + if (markers == null) { + markers = new java.util.ArrayList(); + this.backgroundDomainMarkers.put(index, markers); + } + markers.add(marker); + } + marker.addChangeListener(this); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Clears all the domain markers for the plot and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @see #clearRangeMarkers() + */ + public void clearDomainMarkers() { + if (this.backgroundDomainMarkers != null) { + Set keys = this.backgroundDomainMarkers.keySet(); + Iterator iterator = keys.iterator(); + while (iterator.hasNext()) { + Integer key = (Integer) iterator.next(); + clearDomainMarkers(key); + } + this.backgroundDomainMarkers.clear(); + } + if (this.foregroundDomainMarkers != null) { + Set keys = this.foregroundDomainMarkers.keySet(); + Iterator iterator = keys.iterator(); + while (iterator.hasNext()) { + Integer key = (Integer) iterator.next(); + clearDomainMarkers(key); + } + this.foregroundDomainMarkers.clear(); + } + fireChangeEvent(); + } + + /** + * Returns the list of domain markers (read only) for the specified layer. + * + * @param layer the layer (foreground or background). + * + * @return The list of domain markers. + */ + public Collection getDomainMarkers(Layer layer) { + return getDomainMarkers(0, layer); + } + + /** + * Returns a collection of domain markers for a particular renderer and + * layer. + * + * @param index the renderer index. + * @param layer the layer. + * + * @return A collection of markers (possibly {@code null}). + */ + public Collection getDomainMarkers(int index, Layer layer) { + Collection result = null; + Integer key = index; + if (layer == Layer.FOREGROUND) { + result = this.foregroundDomainMarkers.get(key); + } + else if (layer == Layer.BACKGROUND) { + result = this.backgroundDomainMarkers.get(key); + } + if (result != null) { + result = Collections.unmodifiableCollection(result); + } + return result; + } + + /** + * Clears all the domain markers for the specified renderer. + * + * @param index the renderer index. + * + * @see #clearRangeMarkers(int) + */ + public void clearDomainMarkers(int index) { + Integer key = index; + if (this.backgroundDomainMarkers != null) { + Collection markers = this.backgroundDomainMarkers.get(key); + if (markers != null) { + Iterator iterator = markers.iterator(); + while (iterator.hasNext()) { + Marker m = (Marker) iterator.next(); + m.removeChangeListener(this); + } + markers.clear(); + } + } + if (this.foregroundDomainMarkers != null) { + Collection markers = this.foregroundDomainMarkers.get(key); + if (markers != null) { + Iterator iterator = markers.iterator(); + while (iterator.hasNext()) { + Marker m = (Marker) iterator.next(); + m.removeChangeListener(this); + } + markers.clear(); + } + } + fireChangeEvent(); + } + + /** + * Removes a marker for the domain axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param marker the marker. + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeDomainMarker(Marker marker) { + return removeDomainMarker(marker, Layer.FOREGROUND); + } + + /** + * Removes a marker for the domain axis in the specified layer and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param marker the marker ({@code null} not permitted). + * @param layer the layer (foreground or background). + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeDomainMarker(Marker marker, Layer layer) { + return removeDomainMarker(0, marker, layer); + } + + /** + * Removes a marker for a specific dataset/renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the dataset/renderer index. + * @param marker the marker. + * @param layer the layer (foreground or background). + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeDomainMarker(int index, Marker marker, Layer layer) { + return removeDomainMarker(index, marker, layer, true); + } + + /** + * Removes a marker for a specific dataset/renderer and, if requested, + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param index the dataset/renderer index. + * @param marker the marker. + * @param layer the layer (foreground or background). + * @param notify notify listeners? + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeDomainMarker(int index, Marker marker, Layer layer, + boolean notify) { + ArrayList markers; + if (layer == Layer.FOREGROUND) { + markers = (ArrayList) this.foregroundDomainMarkers.get(index); + } else { + markers = (ArrayList) this.backgroundDomainMarkers.get(index); + } + if (markers == null) { + return false; + } + boolean removed = markers.remove(marker); + if (removed && notify) { + fireChangeEvent(); + } + return removed; + } + + /** + * Adds a marker for display (in the foreground) against the range axis and + * sends a {@link PlotChangeEvent} to all registered listeners. Typically a + * marker will be drawn by the renderer as a line perpendicular to the + * range axis, however this is entirely up to the renderer. + * + * @param marker the marker ({@code null} not permitted). + * + * @see #removeRangeMarker(Marker) + */ + public void addRangeMarker(Marker marker) { + addRangeMarker(marker, Layer.FOREGROUND); + } + + /** + * Adds a marker for display against the range axis and sends a + * {@link PlotChangeEvent} to all registered listeners. Typically a marker + * will be drawn by the renderer as a line perpendicular to the range axis, + * however this is entirely up to the renderer. + * + * @param marker the marker ({@code null} not permitted). + * @param layer the layer (foreground or background) ({@code null} + * not permitted). + * + * @see #removeRangeMarker(Marker, Layer) + */ + public void addRangeMarker(Marker marker, Layer layer) { + addRangeMarker(0, marker, layer); + } + + /** + * Adds a marker for display by a particular renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to a range axis, however this is entirely up to the renderer. + * + * @param index the renderer index. + * @param marker the marker. + * @param layer the layer. + * + * @see #removeRangeMarker(int, Marker, Layer) + */ + public void addRangeMarker(int index, Marker marker, Layer layer) { + addRangeMarker(index, marker, layer, true); + } + + /** + * Adds a marker for display by a particular renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to a range axis, however this is entirely up to the renderer. + * + * @param index the renderer index. + * @param marker the marker. + * @param layer the layer. + * @param notify notify listeners? + * + * @see #removeRangeMarker(int, Marker, Layer, boolean) + */ + public void addRangeMarker(int index, Marker marker, Layer layer, + boolean notify) { + Collection markers; + if (layer == Layer.FOREGROUND) { + markers = this.foregroundRangeMarkers.get(index); + if (markers == null) { + markers = new java.util.ArrayList(); + this.foregroundRangeMarkers.put(index, markers); + } + markers.add(marker); + } else if (layer == Layer.BACKGROUND) { + markers = this.backgroundRangeMarkers.get(index); + if (markers == null) { + markers = new java.util.ArrayList(); + this.backgroundRangeMarkers.put(index, markers); + } + markers.add(marker); + } + marker.addChangeListener(this); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Clears all the range markers for the plot and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @see #clearDomainMarkers() + */ + public void clearRangeMarkers() { + if (this.backgroundRangeMarkers != null) { + Set keys = this.backgroundRangeMarkers.keySet(); + Iterator iterator = keys.iterator(); + while (iterator.hasNext()) { + Integer key = (Integer) iterator.next(); + clearRangeMarkers(key); + } + this.backgroundRangeMarkers.clear(); + } + if (this.foregroundRangeMarkers != null) { + Set keys = this.foregroundRangeMarkers.keySet(); + Iterator iterator = keys.iterator(); + while (iterator.hasNext()) { + Integer key = (Integer) iterator.next(); + clearRangeMarkers(key); + } + this.foregroundRangeMarkers.clear(); + } + fireChangeEvent(); + } + + /** + * Returns the list of range markers (read only) for the specified layer. + * + * @param layer the layer (foreground or background). + * + * @return The list of range markers. + * + * @see #getRangeMarkers(int, Layer) + */ + public Collection getRangeMarkers(Layer layer) { + return getRangeMarkers(0, layer); + } + + /** + * Returns a collection of range markers for a particular renderer and + * layer. + * + * @param index the renderer index. + * @param layer the layer. + * + * @return A collection of markers (possibly {@code null}). + */ + public Collection getRangeMarkers(int index, Layer layer) { + Collection result = null; + if (layer == Layer.FOREGROUND) { + result = this.foregroundRangeMarkers.get(index); + } + else if (layer == Layer.BACKGROUND) { + result = this.backgroundRangeMarkers.get(index); + } + if (result != null) { + result = Collections.unmodifiableCollection(result); + } + return result; + } + + /** + * Clears all the range markers for the specified renderer. + * + * @param index the renderer index. + * + * @see #clearDomainMarkers(int) + */ + public void clearRangeMarkers(int index) { + Integer key = index; + if (this.backgroundRangeMarkers != null) { + Collection markers = this.backgroundRangeMarkers.get(key); + if (markers != null) { + Iterator iterator = markers.iterator(); + while (iterator.hasNext()) { + Marker m = (Marker) iterator.next(); + m.removeChangeListener(this); + } + markers.clear(); + } + } + if (this.foregroundRangeMarkers != null) { + Collection markers = this.foregroundRangeMarkers.get(key); + if (markers != null) { + Iterator iterator = markers.iterator(); + while (iterator.hasNext()) { + Marker m = (Marker) iterator.next(); + m.removeChangeListener(this); + } + markers.clear(); + } + } + fireChangeEvent(); + } + + /** + * Removes a marker for the range axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param marker the marker. + * + * @return A boolean indicating whether or not the marker was actually + * removed. + * + * @see #addRangeMarker(Marker) + */ + public boolean removeRangeMarker(Marker marker) { + return removeRangeMarker(marker, Layer.FOREGROUND); + } + + /** + * Removes a marker for the range axis in the specified layer and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param marker the marker ({@code null} not permitted). + * @param layer the layer (foreground or background). + * + * @return A boolean indicating whether or not the marker was actually + * removed. + * + * @see #addRangeMarker(Marker, Layer) + */ + public boolean removeRangeMarker(Marker marker, Layer layer) { + return removeRangeMarker(0, marker, layer); + } + + /** + * Removes a marker for a specific dataset/renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the dataset/renderer index. + * @param marker the marker. + * @param layer the layer (foreground or background). + * + * @return A boolean indicating whether or not the marker was actually + * removed. + * + * @see #addRangeMarker(int, Marker, Layer) + */ + public boolean removeRangeMarker(int index, Marker marker, Layer layer) { + return removeRangeMarker(index, marker, layer, true); + } + + /** + * Removes a marker for a specific dataset/renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the dataset/renderer index. + * @param marker the marker. + * @param layer the layer (foreground or background). + * @param notify notify listeners. + * + * @return A boolean indicating whether or not the marker was actually + * removed. + * + * @see #addRangeMarker(int, Marker, Layer, boolean) + */ + public boolean removeRangeMarker(int index, Marker marker, Layer layer, + boolean notify) { + Args.nullNotPermitted(marker, "marker"); + ArrayList markers; + if (layer == Layer.FOREGROUND) { + markers = (ArrayList) this.foregroundRangeMarkers.get(index); + } else { + markers = (ArrayList) this.backgroundRangeMarkers.get(index); + } + if (markers == null) { + return false; + } + boolean removed = markers.remove(marker); + if (removed && notify) { + fireChangeEvent(); + } + return removed; + } + + /** + * Returns the flag that controls whether or not the domain crosshair is + * displayed by the plot. + * + * @return A boolean. + * + * @see #setDomainCrosshairVisible(boolean) + */ + public boolean isDomainCrosshairVisible() { + return this.domainCrosshairVisible; + } + + /** + * Sets the flag that controls whether or not the domain crosshair is + * displayed by the plot, and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param flag the new flag value. + * + * @see #isDomainCrosshairVisible() + * @see #setRangeCrosshairVisible(boolean) + */ + public void setDomainCrosshairVisible(boolean flag) { + if (this.domainCrosshairVisible != flag) { + this.domainCrosshairVisible = flag; + fireChangeEvent(); + } + } + + /** + * Returns the row key for the domain crosshair. + * + * @return The row key. + */ + public Comparable getDomainCrosshairRowKey() { + return this.domainCrosshairRowKey; + } + + /** + * Sets the row key for the domain crosshair and sends a + * {PlotChangeEvent} to all registered listeners. + * + * @param key the key. + */ + public void setDomainCrosshairRowKey(Comparable key) { + setDomainCrosshairRowKey(key, true); + } + + /** + * Sets the row key for the domain crosshair and, if requested, sends a + * {PlotChangeEvent} to all registered listeners. + * + * @param key the key. + * @param notify notify listeners? + */ + public void setDomainCrosshairRowKey(Comparable key, boolean notify) { + this.domainCrosshairRowKey = key; + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the column key for the domain crosshair. + * + * @return The column key. + */ + public Comparable getDomainCrosshairColumnKey() { + return this.domainCrosshairColumnKey; + } + + /** + * Sets the column key for the domain crosshair and sends + * a {@link PlotChangeEvent} to all registered listeners. + * + * @param key the key. + */ + public void setDomainCrosshairColumnKey(Comparable key) { + setDomainCrosshairColumnKey(key, true); + } + + /** + * Sets the column key for the domain crosshair and, if requested, sends + * a {@link PlotChangeEvent} to all registered listeners. + * + * @param key the key. + * @param notify notify listeners? + */ + public void setDomainCrosshairColumnKey(Comparable key, boolean notify) { + this.domainCrosshairColumnKey = key; + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the dataset index for the crosshair. + * + * @return The dataset index. + */ + public int getCrosshairDatasetIndex() { + return this.crosshairDatasetIndex; + } + + /** + * Sets the dataset index for the crosshair and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the index. + */ + public void setCrosshairDatasetIndex(int index) { + setCrosshairDatasetIndex(index, true); + } + + /** + * Sets the dataset index for the crosshair and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the index. + * @param notify notify listeners? + */ + public void setCrosshairDatasetIndex(int index, boolean notify) { + this.crosshairDatasetIndex = index; + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the paint used to draw the domain crosshair. + * + * @return The paint (never {@code null}). + * + * @see #setDomainCrosshairPaint(Paint) + * @see #getDomainCrosshairStroke() + */ + public Paint getDomainCrosshairPaint() { + return this.domainCrosshairPaint; + } + + /** + * Sets the paint used to draw the domain crosshair. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getDomainCrosshairPaint() + */ + public void setDomainCrosshairPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.domainCrosshairPaint = paint; + fireChangeEvent(); + } + + /** + * Returns the stroke used to draw the domain crosshair. + * + * @return The stroke (never {@code null}). + * + * @see #setDomainCrosshairStroke(Stroke) + * @see #getDomainCrosshairPaint() + */ + public Stroke getDomainCrosshairStroke() { + return this.domainCrosshairStroke; + } + + /** + * Sets the stroke used to draw the domain crosshair, and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getDomainCrosshairStroke() + */ + public void setDomainCrosshairStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.domainCrosshairStroke = stroke; + } + + /** + * Returns a flag indicating whether or not the range crosshair is visible. + * + * @return The flag. + * + * @see #setRangeCrosshairVisible(boolean) + */ + public boolean isRangeCrosshairVisible() { + return this.rangeCrosshairVisible; + } + + /** + * Sets the flag indicating whether or not the range crosshair is visible. + * + * @param flag the new value of the flag. + * + * @see #isRangeCrosshairVisible() + */ + public void setRangeCrosshairVisible(boolean flag) { + if (this.rangeCrosshairVisible != flag) { + this.rangeCrosshairVisible = flag; + fireChangeEvent(); + } + } + + /** + * Returns a flag indicating whether or not the crosshair should "lock-on" + * to actual data values. + * + * @return The flag. + * + * @see #setRangeCrosshairLockedOnData(boolean) + */ + public boolean isRangeCrosshairLockedOnData() { + return this.rangeCrosshairLockedOnData; + } + + /** + * Sets the flag indicating whether or not the range crosshair should + * "lock-on" to actual data values, and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param flag the flag. + * + * @see #isRangeCrosshairLockedOnData() + */ + public void setRangeCrosshairLockedOnData(boolean flag) { + if (this.rangeCrosshairLockedOnData != flag) { + this.rangeCrosshairLockedOnData = flag; + fireChangeEvent(); + } + } + + /** + * Returns the range crosshair value. + * + * @return The value. + * + * @see #setRangeCrosshairValue(double) + */ + public double getRangeCrosshairValue() { + return this.rangeCrosshairValue; + } + + /** + * Sets the range crosshair value and, if the crosshair is visible, sends + * a {@link PlotChangeEvent} to all registered listeners. + * + * @param value the new value. + * + * @see #getRangeCrosshairValue() + */ + public void setRangeCrosshairValue(double value) { + setRangeCrosshairValue(value, true); + } + + /** + * Sets the range crosshair value and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners (but only if the + * crosshair is visible). + * + * @param value the new value. + * @param notify a flag that controls whether or not listeners are + * notified. + * + * @see #getRangeCrosshairValue() + */ + public void setRangeCrosshairValue(double value, boolean notify) { + this.rangeCrosshairValue = value; + if (isRangeCrosshairVisible() && notify) { + fireChangeEvent(); + } + } + + /** + * Returns the pen-style ({@code Stroke}) used to draw the crosshair + * (if visible). + * + * @return The crosshair stroke (never {@code null}). + * + * @see #setRangeCrosshairStroke(Stroke) + * @see #isRangeCrosshairVisible() + * @see #getRangeCrosshairPaint() + */ + public Stroke getRangeCrosshairStroke() { + return this.rangeCrosshairStroke; + } + + /** + * Sets the pen-style ({@code Stroke}) used to draw the range + * crosshair (if visible), and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param stroke the new crosshair stroke ({@code null} not + * permitted). + * + * @see #getRangeCrosshairStroke() + */ + public void setRangeCrosshairStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.rangeCrosshairStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint used to draw the range crosshair. + * + * @return The paint (never {@code null}). + * + * @see #setRangeCrosshairPaint(Paint) + * @see #isRangeCrosshairVisible() + * @see #getRangeCrosshairStroke() + */ + public Paint getRangeCrosshairPaint() { + return this.rangeCrosshairPaint; + } + + /** + * Sets the paint used to draw the range crosshair (if visible) and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getRangeCrosshairPaint() + */ + public void setRangeCrosshairPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.rangeCrosshairPaint = paint; + fireChangeEvent(); + } + + /** + * Returns the list of annotations. + * + * @return The list of annotations (never {@code null}). + * + * @see #addAnnotation(CategoryAnnotation) + * @see #clearAnnotations() + */ + public List getAnnotations() { + return this.annotations; + } + + /** + * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param annotation the annotation ({@code null} not permitted). + * + * @see #removeAnnotation(CategoryAnnotation) + */ + public void addAnnotation(CategoryAnnotation annotation) { + addAnnotation(annotation, true); + } + + /** + * Adds an annotation to the plot and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param annotation the annotation ({@code null} not permitted). + * @param notify notify listeners? + */ + public void addAnnotation(CategoryAnnotation annotation, boolean notify) { + Args.nullNotPermitted(annotation, "annotation"); + this.annotations.add(annotation); + annotation.addChangeListener(this); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Removes an annotation from the plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param annotation the annotation ({@code null} not permitted). + * + * @return A boolean (indicates whether or not the annotation was removed). + * + * @see #addAnnotation(CategoryAnnotation) + */ + public boolean removeAnnotation(CategoryAnnotation annotation) { + return removeAnnotation(annotation, true); + } + + /** + * Removes an annotation from the plot and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param annotation the annotation ({@code null} not permitted). + * @param notify notify listeners? + * + * @return A boolean (indicates whether or not the annotation was removed). + */ + public boolean removeAnnotation(CategoryAnnotation annotation, + boolean notify) { + Args.nullNotPermitted(annotation, "annotation"); + boolean removed = this.annotations.remove(annotation); + annotation.removeChangeListener(this); + if (removed && notify) { + fireChangeEvent(); + } + return removed; + } + + /** + * Clears all the annotations and sends a {@link PlotChangeEvent} to all + * registered listeners. + */ + public void clearAnnotations() { + for (int i = 0; i < this.annotations.size(); i++) { + CategoryAnnotation annotation = this.annotations.get(i); + annotation.removeChangeListener(this); + } + this.annotations.clear(); + fireChangeEvent(); + } + + /** + * Returns the shadow generator for the plot, if any. + * + * @return The shadow generator (possibly {@code null}). + */ + public ShadowGenerator getShadowGenerator() { + return this.shadowGenerator; + } + + /** + * Sets the shadow generator for the plot and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param generator the generator ({@code null} permitted). + */ + public void setShadowGenerator(ShadowGenerator generator) { + this.shadowGenerator = generator; + fireChangeEvent(); + } + + /** + * Calculates the space required for the domain axis/axes. + * + * @param g2 the graphics device. + * @param plotArea the plot area. + * @param space a carrier for the result ({@code null} permitted). + * + * @return The required space. + */ + protected AxisSpace calculateDomainAxisSpace(Graphics2D g2, + Rectangle2D plotArea, AxisSpace space) { + + if (space == null) { + space = new AxisSpace(); + } + + // reserve some space for the domain axis... + if (this.fixedDomainAxisSpace != null) { + if (this.orientation.isHorizontal()) { + space.ensureAtLeast( + this.fixedDomainAxisSpace.getLeft(), RectangleEdge.LEFT); + space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(), + RectangleEdge.RIGHT); + } else if (this.orientation.isVertical()) { + space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(), + RectangleEdge.TOP); + space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(), + RectangleEdge.BOTTOM); + } + } + else { + // reserve space for the primary domain axis... + RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( + getDomainAxisLocation(), this.orientation); + if (this.drawSharedDomainAxis) { + space = getDomainAxis().reserveSpace(g2, this, plotArea, + domainEdge, space); + } + + // reserve space for any domain axes... + for (CategoryAxis xAxis : this.domainAxes.values()) { + if (xAxis != null) { + int i = getDomainAxisIndex(xAxis); + RectangleEdge edge = getDomainAxisEdge(i); + space = xAxis.reserveSpace(g2, this, plotArea, edge, space); + } + } + } + + return space; + + } + + /** + * Calculates the space required for the range axis/axes. + * + * @param g2 the graphics device. + * @param plotArea the plot area. + * @param space a carrier for the result ({@code null} permitted). + * + * @return The required space. + */ + protected AxisSpace calculateRangeAxisSpace(Graphics2D g2, + Rectangle2D plotArea, AxisSpace space) { + + if (space == null) { + space = new AxisSpace(); + } + + // reserve some space for the range axis... + if (this.fixedRangeAxisSpace != null) { + if (this.orientation.isHorizontal()) { + space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(), + RectangleEdge.TOP); + space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(), + RectangleEdge.BOTTOM); + } else if (this.orientation == PlotOrientation.VERTICAL) { + space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(), + RectangleEdge.LEFT); + space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(), + RectangleEdge.RIGHT); + } + } else { + // reserve space for the range axes (if any)... + for (ValueAxis yAxis : this.rangeAxes.values()) { + if (yAxis != null) { + int i = findRangeAxisIndex(yAxis); + RectangleEdge edge = getRangeAxisEdge(i); + space = yAxis.reserveSpace(g2, this, plotArea, edge, space); + } + } + } + return space; + + } + + /** + * Trims a rectangle to integer coordinates. + * + * @param rect the incoming rectangle. + * + * @return A rectangle with integer coordinates. + */ + private Rectangle integerise(Rectangle2D rect) { + int x0 = (int) Math.ceil(rect.getMinX()); + int y0 = (int) Math.ceil(rect.getMinY()); + int x1 = (int) Math.floor(rect.getMaxX()); + int y1 = (int) Math.floor(rect.getMaxY()); + return new Rectangle(x0, y0, (x1 - x0), (y1 - y0)); + } + + /** + * Calculates the space required for the axes. + * + * @param g2 the graphics device. + * @param plotArea the plot area. + * + * @return The space required for the axes. + */ + protected AxisSpace calculateAxisSpace(Graphics2D g2, + Rectangle2D plotArea) { + AxisSpace space = new AxisSpace(); + space = calculateRangeAxisSpace(g2, plotArea, space); + space = calculateDomainAxisSpace(g2, plotArea, space); + return space; + } + + /** + * Draws the plot on a Java 2D graphics device (such as the screen or a + * printer). + *

+ * At your option, you may supply an instance of {@link PlotRenderingInfo}. + * If you do, it will be populated with information about the drawing, + * including various plot dimensions and tooltip info. + * + * @param g2 the graphics device. + * @param area the area within which the plot (including axes) should + * be drawn. + * @param anchor the anchor point ({@code null} permitted). + * @param parentState the state from the parent plot, if there is one. + * @param state collects info as the chart is drawn (possibly + * {@code null}). + */ + @Override + public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, + PlotState parentState, PlotRenderingInfo state) { + + // if the plot area is too small, just return... + boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); + boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); + if (b1 || b2) { + return; + } + + // record the plot area... + if (state == null) { + // if the incoming state is null, no information will be passed + // back to the caller - but we create a temporary state to record + // the plot area, since that is used later by the axes + state = new PlotRenderingInfo(null); + } + state.setPlotArea(area); + + // adjust the drawing area for the plot insets (if any)... + RectangleInsets insets = getInsets(); + insets.trim(area); + + // calculate the data area... + AxisSpace space = calculateAxisSpace(g2, area); + Rectangle2D dataArea = space.shrink(area, null); + this.axisOffset.trim(dataArea); + dataArea = integerise(dataArea); + if (dataArea.isEmpty()) { + return; + } + state.setDataArea(dataArea); + createAndAddEntity((Rectangle2D) dataArea.clone(), state, null, null); + + // if there is a renderer, it draws the background, otherwise use the + // default background... + if (getRenderer() != null) { + getRenderer().drawBackground(g2, this, dataArea); + } else { + drawBackground(g2, dataArea); + } + + Map axisStateMap = drawAxes(g2, area, dataArea, state); + + // the anchor point is typically the point where the mouse last + // clicked - the crosshairs will be driven off this point... + if (anchor != null && !dataArea.contains(anchor)) { + anchor = ShapeUtils.getPointInRectangle(anchor.getX(), + anchor.getY(), dataArea); + } + CategoryCrosshairState crosshairState = new CategoryCrosshairState(); + crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY); + crosshairState.setAnchor(anchor); + + // specify the anchor X and Y coordinates in Java2D space, for the + // cases where these are not updated during rendering (i.e. no lock + // on data) + crosshairState.setAnchorX(Double.NaN); + crosshairState.setAnchorY(Double.NaN); + if (anchor != null) { + ValueAxis rangeAxis = getRangeAxis(); + if (rangeAxis != null) { + double y; + if (getOrientation() == PlotOrientation.VERTICAL) { + y = rangeAxis.java2DToValue(anchor.getY(), dataArea, + getRangeAxisEdge()); + } + else { + y = rangeAxis.java2DToValue(anchor.getX(), dataArea, + getRangeAxisEdge()); + } + crosshairState.setAnchorY(y); + } + } + crosshairState.setRowKey(getDomainCrosshairRowKey()); + crosshairState.setColumnKey(getDomainCrosshairColumnKey()); + crosshairState.setCrosshairY(getRangeCrosshairValue()); + + // don't let anyone draw outside the data area + Shape savedClip = g2.getClip(); + g2.clip(dataArea); + + drawDomainGridlines(g2, dataArea); + + AxisState rangeAxisState = (AxisState) axisStateMap.get(getRangeAxis()); + if (rangeAxisState == null) { + if (parentState != null) { + rangeAxisState = (AxisState) parentState.getSharedAxisStates() + .get(getRangeAxis()); + } + } + if (rangeAxisState != null) { + drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks()); + drawZeroRangeBaseline(g2, dataArea); + } + + Graphics2D savedG2 = g2; + BufferedImage dataImage = null; + boolean suppressShadow = Boolean.TRUE.equals(g2.getRenderingHint( + JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION)); + if (this.shadowGenerator != null && !suppressShadow) { + dataImage = new BufferedImage((int) dataArea.getWidth(), + (int)dataArea.getHeight(), BufferedImage.TYPE_INT_ARGB); + g2 = dataImage.createGraphics(); + g2.translate(-dataArea.getX(), -dataArea.getY()); + g2.setRenderingHints(savedG2.getRenderingHints()); + } + + // draw the markers... + for (CategoryItemRenderer renderer : this.renderers.values()) { + int i = getIndexOf(renderer); + drawDomainMarkers(g2, dataArea, i, Layer.BACKGROUND); + } + for (CategoryItemRenderer renderer : this.renderers.values()) { + int i = getIndexOf(renderer); + drawRangeMarkers(g2, dataArea, i, Layer.BACKGROUND); + } + + // now render data items... + boolean foundData = false; + + // set up the alpha-transparency... + Composite originalComposite = g2.getComposite(); + g2.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, getForegroundAlpha())); + + DatasetRenderingOrder order = getDatasetRenderingOrder(); + List datasetIndices = getDatasetIndices(order); + for (int i : datasetIndices) { + foundData = render(g2, dataArea, i, state, crosshairState) + || foundData; + } + + // draw the foreground markers... + List rendererIndices = getRendererIndices(order); + for (int i : rendererIndices) { + drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND); + } + for (int i : rendererIndices) { + drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND); + } + + // draw the annotations (if any)... + drawAnnotations(g2, dataArea); + + if (this.shadowGenerator != null && !suppressShadow) { + BufferedImage shadowImage = this.shadowGenerator.createDropShadow( + dataImage); + g2 = savedG2; + g2.drawImage(shadowImage, (int) dataArea.getX() + + this.shadowGenerator.calculateOffsetX(), + (int) dataArea.getY() + + this.shadowGenerator.calculateOffsetY(), null); + g2.drawImage(dataImage, (int) dataArea.getX(), + (int) dataArea.getY(), null); + } + g2.setClip(savedClip); + g2.setComposite(originalComposite); + + if (!foundData) { + drawNoDataMessage(g2, dataArea); + } + + int datasetIndex = crosshairState.getDatasetIndex(); + setCrosshairDatasetIndex(datasetIndex, false); + + // draw domain crosshair if required... + Comparable rowKey = crosshairState.getRowKey(); + Comparable columnKey = crosshairState.getColumnKey(); + setDomainCrosshairRowKey(rowKey, false); + setDomainCrosshairColumnKey(columnKey, false); + if (isDomainCrosshairVisible() && columnKey != null) { + Paint paint = getDomainCrosshairPaint(); + Stroke stroke = getDomainCrosshairStroke(); + drawDomainCrosshair(g2, dataArea, this.orientation, + datasetIndex, rowKey, columnKey, stroke, paint); + } + + // draw range crosshair if required... + ValueAxis yAxis = getRangeAxisForDataset(datasetIndex); + RectangleEdge yAxisEdge = getRangeAxisEdge(); + if (!this.rangeCrosshairLockedOnData && anchor != null) { + double yy; + if (getOrientation() == PlotOrientation.VERTICAL) { + yy = yAxis.java2DToValue(anchor.getY(), dataArea, yAxisEdge); + } + else { + yy = yAxis.java2DToValue(anchor.getX(), dataArea, yAxisEdge); + } + crosshairState.setCrosshairY(yy); + } + setRangeCrosshairValue(crosshairState.getCrosshairY(), false); + if (isRangeCrosshairVisible()) { + double y = getRangeCrosshairValue(); + Paint paint = getRangeCrosshairPaint(); + Stroke stroke = getRangeCrosshairStroke(); + drawRangeCrosshair(g2, dataArea, getOrientation(), y, yAxis, + stroke, paint); + } + + // draw an outline around the plot area... + if (isOutlineVisible()) { + if (getRenderer() != null) { + getRenderer().drawOutline(g2, this, dataArea); + } + else { + drawOutline(g2, dataArea); + } + } + + } + + /** + * Returns the indices of the non-null datasets in the specified order. + * + * @param order the order ({@code null} not permitted). + * + * @return The list of indices. + */ + private List getDatasetIndices(DatasetRenderingOrder order) { + List result = new ArrayList<>(); + for (Map.Entry entry : + this.datasets.entrySet()) { + if (entry.getValue() != null) { + result.add(entry.getKey()); + } + } + Collections.sort(result); + if (order == DatasetRenderingOrder.REVERSE) { + Collections.reverse(result); + } + return result; + } + + /** + * Returns the indices of the non-null renderers for the plot, in the + * specified order. + * + * @param order the rendering order {@code null} not permitted). + * + * @return A list of indices. + */ + private List getRendererIndices(DatasetRenderingOrder order) { + List result = new ArrayList<>(); + for (Map.Entry entry: + this.renderers.entrySet()) { + if (entry.getValue() != null) { + result.add(entry.getKey()); + } + } + Collections.sort(result); + if (order == DatasetRenderingOrder.REVERSE) { + Collections.reverse(result); + } + return result; + } + + /** + * Draws the plot background (the background color and/or image). + *

+ * This method will be called during the chart drawing process and is + * declared public so that it can be accessed by the renderers used by + * certain subclasses. You shouldn't need to call this method directly. + * + * @param g2 the graphics device. + * @param area the area within which the plot should be drawn. + */ + @Override + public void drawBackground(Graphics2D g2, Rectangle2D area) { + fillBackground(g2, area, this.orientation); + drawBackgroundImage(g2, area); + } + + /** + * A utility method for drawing the plot's axes. + * + * @param g2 the graphics device. + * @param plotArea the plot area. + * @param dataArea the data area. + * @param plotState collects information about the plot ({@code null} + * permitted). + * + * @return A map containing the axis states. + */ + protected Map drawAxes(Graphics2D g2, Rectangle2D plotArea, + Rectangle2D dataArea, PlotRenderingInfo plotState) { + + AxisCollection axisCollection = new AxisCollection(); + + // add domain axes to lists... + for (CategoryAxis xAxis : this.domainAxes.values()) { + if (xAxis != null) { + int index = getDomainAxisIndex(xAxis); + axisCollection.add(xAxis, getDomainAxisEdge(index)); + } + } + + // add range axes to lists... + for (ValueAxis yAxis : this.rangeAxes.values()) { + if (yAxis != null) { + int index = findRangeAxisIndex(yAxis); + axisCollection.add(yAxis, getRangeAxisEdge(index)); + } + } + + Map axisStateMap = new HashMap(); + + // draw the top axes + double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset( + dataArea.getHeight()); + Iterator iterator = axisCollection.getAxesAtTop().iterator(); + while (iterator.hasNext()) { + Axis axis = (Axis) iterator.next(); + if (axis != null) { + AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.TOP, plotState); + cursor = axisState.getCursor(); + axisStateMap.put(axis, axisState); + } + } + + // draw the bottom axes + cursor = dataArea.getMaxY() + + this.axisOffset.calculateBottomOutset(dataArea.getHeight()); + iterator = axisCollection.getAxesAtBottom().iterator(); + while (iterator.hasNext()) { + Axis axis = (Axis) iterator.next(); + if (axis != null) { + AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.BOTTOM, plotState); + cursor = axisState.getCursor(); + axisStateMap.put(axis, axisState); + } + } + + // draw the left axes + cursor = dataArea.getMinX() + - this.axisOffset.calculateLeftOutset(dataArea.getWidth()); + iterator = axisCollection.getAxesAtLeft().iterator(); + while (iterator.hasNext()) { + Axis axis = (Axis) iterator.next(); + if (axis != null) { + AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.LEFT, plotState); + cursor = axisState.getCursor(); + axisStateMap.put(axis, axisState); + } + } + + // draw the right axes + cursor = dataArea.getMaxX() + + this.axisOffset.calculateRightOutset(dataArea.getWidth()); + iterator = axisCollection.getAxesAtRight().iterator(); + while (iterator.hasNext()) { + Axis axis = (Axis) iterator.next(); + if (axis != null) { + AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.RIGHT, plotState); + cursor = axisState.getCursor(); + axisStateMap.put(axis, axisState); + } + } + + return axisStateMap; + + } + + /** + * Draws a representation of a dataset within the dataArea region using the + * appropriate renderer. + * + * @param g2 the graphics device. + * @param dataArea the region in which the data is to be drawn. + * @param index the dataset and renderer index. + * @param info an optional object for collection dimension information. + * @param crosshairState a state object for tracking crosshair info + * ({@code null} permitted). + * + * @return A boolean that indicates whether or not real data was found. + */ + public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, + PlotRenderingInfo info, CategoryCrosshairState crosshairState) { + + boolean foundData = false; + CategoryDataset currentDataset = getDataset(index); + CategoryItemRenderer renderer = getRenderer(index); + CategoryAxis domainAxis = getDomainAxisForDataset(index); + ValueAxis rangeAxis = getRangeAxisForDataset(index); + boolean hasData = !DatasetUtils.isEmptyOrNull(currentDataset); + if (hasData && renderer != null) { + + foundData = true; + CategoryItemRendererState state = renderer.initialise(g2, dataArea, + this, index, info); + state.setCrosshairState(crosshairState); + int columnCount = currentDataset.getColumnCount(); + int rowCount = currentDataset.getRowCount(); + int passCount = renderer.getPassCount(); + for (int pass = 0; pass < passCount; pass++) { + if (this.columnRenderingOrder == SortOrder.ASCENDING) { + for (int column = 0; column < columnCount; column++) { + if (this.rowRenderingOrder == SortOrder.ASCENDING) { + for (int row = 0; row < rowCount; row++) { + renderer.drawItem(g2, state, dataArea, this, + domainAxis, rangeAxis, currentDataset, + row, column, pass); + } + } + else { + for (int row = rowCount - 1; row >= 0; row--) { + renderer.drawItem(g2, state, dataArea, this, + domainAxis, rangeAxis, currentDataset, + row, column, pass); + } + } + } + } + else { + for (int column = columnCount - 1; column >= 0; column--) { + if (this.rowRenderingOrder == SortOrder.ASCENDING) { + for (int row = 0; row < rowCount; row++) { + renderer.drawItem(g2, state, dataArea, this, + domainAxis, rangeAxis, currentDataset, + row, column, pass); + } + } + else { + for (int row = rowCount - 1; row >= 0; row--) { + renderer.drawItem(g2, state, dataArea, this, + domainAxis, rangeAxis, currentDataset, + row, column, pass); + } + } + } + } + } + } + return foundData; + + } + + /** + * Draws the domain gridlines for the plot, if they are visible. + * + * @param g2 the graphics device. + * @param dataArea the area inside the axes. + * + * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List) + */ + protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea) { + + if (!isDomainGridlinesVisible()) { + return; + } + CategoryAnchor anchor = getDomainGridlinePosition(); + RectangleEdge domainAxisEdge = getDomainAxisEdge(); + CategoryDataset dataset = getDataset(); + if (dataset == null) { + return; + } + CategoryAxis axis = getDomainAxis(); + if (axis != null) { + int columnCount = dataset.getColumnCount(); + for (int c = 0; c < columnCount; c++) { + double xx = axis.getCategoryJava2DCoordinate(anchor, c, + columnCount, dataArea, domainAxisEdge); + CategoryItemRenderer renderer1 = getRenderer(); + if (renderer1 != null) { + renderer1.drawDomainGridline(g2, this, dataArea, xx); + } + } + } + } + + /** + * Draws the range gridlines for the plot, if they are visible. + * + * @param g2 the graphics device ({@code null} not permitted). + * @param dataArea the area inside the axes ({@code null} not permitted). + * @param ticks the ticks. + * + * @see #drawDomainGridlines(Graphics2D, Rectangle2D) + */ + protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea, + List ticks) { + // draw the range grid lines, if any... + if (!isRangeGridlinesVisible() && !isRangeMinorGridlinesVisible()) { + return; + } + // no axis, no gridlines... + ValueAxis axis = getRangeAxis(); + if (axis == null) { + return; + } + // no renderer, no gridlines... + CategoryItemRenderer r = getRenderer(); + if (r == null) { + return; + } + + Stroke gridStroke = null; + Paint gridPaint = null; + boolean paintLine; + Iterator iterator = ticks.iterator(); + while (iterator.hasNext()) { + paintLine = false; + ValueTick tick = (ValueTick) iterator.next(); + if ((tick.getTickType() == TickType.MINOR) + && isRangeMinorGridlinesVisible()) { + gridStroke = getRangeMinorGridlineStroke(); + gridPaint = getRangeMinorGridlinePaint(); + paintLine = true; + } + else if ((tick.getTickType() == TickType.MAJOR) + && isRangeGridlinesVisible()) { + gridStroke = getRangeGridlineStroke(); + gridPaint = getRangeGridlinePaint(); + paintLine = true; + } + if (((tick.getValue() != 0.0) + || !isRangeZeroBaselineVisible()) && paintLine) { + r .drawRangeLine(g2, this, axis, dataArea, + tick.getValue(), gridPaint, gridStroke); + } + } + } + + /** + * Draws a base line across the chart at value zero on the range axis. + * + * @param g2 the graphics device. + * @param area the data area. + * + * @see #setRangeZeroBaselineVisible(boolean) + */ + protected void drawZeroRangeBaseline(Graphics2D g2, Rectangle2D area) { + if (!isRangeZeroBaselineVisible()) { + return; + } + CategoryItemRenderer r = getRenderer(); + r.drawRangeLine(g2, this, getRangeAxis(), area, 0.0, + this.rangeZeroBaselinePaint, this.rangeZeroBaselineStroke); + } + + /** + * Draws the annotations. + * + * @param g2 the graphics device. + * @param dataArea the data area. + */ + protected void drawAnnotations(Graphics2D g2, Rectangle2D dataArea) { + + if (getAnnotations() != null) { + Iterator iterator = getAnnotations().iterator(); + while (iterator.hasNext()) { + CategoryAnnotation annotation + = (CategoryAnnotation) iterator.next(); + annotation.draw(g2, this, dataArea, getDomainAxis(), + getRangeAxis()); + } + } + + } + + /** + * Draws the domain markers (if any) for an axis and layer. This method is + * typically called from within the draw() method. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param index the renderer index. + * @param layer the layer (foreground or background). + * + * @see #drawRangeMarkers(Graphics2D, Rectangle2D, int, Layer) + */ + protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea, + int index, Layer layer) { + + CategoryItemRenderer r = getRenderer(index); + if (r == null) { + return; + } + + Collection markers = getDomainMarkers(index, layer); + CategoryAxis axis = getDomainAxisForDataset(index); + if (markers != null && axis != null) { + for (CategoryMarker marker : markers) { + r.drawDomainMarker(g2, this, axis, marker, dataArea); + } + } + + } + + /** + * Draws the range markers (if any) for an axis and layer. This method is + * typically called from within the draw() method. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param index the renderer index. + * @param layer the layer (foreground or background). + * + * @see #drawDomainMarkers(Graphics2D, Rectangle2D, int, Layer) + */ + protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea, + int index, Layer layer) { + + CategoryItemRenderer r = getRenderer(index); + if (r == null) { + return; + } + + Collection markers = getRangeMarkers(index, layer); + ValueAxis axis = getRangeAxisForDataset(index); + if (markers != null && axis != null) { + for (Marker marker : markers) { + r.drawRangeMarker(g2, this, axis, marker, dataArea); + } + } + + } + + /** + * Utility method for drawing a line perpendicular to the range axis (used + * for crosshairs). + * + * @param g2 the graphics device. + * @param dataArea the area defined by the axes. + * @param value the data value. + * @param stroke the line stroke ({@code null} not permitted). + * @param paint the line paint ({@code null} not permitted). + */ + protected void drawRangeLine(Graphics2D g2, Rectangle2D dataArea, + double value, Stroke stroke, Paint paint) { + + double java2D = getRangeAxis().valueToJava2D(value, dataArea, + getRangeAxisEdge()); + Line2D line = null; + if (this.orientation == PlotOrientation.HORIZONTAL) { + line = new Line2D.Double(java2D, dataArea.getMinY(), java2D, + dataArea.getMaxY()); + } + else if (this.orientation == PlotOrientation.VERTICAL) { + line = new Line2D.Double(dataArea.getMinX(), java2D, + dataArea.getMaxX(), java2D); + } + g2.setStroke(stroke); + g2.setPaint(paint); + g2.draw(line); + + } + + /** + * Draws a domain crosshair. + * + * @param g2 the graphics target. + * @param dataArea the data area. + * @param orientation the plot orientation. + * @param datasetIndex the dataset index. + * @param rowKey the row key. + * @param columnKey the column key. + * @param stroke the stroke used to draw the crosshair line. + * @param paint the paint used to draw the crosshair line. + * + * @see #drawRangeCrosshair(Graphics2D, Rectangle2D, PlotOrientation, + * double, ValueAxis, Stroke, Paint) + */ + protected void drawDomainCrosshair(Graphics2D g2, Rectangle2D dataArea, + PlotOrientation orientation, int datasetIndex, + Comparable rowKey, Comparable columnKey, Stroke stroke, + Paint paint) { + + CategoryDataset dataset = getDataset(datasetIndex); + CategoryAxis axis = getDomainAxisForDataset(datasetIndex); + CategoryItemRenderer renderer = getRenderer(datasetIndex); + Line2D line; + if (orientation == PlotOrientation.VERTICAL) { + double xx = renderer.getItemMiddle(rowKey, columnKey, dataset, axis, + dataArea, RectangleEdge.BOTTOM); + line = new Line2D.Double(xx, dataArea.getMinY(), xx, + dataArea.getMaxY()); + } + else { + double yy = renderer.getItemMiddle(rowKey, columnKey, dataset, axis, + dataArea, RectangleEdge.LEFT); + line = new Line2D.Double(dataArea.getMinX(), yy, + dataArea.getMaxX(), yy); + } + g2.setStroke(stroke); + g2.setPaint(paint); + g2.draw(line); + + } + + /** + * Draws a range crosshair. + * + * @param g2 the graphics target. + * @param dataArea the data area. + * @param orientation the plot orientation. + * @param value the crosshair value. + * @param axis the axis against which the value is measured. + * @param stroke the stroke used to draw the crosshair line. + * @param paint the paint used to draw the crosshair line. + * + * @see #drawDomainCrosshair(Graphics2D, Rectangle2D, PlotOrientation, int, + * Comparable, Comparable, Stroke, Paint) + */ + protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea, + PlotOrientation orientation, double value, ValueAxis axis, + Stroke stroke, Paint paint) { + + if (!axis.getRange().contains(value)) { + return; + } + Line2D line; + if (orientation == PlotOrientation.HORIZONTAL) { + double xx = axis.valueToJava2D(value, dataArea, + RectangleEdge.BOTTOM); + line = new Line2D.Double(xx, dataArea.getMinY(), xx, + dataArea.getMaxY()); + } + else { + double yy = axis.valueToJava2D(value, dataArea, + RectangleEdge.LEFT); + line = new Line2D.Double(dataArea.getMinX(), yy, + dataArea.getMaxX(), yy); + } + g2.setStroke(stroke); + g2.setPaint(paint); + g2.draw(line); + + } + + /** + * Returns the range of data values that will be plotted against the range + * axis. If the dataset is {@code null}, this method returns + * {@code null}. + * + * @param axis the axis. + * + * @return The data range. + */ + @Override + public Range getDataRange(ValueAxis axis) { + Range result = null; + List mappedDatasets = new ArrayList<>(); + int rangeIndex = findRangeAxisIndex(axis); + if (rangeIndex >= 0) { + mappedDatasets.addAll(datasetsMappedToRangeAxis(rangeIndex)); + } + else if (axis == getRangeAxis()) { + mappedDatasets.addAll(datasetsMappedToRangeAxis(0)); + } + + // iterate through the datasets that map to the axis and get the union + // of the ranges. + for (CategoryDataset d : mappedDatasets) { + CategoryItemRenderer r = getRendererForDataset(d); + if (r != null) { + result = Range.combine(result, r.findRangeBounds(d)); + } + } + return result; + } + + /** + * Returns a list of the datasets that are mapped to the axis with the + * specified index. + * + * @param axisIndex the axis index. + * + * @return The list (possibly empty, but never {@code null}). + */ + private List datasetsMappedToDomainAxis(int axisIndex) { + List result = new ArrayList<>(); + for (Entry entry : this.datasets.entrySet()) { + CategoryDataset dataset = entry.getValue(); + if (dataset == null) { + continue; + } + Integer datasetIndex = entry.getKey(); + List mappedAxes = this.datasetToDomainAxesMap.get(datasetIndex); + if (mappedAxes == null) { + if (axisIndex == 0) { + result.add(dataset); + } + } else { + if (mappedAxes.contains(axisIndex)) { + result.add(dataset); + } + } + } + return result; + } + + /** + * A utility method that returns a list of datasets that are mapped to a + * given range axis. + * + * @param axisIndex the axis index. + * + * @return The list (possibly empty, but never {@code null}). + */ + private List datasetsMappedToRangeAxis(int axisIndex) { + List result = new ArrayList<>(); + for (Entry entry : this.datasets.entrySet()) { + Integer datasetIndex = entry.getKey(); + CategoryDataset dataset = entry.getValue(); + List mappedAxes = this.datasetToRangeAxesMap.get(datasetIndex); + if (mappedAxes == null) { + if (axisIndex == 0) { + result.add(dataset); + } + } else { + if (mappedAxes.contains(axisIndex)) { + result.add(dataset); + } + } + } + return result; + } + + /** + * Returns the weight for this plot when it is used as a subplot within a + * combined plot. + * + * @return The weight. + * + * @see #setWeight(int) + */ + public int getWeight() { + return this.weight; + } + + /** + * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param weight the weight. + * + * @see #getWeight() + */ + public void setWeight(int weight) { + this.weight = weight; + fireChangeEvent(); + } + + /** + * Returns the fixed domain axis space. + * + * @return The fixed domain axis space (possibly {@code null}). + * + * @see #setFixedDomainAxisSpace(AxisSpace) + */ + public AxisSpace getFixedDomainAxisSpace() { + return this.fixedDomainAxisSpace; + } + + /** + * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param space the space ({@code null} permitted). + * + * @see #getFixedDomainAxisSpace() + */ + public void setFixedDomainAxisSpace(AxisSpace space) { + setFixedDomainAxisSpace(space, true); + } + + /** + * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param space the space ({@code null} permitted). + * @param notify notify listeners? + * + * @see #getFixedDomainAxisSpace() + */ + public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) { + this.fixedDomainAxisSpace = space; + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the fixed range axis space. + * + * @return The fixed range axis space (possibly {@code null}). + * + * @see #setFixedRangeAxisSpace(AxisSpace) + */ + public AxisSpace getFixedRangeAxisSpace() { + return this.fixedRangeAxisSpace; + } + + /** + * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param space the space ({@code null} permitted). + * + * @see #getFixedRangeAxisSpace() + */ + public void setFixedRangeAxisSpace(AxisSpace space) { + setFixedRangeAxisSpace(space, true); + } + + /** + * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param space the space ({@code null} permitted). + * @param notify notify listeners? + * + * @see #getFixedRangeAxisSpace() + */ + public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) { + this.fixedRangeAxisSpace = space; + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns a list of the categories in the plot's primary dataset. + * + * @return A list of the categories in the plot's primary dataset. + * + * @see #getCategoriesForAxis(CategoryAxis) + */ + public List getCategories() { + List result = null; + if (getDataset() != null) { + result = Collections.unmodifiableList(getDataset().getColumnKeys()); + } + return result; + } + + /** + * Returns a list of the categories that should be displayed for the + * specified axis. + * + * @param axis the axis ({@code null} not permitted) + * + * @return The categories. + */ + public List getCategoriesForAxis(CategoryAxis axis) { + List result = new ArrayList(); + int axisIndex = getDomainAxisIndex(axis); + for (CategoryDataset dataset : datasetsMappedToDomainAxis(axisIndex)) { + // add the unique categories from this dataset + for (int i = 0; i < dataset.getColumnCount(); i++) { + Comparable category = dataset.getColumnKey(i); + if (!result.contains(category)) { + result.add(category); + } + } + } + return result; + } + + /** + * Returns the flag that controls whether or not the shared domain axis is + * drawn for each subplot. + * + * @return A boolean. + * + * @see #setDrawSharedDomainAxis(boolean) + */ + public boolean getDrawSharedDomainAxis() { + return this.drawSharedDomainAxis; + } + + /** + * Sets the flag that controls whether the shared domain axis is drawn when + * this plot is being used as a subplot. + * + * @param draw a boolean. + * + * @see #getDrawSharedDomainAxis() + */ + public void setDrawSharedDomainAxis(boolean draw) { + this.drawSharedDomainAxis = draw; + fireChangeEvent(); + } + + /** + * Returns {@code false} always, because the plot cannot be panned + * along the domain axis/axes. + * + * @return A boolean. + * + * @see #isRangePannable() + */ + @Override + public boolean isDomainPannable() { + return false; + } + + /** + * Returns {@code true} if panning is enabled for the range axes, + * and {@code false} otherwise. + * + * @return A boolean. + * + * @see #setRangePannable(boolean) + * @see #isDomainPannable() + */ + @Override + public boolean isRangePannable() { + return this.rangePannable; + } + + /** + * Sets the flag that enables or disables panning of the plot along + * the range axes. + * + * @param pannable the new flag value. + * + * @see #isRangePannable() + */ + public void setRangePannable(boolean pannable) { + this.rangePannable = pannable; + } + + /** + * Pans the domain axes by the specified percentage. + * + * @param percent the distance to pan (as a percentage of the axis length). + * @param info the plot info + * @param source the source point where the pan action started. + */ + @Override + public void panDomainAxes(double percent, PlotRenderingInfo info, + Point2D source) { + // do nothing, because the plot is not pannable along the domain axes + } + + /** + * Pans the range axes by the specified percentage. + * + * @param percent the distance to pan (as a percentage of the axis length). + * @param info the plot info + * @param source the source point where the pan action started. + */ + @Override + public void panRangeAxes(double percent, PlotRenderingInfo info, + Point2D source) { + if (!isRangePannable()) { + return; + } + for (ValueAxis axis : this.rangeAxes.values()) { + if (axis == null) { + continue; + } + double length = axis.getRange().getLength(); + double adj = percent * length; + if (axis.isInverted()) { + adj = -adj; + } + axis.setRange(axis.getLowerBound() + adj, + axis.getUpperBound() + adj); + } + } + + /** + * Returns {@code false} to indicate that the domain axes are not + * zoomable. + * + * @return A boolean. + * + * @see #isRangeZoomable() + */ + @Override + public boolean isDomainZoomable() { + return false; + } + + /** + * Returns {@code true} to indicate that the range axes are zoomable. + * + * @return A boolean. + * + * @see #isDomainZoomable() + */ + @Override + public boolean isRangeZoomable() { + return true; + } + + /** + * This method does nothing, because {@code CategoryPlot} doesn't + * support zooming on the domain. + * + * @param factor the zoom factor. + * @param state the plot state. + * @param source the source point (in Java2D space) for the zoom. + */ + @Override + public void zoomDomainAxes(double factor, PlotRenderingInfo state, + Point2D source) { + // can't zoom domain axis + } + + /** + * This method does nothing, because {@code CategoryPlot} doesn't + * support zooming on the domain. + * + * @param lowerPercent the lower bound. + * @param upperPercent the upper bound. + * @param state the plot state. + * @param source the source point (in Java2D space) for the zoom. + */ + @Override + public void zoomDomainAxes(double lowerPercent, double upperPercent, + PlotRenderingInfo state, Point2D source) { + // can't zoom domain axis + } + + /** + * This method does nothing, because {@code CategoryPlot} doesn't + * support zooming on the domain. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point (in Java2D space). + * @param useAnchor use source point as zoom anchor? + * + * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean) + */ + @Override + public void zoomDomainAxes(double factor, PlotRenderingInfo info, + Point2D source, boolean useAnchor) { + // can't zoom domain axis + } + + /** + * Multiplies the range on the range axis/axes by the specified factor. + * + * @param factor the zoom factor. + * @param state the plot state. + * @param source the source point (in Java2D space) for the zoom. + */ + @Override + public void zoomRangeAxes(double factor, PlotRenderingInfo state, + Point2D source) { + // delegate to other method + zoomRangeAxes(factor, state, source, false); + } + + /** + * Multiplies the range on the range axis/axes by the specified factor. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point. + * @param useAnchor a flag that controls whether or not the source point + * is used for the zoom anchor. + * + * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) + */ + @Override + public void zoomRangeAxes(double factor, PlotRenderingInfo info, + Point2D source, boolean useAnchor) { + + // perform the zoom on each range axis + for (ValueAxis rangeAxis : this.rangeAxes.values()) { + if (rangeAxis == null) { + continue; + } + if (useAnchor) { + // get the relevant source coordinate given the plot orientation + double sourceY = source.getY(); + if (this.orientation.isHorizontal()) { + sourceY = source.getX(); + } + double anchorY = rangeAxis.java2DToValue(sourceY, + info.getDataArea(), getRangeAxisEdge()); + rangeAxis.resizeRange2(factor, anchorY); + } else { + rangeAxis.resizeRange(factor); + } + } + } + + /** + * Zooms in on the range axes. + * + * @param lowerPercent the lower bound. + * @param upperPercent the upper bound. + * @param state the plot state. + * @param source the source point (in Java2D space) for the zoom. + */ + @Override + public void zoomRangeAxes(double lowerPercent, double upperPercent, + PlotRenderingInfo state, Point2D source) { + for (ValueAxis yAxis : this.rangeAxes.values()) { + if (yAxis != null) { + yAxis.zoomRange(lowerPercent, upperPercent); + } + } + } + + /** + * Returns the anchor value. + * + * @return The anchor value. + * + * @see #setAnchorValue(double) + */ + public double getAnchorValue() { + return this.anchorValue; + } + + /** + * Sets the anchor value and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param value the anchor value. + * + * @see #getAnchorValue() + */ + public void setAnchorValue(double value) { + setAnchorValue(value, true); + } + + /** + * Sets the anchor value and, if requested, sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param value the value. + * @param notify notify listeners? + * + * @see #getAnchorValue() + */ + public void setAnchorValue(double value, boolean notify) { + this.anchorValue = value; + if (notify) { + fireChangeEvent(); + } + } + + /** + * Tests the plot for equality with an arbitrary object. + * + * @param obj the object to test against ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof CategoryPlot)) { + return false; + } + CategoryPlot that = (CategoryPlot) obj; + if (!that.canEqual(this)) { + return false; + } + if (!Objects.equals(this.orientation, that.orientation)) { + return false; + } + if (!Objects.equals(this.datasets, that.datasets)) { + return false; + } + if (!Objects.equals(this.axisOffset, that.axisOffset)) { + return false; + } + if (!Objects.equals(this.domainAxes, that.domainAxes)) { + return false; + } + if (!Objects.equals(this.domainAxisLocations, + that.domainAxisLocations)) { + return false; + } + if (this.drawSharedDomainAxis != that.drawSharedDomainAxis) { + return false; + } + if (!Objects.equals(this.rangeAxes, that.rangeAxes)) { + return false; + } + if (!Objects.equals(this.rangeAxisLocations, that.rangeAxisLocations)) { + return false; + } + if (!Objects.equals(this.datasetToDomainAxesMap, + that.datasetToDomainAxesMap)) { + return false; + } + if (!Objects.equals(this.datasetToRangeAxesMap, + that.datasetToRangeAxesMap)) { + return false; + } + if (!Objects.equals(this.renderers, that.renderers)) { + return false; + } + if (!Objects.equals(this.renderingOrder, that.renderingOrder)) { + return false; + } + if (!Objects.equals(this.columnRenderingOrder, + that.columnRenderingOrder)) { + return false; + } + if (!Objects.equals(this.rowRenderingOrder, that.rowRenderingOrder)) { + return false; + } + if (this.domainGridlinesVisible != that.domainGridlinesVisible) { + return false; + } + if (this.rangePannable != that.rangePannable) { + return false; + } + if (!Objects.equals(this.domainGridlinePosition, + that.domainGridlinePosition)) { + return false; + } + if (!Objects.equals(this.domainGridlineStroke, + that.domainGridlineStroke)) { + return false; + } + if (!PaintUtils.equal(this.domainGridlinePaint, + that.domainGridlinePaint)) { + return false; + } + if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) { + return false; + } + if (!Objects.equals(this.rangeGridlineStroke, + that.rangeGridlineStroke)) { + return false; + } + if (!PaintUtils.equal(this.rangeGridlinePaint, + that.rangeGridlinePaint)) { + return false; + } + if (Double.compare(this.anchorValue, that.anchorValue) != 0) { + return false; + } + if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) { + return false; + } + if (Double.doubleToLongBits(this.rangeCrosshairValue) != + Double.doubleToLongBits(that.rangeCrosshairValue)) { + return false; + } + if (!Objects.equals(this.rangeCrosshairStroke, + that.rangeCrosshairStroke)) { + return false; + } + if (!PaintUtils.equal(this.rangeCrosshairPaint, + that.rangeCrosshairPaint)) { + return false; + } + if (this.rangeCrosshairLockedOnData != that.rangeCrosshairLockedOnData) { + return false; + } + if (!Objects.equals(this.foregroundDomainMarkers, + that.foregroundDomainMarkers)) { + return false; + } + if (!Objects.equals(this.backgroundDomainMarkers, + that.backgroundDomainMarkers)) { + return false; + } + if (!Objects.equals(this.foregroundRangeMarkers, + that.foregroundRangeMarkers)) { + return false; + } + if (!Objects.equals(this.backgroundRangeMarkers, + that.backgroundRangeMarkers)) { + return false; + } + if (!Objects.equals(this.annotations, that.annotations)) { + return false; + } + if (this.weight != that.weight) { + return false; + } + if (!Objects.equals(this.fixedDomainAxisSpace, + that.fixedDomainAxisSpace)) { + return false; + } + if (!Objects.equals(this.fixedRangeAxisSpace, + that.fixedRangeAxisSpace)) { + return false; + } + if (!Objects.equals(this.fixedLegendItems, that.fixedLegendItems)) { + return false; + } + if (this.domainCrosshairVisible != that.domainCrosshairVisible) { + return false; + } + if (this.crosshairDatasetIndex != that.crosshairDatasetIndex) { + return false; + } + if (!Objects.equals(this.domainCrosshairColumnKey, + that.domainCrosshairColumnKey)) { + return false; + } + if (!Objects.equals(this.domainCrosshairRowKey, + that.domainCrosshairRowKey)) { + return false; + } + if (!PaintUtils.equal(this.domainCrosshairPaint, + that.domainCrosshairPaint)) { + return false; + } + if (!Objects.equals(this.domainCrosshairStroke, + that.domainCrosshairStroke)) { + return false; + } + if (this.rangeMinorGridlinesVisible != that.rangeMinorGridlinesVisible) { + return false; + } + if (!PaintUtils.equal(this.rangeMinorGridlinePaint, + that.rangeMinorGridlinePaint)) { + return false; + } + if (!Objects.equals(this.rangeMinorGridlineStroke, + that.rangeMinorGridlineStroke)) { + return false; + } + if (this.rangeZeroBaselineVisible != that.rangeZeroBaselineVisible) { + return false; + } + if (!PaintUtils.equal(this.rangeZeroBaselinePaint, + that.rangeZeroBaselinePaint)) { + return false; + } + if (!Objects.equals(this.rangeZeroBaselineStroke, + that.rangeZeroBaselineStroke)) { + return false; + } + if (!Objects.equals(this.shadowGenerator, that.shadowGenerator)) { + return false; + } + return super.equals(obj); + } + + /** + * Ensures symmetry between super/subclass implementations of equals. For + * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. + * + * @param other Object + * + * @return true ONLY if the parameter is THIS class type + */ + @Override + public boolean canEqual(Object other) { + // Solves Problem: equals not symmetric + return (other instanceof CategoryPlot); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 71 * hash + Objects.hashCode(this.orientation); + hash = 71 * hash + Objects.hashCode(this.axisOffset); + hash = 71 * hash + Objects.hashCode(this.domainAxes); + hash = 71 * hash + Objects.hashCode(this.domainAxisLocations); + hash = 71 * hash + (this.drawSharedDomainAxis ? 1 : 0); + hash = 71 * hash + Objects.hashCode(this.rangeAxes); + hash = 71 * hash + Objects.hashCode(this.rangeAxisLocations); + hash = 71 * hash + Objects.hashCode(this.datasets); + hash = 71 * hash + Objects.hashCode(this.datasetToDomainAxesMap); + hash = 71 * hash + Objects.hashCode(this.datasetToRangeAxesMap); + hash = 71 * hash + Objects.hashCode(this.renderers); + hash = 71 * hash + Objects.hashCode(this.renderingOrder); + hash = 71 * hash + Objects.hashCode(this.columnRenderingOrder); + hash = 71 * hash + Objects.hashCode(this.rowRenderingOrder); + hash = 71 * hash + (this.domainGridlinesVisible ? 1 : 0); + hash = 71 * hash + Objects.hashCode(this.domainGridlinePosition); + hash = 71 * hash + Objects.hashCode(this.domainGridlineStroke); + hash = 71 * hash + Objects.hashCode(this.domainGridlinePaint); + hash = 71 * hash + (this.rangeZeroBaselineVisible ? 1 : 0); + hash = 71 * hash + Objects.hashCode(this.rangeZeroBaselineStroke); + hash = 71 * hash + Objects.hashCode(this.rangeZeroBaselinePaint); + hash = 71 * hash + (this.rangeGridlinesVisible ? 1 : 0); + hash = 71 * hash + Objects.hashCode(this.rangeGridlineStroke); + hash = 71 * hash + Objects.hashCode(this.rangeGridlinePaint); + hash = 71 * hash + (this.rangeMinorGridlinesVisible ? 1 : 0); + hash = 71 * hash + Objects.hashCode(this.rangeMinorGridlineStroke); + hash = 71 * hash + Objects.hashCode(this.rangeMinorGridlinePaint); + hash = 71 * hash + (int) (Double.doubleToLongBits(this.anchorValue) ^ + (Double.doubleToLongBits(this.anchorValue) >>> 32)); + hash = 71 * hash + this.crosshairDatasetIndex; + hash = 71 * hash + (this.domainCrosshairVisible ? 1 : 0); + hash = 71 * hash + Objects.hashCode(this.domainCrosshairRowKey); + hash = 71 * hash + Objects.hashCode(this.domainCrosshairColumnKey); + hash = 71 * hash + Objects.hashCode(this.domainCrosshairStroke); + hash = 71 * hash + Objects.hashCode(this.domainCrosshairPaint); + hash = 71 * hash + (this.rangeCrosshairVisible ? 1 : 0); + hash = 71 * hash + (int) (Double.doubleToLongBits(this.rangeCrosshairValue) ^ + (Double.doubleToLongBits(this.rangeCrosshairValue) >>> 32)); + hash = 71 * hash + Objects.hashCode(this.rangeCrosshairStroke); + hash = 71 * hash + Objects.hashCode(this.rangeCrosshairPaint); + hash = 71 * hash + (this.rangeCrosshairLockedOnData ? 1 : 0); + hash = 71 * hash + Objects.hashCode(this.foregroundDomainMarkers); + hash = 71 * hash + Objects.hashCode(this.backgroundDomainMarkers); + hash = 71 * hash + Objects.hashCode(this.foregroundRangeMarkers); + hash = 71 * hash + Objects.hashCode(this.backgroundRangeMarkers); + hash = 71 * hash + Objects.hashCode(this.annotations); + hash = 71 * hash + this.weight; + hash = 71 * hash + Objects.hashCode(this.fixedDomainAxisSpace); + hash = 71 * hash + Objects.hashCode(this.fixedRangeAxisSpace); + hash = 71 * hash + Objects.hashCode(this.fixedLegendItems); + hash = 71 * hash + (this.rangePannable ? 1 : 0); + hash = 71 * hash + Objects.hashCode(this.shadowGenerator); + return hash; + } + + /** + * Returns a clone of the plot. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the cloning is not supported. + */ + @Override + public Object clone() throws CloneNotSupportedException { + CategoryPlot clone = (CategoryPlot) super.clone(); + clone.domainAxes = CloneUtils.cloneMapValues(this.domainAxes); + for (CategoryAxis axis : clone.domainAxes.values()) { + if (axis != null) { + axis.setPlot(clone); + axis.addChangeListener(clone); + } + } + clone.rangeAxes = CloneUtils.cloneMapValues(this.rangeAxes); + for (ValueAxis axis : clone.rangeAxes.values()) { + if (axis != null) { + axis.setPlot(clone); + axis.addChangeListener(clone); + } + } + + // AxisLocation is immutable, so we can just copy the maps + clone.domainAxisLocations = new HashMap<>( + this.domainAxisLocations); + clone.rangeAxisLocations = new HashMap<>( + this.rangeAxisLocations); + + clone.datasets = new HashMap<>(this.datasets); + for (CategoryDataset dataset : clone.datasets.values()) { + if (dataset != null) { + dataset.addChangeListener(clone); + } + } + clone.datasetToDomainAxesMap = new TreeMap(); + clone.datasetToDomainAxesMap.putAll(this.datasetToDomainAxesMap); + clone.datasetToRangeAxesMap = new TreeMap(); + clone.datasetToRangeAxesMap.putAll(this.datasetToRangeAxesMap); + + clone.renderers = CloneUtils.cloneMapValues(this.renderers); + for (CategoryItemRenderer renderer : clone.renderers.values()) { + if (renderer != null) { + renderer.setPlot(clone); + renderer.addChangeListener(clone); + } + } + if (this.fixedDomainAxisSpace != null) { + clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtils.clone( + this.fixedDomainAxisSpace); + } + if (this.fixedRangeAxisSpace != null) { + clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtils.clone( + this.fixedRangeAxisSpace); + } + + clone.annotations = (List) ObjectUtils.deepClone(this.annotations); + clone.foregroundDomainMarkers = cloneMarkerMap( + this.foregroundDomainMarkers); + clone.backgroundDomainMarkers = cloneMarkerMap( + this.backgroundDomainMarkers); + clone.foregroundRangeMarkers = cloneMarkerMap( + this.foregroundRangeMarkers); + clone.backgroundRangeMarkers = cloneMarkerMap( + this.backgroundRangeMarkers); + if (this.fixedLegendItems != null) { + clone.fixedLegendItems + = (LegendItemCollection) this.fixedLegendItems.clone(); + } + return clone; + } + + /** + * A utility method to clone the marker maps. + * + * @param map the map to clone. + * + * @return A clone of the map. + * + * @throws CloneNotSupportedException if there is some problem cloning the + * map. + */ + private Map cloneMarkerMap(Map map) throws CloneNotSupportedException { + Map clone = new HashMap(); + Set keys = map.keySet(); + Iterator iterator = keys.iterator(); + while (iterator.hasNext()) { + Object key = iterator.next(); + List entry = (List) map.get(key); + Object toAdd = ObjectUtils.deepClone(entry); + clone.put(key, toAdd); + } + return clone; + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writeStroke(this.domainGridlineStroke, stream); + SerialUtils.writePaint(this.domainGridlinePaint, stream); + SerialUtils.writeStroke(this.rangeGridlineStroke, stream); + SerialUtils.writePaint(this.rangeGridlinePaint, stream); + SerialUtils.writeStroke(this.rangeCrosshairStroke, stream); + SerialUtils.writePaint(this.rangeCrosshairPaint, stream); + SerialUtils.writeStroke(this.domainCrosshairStroke, stream); + SerialUtils.writePaint(this.domainCrosshairPaint, stream); + SerialUtils.writeStroke(this.rangeMinorGridlineStroke, stream); + SerialUtils.writePaint(this.rangeMinorGridlinePaint, stream); + SerialUtils.writeStroke(this.rangeZeroBaselineStroke, stream); + SerialUtils.writePaint(this.rangeZeroBaselinePaint, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + + stream.defaultReadObject(); + this.domainGridlineStroke = SerialUtils.readStroke(stream); + this.domainGridlinePaint = SerialUtils.readPaint(stream); + this.rangeGridlineStroke = SerialUtils.readStroke(stream); + this.rangeGridlinePaint = SerialUtils.readPaint(stream); + this.rangeCrosshairStroke = SerialUtils.readStroke(stream); + this.rangeCrosshairPaint = SerialUtils.readPaint(stream); + this.domainCrosshairStroke = SerialUtils.readStroke(stream); + this.domainCrosshairPaint = SerialUtils.readPaint(stream); + this.rangeMinorGridlineStroke = SerialUtils.readStroke(stream); + this.rangeMinorGridlinePaint = SerialUtils.readPaint(stream); + this.rangeZeroBaselineStroke = SerialUtils.readStroke(stream); + this.rangeZeroBaselinePaint = SerialUtils.readPaint(stream); + + for (CategoryAxis xAxis : this.domainAxes.values()) { + if (xAxis != null) { + xAxis.setPlot(this); + xAxis.addChangeListener(this); + } + } + for (ValueAxis yAxis : this.rangeAxes.values()) { + if (yAxis != null) { + yAxis.setPlot(this); + yAxis.addChangeListener(this); + } + } + for (CategoryDataset dataset : this.datasets.values()) { + if (dataset != null) { + dataset.addChangeListener(this); + } + } + for (CategoryItemRenderer renderer : this.renderers.values()) { + if (renderer != null) { + renderer.addChangeListener(this); + } + } + + } + +} diff --git a/src/main/java/org/jfree/chart/plot/CompassPlot.java b/src/main/java/org/jfree/chart/plot/CompassPlot.java index a4d993ea1..0ee5fe090 100644 --- a/src/main/java/org/jfree/chart/plot/CompassPlot.java +++ b/src/main/java/org/jfree/chart/plot/CompassPlot.java @@ -59,7 +59,6 @@ import java.util.ResourceBundle; import org.jfree.chart.LegendItemCollection; -import org.jfree.chart.event.PlotChangeEvent; import org.jfree.chart.needle.ArrowNeedle; import org.jfree.chart.needle.LineNeedle; import org.jfree.chart.needle.LongNeedle; @@ -219,8 +218,7 @@ public Font getLabelFont() { } /** - * Sets the label font and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the label font and calls {@link #fireChangeEvent() }. * * @param font the new label font. * @@ -246,7 +244,7 @@ public Paint getRosePaint() { /** * Sets the paint used to fill the outer circle of the compass, - * and sends a {@link PlotChangeEvent} to all registered listeners. + * and calls {@link #fireChangeEvent() }. * * @param paint the paint ({@code null} not permitted). * @@ -272,7 +270,7 @@ public Paint getRoseCenterPaint() { /** * Sets the paint used to fill the inner background area of the compass, - * and sends a {@link PlotChangeEvent} to all registered listeners. + * and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -298,7 +296,7 @@ public Paint getRoseHighlightPaint() { /** * Sets the paint used to draw the circles, symbols and labels of the - * compass, and sends a {@link PlotChangeEvent} to all registered listeners. + * compass, and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -457,8 +455,7 @@ public void setSeriesNeedle(int index, int type) { } /** - * Sets the needle for a series and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the needle for a series and calls {@link #fireChangeEvent()}. * * @param index the series index. * @param needle the needle. diff --git a/src/main/java/org/jfree/chart/plot/FastScatterPlot.java b/src/main/java/org/jfree/chart/plot/FastScatterPlot.java index b226d13b2..cdb0fbe16 100644 --- a/src/main/java/org/jfree/chart/plot/FastScatterPlot.java +++ b/src/main/java/org/jfree/chart/plot/FastScatterPlot.java @@ -1,1078 +1,1071 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------------- - * FastScatterPlot.java - * -------------------- - * (C) Copyright 2002-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): Arnaud Lelievre; - * Ulrich Voigt (patch #307); - * - */ - -package org.jfree.chart.plot; - -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.RenderingHints; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Line2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import java.util.ResourceBundle; - -import org.jfree.chart.axis.AxisSpace; -import org.jfree.chart.axis.AxisState; -import org.jfree.chart.axis.NumberAxis; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.axis.ValueTick; -import org.jfree.chart.event.PlotChangeEvent; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.RectangleInsets; -import org.jfree.chart.util.ArrayUtils; -import org.jfree.chart.util.PaintUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.ResourceBundleWrapper; -import org.jfree.chart.util.SerialUtils; -import org.jfree.data.Range; - -/** - * A fast scatter plot. - */ -public class FastScatterPlot extends Plot implements ValueAxisPlot, Pannable, - Zoomable, Cloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = 7871545897358563521L; - - /** The default grid line stroke. */ - public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, - BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[] - {2.0f, 2.0f}, 0.0f); - - /** The default grid line paint. */ - public static final Paint DEFAULT_GRIDLINE_PAINT = Color.LIGHT_GRAY; - - /** The data. */ - private float[][] data; - - /** The x data range. */ - private Range xDataRange; - - /** The y data range. */ - private Range yDataRange; - - /** The domain axis (used for the x-values). */ - private ValueAxis domainAxis; - - /** The range axis (used for the y-values). */ - private ValueAxis rangeAxis; - - /** The paint used to plot data points. */ - private transient Paint paint; - - /** A flag that controls whether the domain grid-lines are visible. */ - private boolean domainGridlinesVisible; - - /** The stroke used to draw the domain grid-lines. */ - private transient Stroke domainGridlineStroke; - - /** The paint used to draw the domain grid-lines. */ - private transient Paint domainGridlinePaint; - - /** A flag that controls whether the range grid-lines are visible. */ - private boolean rangeGridlinesVisible; - - /** The stroke used to draw the range grid-lines. */ - private transient Stroke rangeGridlineStroke; - - /** The paint used to draw the range grid-lines. */ - private transient Paint rangeGridlinePaint; - - /** - * A flag that controls whether or not panning is enabled for the domain - * axis. - */ - private boolean domainPannable; - - /** - * A flag that controls whether or not panning is enabled for the range - * axis. - */ - private boolean rangePannable; - - /** The resourceBundle for the localization. */ - protected static ResourceBundle localizationResources - = ResourceBundleWrapper.getBundle( - "org.jfree.chart.plot.LocalizationBundle"); - - /** - * Creates a new instance of {@code FastScatterPlot} with default - * axes. - */ - public FastScatterPlot() { - this(null, new NumberAxis("X"), new NumberAxis("Y")); - } - - /** - * Creates a new fast scatter plot. - *

- * The data is an array of x, y values: data[0][i] = x, data[1][i] = y. - * - * @param data the data ({@code null} permitted). - * @param domainAxis the domain (x) axis ({@code null} not permitted). - * @param rangeAxis the range (y) axis ({@code null} not permitted). - */ - public FastScatterPlot(float[][] data, - ValueAxis domainAxis, ValueAxis rangeAxis) { - - super(); - Args.nullNotPermitted(domainAxis, "domainAxis"); - Args.nullNotPermitted(rangeAxis, "rangeAxis"); - - this.data = data; - this.xDataRange = calculateXDataRange(data); - this.yDataRange = calculateYDataRange(data); - this.domainAxis = domainAxis; - this.domainAxis.setPlot(this); - this.domainAxis.addChangeListener(this); - this.rangeAxis = rangeAxis; - this.rangeAxis.setPlot(this); - this.rangeAxis.addChangeListener(this); - - this.paint = Color.RED; - - this.domainGridlinesVisible = true; - this.domainGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT; - this.domainGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE; - - this.rangeGridlinesVisible = true; - this.rangeGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT; - this.rangeGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE; - } - - /** - * Returns a short string describing the plot type. - * - * @return A short string describing the plot type. - */ - @Override - public String getPlotType() { - return localizationResources.getString("Fast_Scatter_Plot"); - } - - /** - * Returns the data array used by the plot. - * - * @return The data array (possibly {@code null}). - * - * @see #setData(float[][]) - */ - public float[][] getData() { - return this.data; - } - - /** - * Sets the data array used by the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param data the data array ({@code null} permitted). - * - * @see #getData() - */ - public void setData(float[][] data) { - this.data = data; - fireChangeEvent(); - } - - /** - * Returns the orientation of the plot. - * - * @return The orientation (always {@link PlotOrientation#VERTICAL}). - */ - @Override - public PlotOrientation getOrientation() { - return PlotOrientation.VERTICAL; - } - - /** - * Returns the domain axis for the plot. - * - * @return The domain axis (never {@code null}). - * - * @see #setDomainAxis(ValueAxis) - */ - public ValueAxis getDomainAxis() { - return this.domainAxis; - } - - /** - * Sets the domain axis and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param axis the axis ({@code null} not permitted). - * - * @see #getDomainAxis() - */ - public void setDomainAxis(ValueAxis axis) { - Args.nullNotPermitted(axis, "axis"); - this.domainAxis = axis; - fireChangeEvent(); - } - - /** - * Returns the range axis for the plot. - * - * @return The range axis (never {@code null}). - * - * @see #setRangeAxis(ValueAxis) - */ - public ValueAxis getRangeAxis() { - return this.rangeAxis; - } - - /** - * Sets the range axis and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param axis the axis ({@code null} not permitted). - * - * @see #getRangeAxis() - */ - public void setRangeAxis(ValueAxis axis) { - Args.nullNotPermitted(axis, "axis"); - this.rangeAxis = axis; - fireChangeEvent(); - } - - /** - * Returns the paint used to plot data points. The default is - * {@code Color.RED}. - * - * @return The paint. - * - * @see #setPaint(Paint) - */ - public Paint getPaint() { - return this.paint; - } - - /** - * Sets the color for the data points and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getPaint() - */ - public void setPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.paint = paint; - fireChangeEvent(); - } - - /** - * Returns {@code true} if the domain gridlines are visible, and - * {@code false} otherwise. - * - * @return {@code true} or {@code false}. - * - * @see #setDomainGridlinesVisible(boolean) - * @see #setDomainGridlinePaint(Paint) - */ - public boolean isDomainGridlinesVisible() { - return this.domainGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not the domain grid-lines are - * visible. If the flag value is changed, a {@link PlotChangeEvent} is - * sent to all registered listeners. - * - * @param visible the new value of the flag. - * - * @see #getDomainGridlinePaint() - */ - public void setDomainGridlinesVisible(boolean visible) { - if (this.domainGridlinesVisible != visible) { - this.domainGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the stroke for the grid-lines (if any) plotted against the - * domain axis. - * - * @return The stroke (never {@code null}). - * - * @see #setDomainGridlineStroke(Stroke) - */ - public Stroke getDomainGridlineStroke() { - return this.domainGridlineStroke; - } - - /** - * Sets the stroke for the grid lines plotted against the domain axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getDomainGridlineStroke() - */ - public void setDomainGridlineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.domainGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the grid lines (if any) plotted against the domain - * axis. - * - * @return The paint (never {@code null}). - * - * @see #setDomainGridlinePaint(Paint) - */ - public Paint getDomainGridlinePaint() { - return this.domainGridlinePaint; - } - - /** - * Sets the paint for the grid lines plotted against the domain axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getDomainGridlinePaint() - */ - public void setDomainGridlinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.domainGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns {@code true} if the range axis grid is visible, and - * {@code false} otherwise. - * - * @return {@code true} or {@code false}. - * - * @see #setRangeGridlinesVisible(boolean) - */ - public boolean isRangeGridlinesVisible() { - return this.rangeGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not the range axis grid lines are - * visible. If the flag value is changed, a {@link PlotChangeEvent} is - * sent to all registered listeners. - * - * @param visible the new value of the flag. - * - * @see #isRangeGridlinesVisible() - */ - public void setRangeGridlinesVisible(boolean visible) { - if (this.rangeGridlinesVisible != visible) { - this.rangeGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the stroke for the grid lines (if any) plotted against the range - * axis. - * - * @return The stroke (never {@code null}). - * - * @see #setRangeGridlineStroke(Stroke) - */ - public Stroke getRangeGridlineStroke() { - return this.rangeGridlineStroke; - } - - /** - * Sets the stroke for the grid lines plotted against the range axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} permitted). - * - * @see #getRangeGridlineStroke() - */ - public void setRangeGridlineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.rangeGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the grid lines (if any) plotted against the range - * axis. - * - * @return The paint (never {@code null}). - * - * @see #setRangeGridlinePaint(Paint) - */ - public Paint getRangeGridlinePaint() { - return this.rangeGridlinePaint; - } - - /** - * Sets the paint for the grid lines plotted against the range axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getRangeGridlinePaint() - */ - public void setRangeGridlinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.rangeGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Draws the fast scatter plot on a Java 2D graphics device (such as the - * screen or a printer). - * - * @param g2 the graphics device. - * @param area the area within which the plot (including axis labels) - * should be drawn. - * @param anchor the anchor point ({@code null} permitted). - * @param parentState the state from the parent plot (ignored). - * @param info collects chart drawing information ({@code null} - * permitted). - */ - @Override - public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, - PlotState parentState, PlotRenderingInfo info) { - - // set up info collection... - if (info != null) { - info.setPlotArea(area); - } - - // adjust the drawing area for plot insets (if any)... - RectangleInsets insets = getInsets(); - insets.trim(area); - - AxisSpace space = new AxisSpace(); - space = this.domainAxis.reserveSpace(g2, this, area, - RectangleEdge.BOTTOM, space); - space = this.rangeAxis.reserveSpace(g2, this, area, RectangleEdge.LEFT, - space); - Rectangle2D dataArea = space.shrink(area, null); - - if (info != null) { - info.setDataArea(dataArea); - } - - // draw the plot background and axes... - drawBackground(g2, dataArea); - - AxisState domainAxisState = this.domainAxis.draw(g2, - dataArea.getMaxY(), area, dataArea, RectangleEdge.BOTTOM, info); - AxisState rangeAxisState = this.rangeAxis.draw(g2, dataArea.getMinX(), - area, dataArea, RectangleEdge.LEFT, info); - drawDomainGridlines(g2, dataArea, domainAxisState.getTicks()); - drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks()); - - Shape originalClip = g2.getClip(); - Composite originalComposite = g2.getComposite(); - - g2.clip(dataArea); - g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - getForegroundAlpha())); - - render(g2, dataArea, info, null); - - g2.setClip(originalClip); - g2.setComposite(originalComposite); - drawOutline(g2, dataArea); - - } - - /** - * Draws a representation of the data within the dataArea region. The - * {@code info} and {@code crosshairState} arguments may be - * {@code null}. - * - * @param g2 the graphics device. - * @param dataArea the region in which the data is to be drawn. - * @param info an optional object for collection dimension information. - * @param crosshairState collects crosshair information ({@code null} - * permitted). - */ - public void render(Graphics2D g2, Rectangle2D dataArea, - PlotRenderingInfo info, CrosshairState crosshairState) { - g2.setPaint(this.paint); - - // if the axes use a linear scale, you can uncomment the code below and - // switch to the alternative transX/transY calculation inside the loop - // that follows - it is a little bit faster then. - // - // int xx = (int) dataArea.getMinX(); - // int ww = (int) dataArea.getWidth(); - // int yy = (int) dataArea.getMaxY(); - // int hh = (int) dataArea.getHeight(); - // double domainMin = this.domainAxis.getLowerBound(); - // double domainLength = this.domainAxis.getUpperBound() - domainMin; - // double rangeMin = this.rangeAxis.getLowerBound(); - // double rangeLength = this.rangeAxis.getUpperBound() - rangeMin; - - if (this.data != null) { - for (int i = 0; i < this.data[0].length; i++) { - float x = this.data[0][i]; - float y = this.data[1][i]; - - //int transX = (int) (xx + ww * (x - domainMin) / domainLength); - //int transY = (int) (yy - hh * (y - rangeMin) / rangeLength); - int transX = (int) this.domainAxis.valueToJava2D(x, dataArea, - RectangleEdge.BOTTOM); - int transY = (int) this.rangeAxis.valueToJava2D(y, dataArea, - RectangleEdge.LEFT); - g2.fillRect(transX, transY, 1, 1); - } - } - } - - /** - * Draws the gridlines for the plot, if they are visible. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param ticks the ticks. - */ - protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea, - List ticks) { - if (!isDomainGridlinesVisible()) { - return; - } - Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_NORMALIZE); - Iterator iterator = ticks.iterator(); - while (iterator.hasNext()) { - ValueTick tick = (ValueTick) iterator.next(); - double v = this.domainAxis.valueToJava2D(tick.getValue(), - dataArea, RectangleEdge.BOTTOM); - Line2D line = new Line2D.Double(v, dataArea.getMinY(), v, - dataArea.getMaxY()); - g2.setPaint(getDomainGridlinePaint()); - g2.setStroke(getDomainGridlineStroke()); - g2.draw(line); - } - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); - } - - /** - * Draws the gridlines for the plot, if they are visible. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param ticks the ticks. - */ - protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea, - List ticks) { - - if (!isRangeGridlinesVisible()) { - return; - } - Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_NORMALIZE); - - Iterator iterator = ticks.iterator(); - while (iterator.hasNext()) { - ValueTick tick = (ValueTick) iterator.next(); - double v = this.rangeAxis.valueToJava2D(tick.getValue(), - dataArea, RectangleEdge.LEFT); - Line2D line = new Line2D.Double(dataArea.getMinX(), v, - dataArea.getMaxX(), v); - g2.setPaint(getRangeGridlinePaint()); - g2.setStroke(getRangeGridlineStroke()); - g2.draw(line); - } - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); - } - - /** - * Returns the range of data values to be plotted along the axis, or - * {@code null} if the specified axis isn't the domain axis or the - * range axis for the plot. - * - * @param axis the axis ({@code null} permitted). - * - * @return The range (possibly {@code null}). - */ - @Override - public Range getDataRange(ValueAxis axis) { - Range result = null; - if (axis == this.domainAxis) { - result = this.xDataRange; - } - else if (axis == this.rangeAxis) { - result = this.yDataRange; - } - return result; - } - - /** - * Calculates the X data range. - * - * @param data the data ({@code null} permitted). - * - * @return The range. - */ - private Range calculateXDataRange(float[][] data) { - - Range result = null; - - if (data != null) { - float lowest = Float.POSITIVE_INFINITY; - float highest = Float.NEGATIVE_INFINITY; - for (int i = 0; i < data[0].length; i++) { - float v = data[0][i]; - if (v < lowest) { - lowest = v; - } - if (v > highest) { - highest = v; - } - } - if (lowest <= highest) { - result = new Range(lowest, highest); - } - } - - return result; - - } - - /** - * Calculates the Y data range. - * - * @param data the data ({@code null} permitted). - * - * @return The range. - */ - private Range calculateYDataRange(float[][] data) { - - Range result = null; - if (data != null) { - float lowest = Float.POSITIVE_INFINITY; - float highest = Float.NEGATIVE_INFINITY; - for (int i = 0; i < data[0].length; i++) { - float v = data[1][i]; - if (v < lowest) { - lowest = v; - } - if (v > highest) { - highest = v; - } - } - if (lowest <= highest) { - result = new Range(lowest, highest); - } - } - return result; - - } - - /** - * Multiplies the range on the domain axis by the specified factor. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point. - */ - @Override - public void zoomDomainAxes(double factor, PlotRenderingInfo info, - Point2D source) { - this.domainAxis.resizeRange(factor); - } - - /** - * Multiplies the range on the domain axis by the specified factor. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point (in Java2D space). - * @param useAnchor use source point as zoom anchor? - * - * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean) - */ - @Override - public void zoomDomainAxes(double factor, PlotRenderingInfo info, - Point2D source, boolean useAnchor) { - - if (useAnchor) { - // get the source coordinate - this plot has always a VERTICAL - // orientation - double sourceX = source.getX(); - double anchorX = this.domainAxis.java2DToValue(sourceX, - info.getDataArea(), RectangleEdge.BOTTOM); - this.domainAxis.resizeRange2(factor, anchorX); - } - else { - this.domainAxis.resizeRange(factor); - } - - } - - /** - * Zooms in on the domain axes. - * - * @param lowerPercent the new lower bound as a percentage of the current - * range. - * @param upperPercent the new upper bound as a percentage of the current - * range. - * @param info the plot rendering info. - * @param source the source point. - */ - @Override - public void zoomDomainAxes(double lowerPercent, double upperPercent, - PlotRenderingInfo info, Point2D source) { - this.domainAxis.zoomRange(lowerPercent, upperPercent); - } - - /** - * Multiplies the range on the range axis/axes by the specified factor. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point. - */ - @Override - public void zoomRangeAxes(double factor, PlotRenderingInfo info, - Point2D source) { - this.rangeAxis.resizeRange(factor); - } - - /** - * Multiplies the range on the range axis by the specified factor. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point (in Java2D space). - * @param useAnchor use source point as zoom anchor? - * - * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) - */ - @Override - public void zoomRangeAxes(double factor, PlotRenderingInfo info, - Point2D source, boolean useAnchor) { - - if (useAnchor) { - // get the source coordinate - this plot has always a VERTICAL - // orientation - double sourceY = source.getY(); - double anchorY = this.rangeAxis.java2DToValue(sourceY, - info.getDataArea(), RectangleEdge.LEFT); - this.rangeAxis.resizeRange2(factor, anchorY); - } - else { - this.rangeAxis.resizeRange(factor); - } - - } - - /** - * Zooms in on the range axes. - * - * @param lowerPercent the new lower bound as a percentage of the current - * range. - * @param upperPercent the new upper bound as a percentage of the current - * range. - * @param info the plot rendering info. - * @param source the source point. - */ - @Override - public void zoomRangeAxes(double lowerPercent, double upperPercent, - PlotRenderingInfo info, Point2D source) { - this.rangeAxis.zoomRange(lowerPercent, upperPercent); - } - - /** - * Returns {@code true}. - * - * @return A boolean. - */ - @Override - public boolean isDomainZoomable() { - return true; - } - - /** - * Returns {@code true}. - * - * @return A boolean. - */ - @Override - public boolean isRangeZoomable() { - return true; - } - - /** - * Returns {@code true} if panning is enabled for the domain axes, - * and {@code false} otherwise. - * - * @return A boolean. - */ - @Override - public boolean isDomainPannable() { - return this.domainPannable; - } - - /** - * Sets the flag that enables or disables panning of the plot along the - * domain axes. - * - * @param pannable the new flag value. - */ - public void setDomainPannable(boolean pannable) { - this.domainPannable = pannable; - } - - /** - * Returns {@code true} if panning is enabled for the range axes, - * and {@code false} otherwise. - * - * @return A boolean. - */ - @Override - public boolean isRangePannable() { - return this.rangePannable; - } - - /** - * Sets the flag that enables or disables panning of the plot along - * the range axes. - * - * @param pannable the new flag value. - */ - public void setRangePannable(boolean pannable) { - this.rangePannable = pannable; - } - - /** - * Pans the domain axes by the specified percentage. - * - * @param percent the distance to pan (as a percentage of the axis length). - * @param info the plot info - * @param source the source point where the pan action started. - */ - @Override - public void panDomainAxes(double percent, PlotRenderingInfo info, - Point2D source) { - if (!isDomainPannable() || this.domainAxis == null) { - return; - } - double length = this.domainAxis.getRange().getLength(); - double adj = percent * length; - if (this.domainAxis.isInverted()) { - adj = -adj; - } - this.domainAxis.setRange(this.domainAxis.getLowerBound() + adj, - this.domainAxis.getUpperBound() + adj); - } - - /** - * Pans the range axes by the specified percentage. - * - * @param percent the distance to pan (as a percentage of the axis length). - * @param info the plot info - * @param source the source point where the pan action started. - */ - @Override - public void panRangeAxes(double percent, PlotRenderingInfo info, - Point2D source) { - if (!isRangePannable() || this.rangeAxis == null) { - return; - } - double length = this.rangeAxis.getRange().getLength(); - double adj = percent * length; - if (this.rangeAxis.isInverted()) { - adj = -adj; - } - this.rangeAxis.setRange(this.rangeAxis.getLowerBound() + adj, - this.rangeAxis.getUpperBound() + adj); - } - - /** - * Tests an arbitrary object for equality with this plot. Note that - * {@code FastScatterPlot} carries its data around with it (rather - * than referencing a dataset), and the data is included in the - * equality test. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!super.equals(obj)) { - return false; - } - if (!(obj instanceof FastScatterPlot)) { - return false; - } - FastScatterPlot that = (FastScatterPlot) obj; - if (this.domainPannable != that.domainPannable) { - return false; - } - if (this.rangePannable != that.rangePannable) { - return false; - } - if (!ArrayUtils.equal(this.data, that.data)) { - return false; - } - if (!Objects.equals(this.domainAxis, that.domainAxis)) { - return false; - } - if (!Objects.equals(this.rangeAxis, that.rangeAxis)) { - return false; - } - if (!PaintUtils.equal(this.paint, that.paint)) { - return false; - } - if (this.domainGridlinesVisible != that.domainGridlinesVisible) { - return false; - } - if (!PaintUtils.equal(this.domainGridlinePaint, - that.domainGridlinePaint)) { - return false; - } - if (!Objects.equals(this.domainGridlineStroke, - that.domainGridlineStroke)) { - return false; - } - if (!this.rangeGridlinesVisible == that.rangeGridlinesVisible) { - return false; - } - if (!PaintUtils.equal(this.rangeGridlinePaint, - that.rangeGridlinePaint)) { - return false; - } - if (!Objects.equals(this.rangeGridlineStroke, - that.rangeGridlineStroke)) { - return false; - } - return true; - } - - /** - * Returns a clone of the plot. - * - * @return A clone. - * - * @throws CloneNotSupportedException if some component of the plot does - * not support cloning. - */ - @Override - public Object clone() throws CloneNotSupportedException { - - FastScatterPlot clone = (FastScatterPlot) super.clone(); - if (this.data != null) { - clone.data = ArrayUtils.clone(this.data); - } - if (this.domainAxis != null) { - clone.domainAxis = (ValueAxis) this.domainAxis.clone(); - clone.domainAxis.setPlot(clone); - clone.domainAxis.addChangeListener(clone); - } - if (this.rangeAxis != null) { - clone.rangeAxis = (ValueAxis) this.rangeAxis.clone(); - clone.rangeAxis.setPlot(clone); - clone.rangeAxis.addChangeListener(clone); - } - return clone; - - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writePaint(this.paint, stream); - SerialUtils.writeStroke(this.domainGridlineStroke, stream); - SerialUtils.writePaint(this.domainGridlinePaint, stream); - SerialUtils.writeStroke(this.rangeGridlineStroke, stream); - SerialUtils.writePaint(this.rangeGridlinePaint, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - - this.paint = SerialUtils.readPaint(stream); - this.domainGridlineStroke = SerialUtils.readStroke(stream); - this.domainGridlinePaint = SerialUtils.readPaint(stream); - - this.rangeGridlineStroke = SerialUtils.readStroke(stream); - this.rangeGridlinePaint = SerialUtils.readPaint(stream); - - if (this.domainAxis != null) { - this.domainAxis.addChangeListener(this); - } - - if (this.rangeAxis != null) { - this.rangeAxis.addChangeListener(this); - } - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * -------------------- + * FastScatterPlot.java + * -------------------- + * (C) Copyright 2002-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): Arnaud Lelievre; + * Ulrich Voigt (patch #307); + * + */ + +package org.jfree.chart.plot; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.ResourceBundle; + +import org.jfree.chart.axis.AxisSpace; +import org.jfree.chart.axis.AxisState; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.axis.ValueTick; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.chart.util.ArrayUtils; +import org.jfree.chart.util.PaintUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.ResourceBundleWrapper; +import org.jfree.chart.util.SerialUtils; +import org.jfree.data.Range; + +/** + * A fast scatter plot. + */ +public class FastScatterPlot extends Plot implements ValueAxisPlot, Pannable, + Zoomable, Cloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = 7871545897358563521L; + + /** The default grid line stroke. */ + public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, + BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[] + {2.0f, 2.0f}, 0.0f); + + /** The default grid line paint. */ + public static final Paint DEFAULT_GRIDLINE_PAINT = Color.LIGHT_GRAY; + + /** The data. */ + private float[][] data; + + /** The x data range. */ + private Range xDataRange; + + /** The y data range. */ + private Range yDataRange; + + /** The domain axis (used for the x-values). */ + private ValueAxis domainAxis; + + /** The range axis (used for the y-values). */ + private ValueAxis rangeAxis; + + /** The paint used to plot data points. */ + private transient Paint paint; + + /** A flag that controls whether the domain grid-lines are visible. */ + private boolean domainGridlinesVisible; + + /** The stroke used to draw the domain grid-lines. */ + private transient Stroke domainGridlineStroke; + + /** The paint used to draw the domain grid-lines. */ + private transient Paint domainGridlinePaint; + + /** A flag that controls whether the range grid-lines are visible. */ + private boolean rangeGridlinesVisible; + + /** The stroke used to draw the range grid-lines. */ + private transient Stroke rangeGridlineStroke; + + /** The paint used to draw the range grid-lines. */ + private transient Paint rangeGridlinePaint; + + /** + * A flag that controls whether or not panning is enabled for the domain + * axis. + */ + private boolean domainPannable; + + /** + * A flag that controls whether or not panning is enabled for the range + * axis. + */ + private boolean rangePannable; + + /** The resourceBundle for the localization. */ + protected static ResourceBundle localizationResources + = ResourceBundleWrapper.getBundle( + "org.jfree.chart.plot.LocalizationBundle"); + + /** + * Creates a new instance of {@code FastScatterPlot} with default + * axes. + */ + public FastScatterPlot() { + this(null, new NumberAxis("X"), new NumberAxis("Y")); + } + + /** + * Creates a new fast scatter plot. + *

+ * The data is an array of x, y values: data[0][i] = x, data[1][i] = y. + * + * @param data the data ({@code null} permitted). + * @param domainAxis the domain (x) axis ({@code null} not permitted). + * @param rangeAxis the range (y) axis ({@code null} not permitted). + */ + public FastScatterPlot(float[][] data, + ValueAxis domainAxis, ValueAxis rangeAxis) { + + super(); + Args.nullNotPermitted(domainAxis, "domainAxis"); + Args.nullNotPermitted(rangeAxis, "rangeAxis"); + + this.data = data; + this.xDataRange = calculateXDataRange(data); + this.yDataRange = calculateYDataRange(data); + this.domainAxis = domainAxis; + this.domainAxis.setPlot(this); + this.domainAxis.addChangeListener(this); + this.rangeAxis = rangeAxis; + this.rangeAxis.setPlot(this); + this.rangeAxis.addChangeListener(this); + + this.paint = Color.RED; + + this.domainGridlinesVisible = true; + this.domainGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT; + this.domainGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE; + + this.rangeGridlinesVisible = true; + this.rangeGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT; + this.rangeGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE; + } + + /** + * Returns a short string describing the plot type. + * + * @return A short string describing the plot type. + */ + @Override + public String getPlotType() { + return localizationResources.getString("Fast_Scatter_Plot"); + } + + /** + * Returns the data array used by the plot. + * + * @return The data array (possibly {@code null}). + * + * @see #setData(float[][]) + */ + public float[][] getData() { + return this.data; + } + + /** + * Sets the data array used by the plot and calls {@link #fireChangeEvent()}. + * + * @param data the data array ({@code null} permitted). + * + * @see #getData() + */ + public void setData(float[][] data) { + this.data = data; + fireChangeEvent(); + } + + /** + * Returns the orientation of the plot. + * + * @return The orientation (always {@link PlotOrientation#VERTICAL}). + */ + @Override + public PlotOrientation getOrientation() { + return PlotOrientation.VERTICAL; + } + + /** + * Returns the domain axis for the plot. + * + * @return The domain axis (never {@code null}). + * + * @see #setDomainAxis(ValueAxis) + */ + public ValueAxis getDomainAxis() { + return this.domainAxis; + } + + /** + * Sets the domain axis and calls {@link #fireChangeEvent()}. + * + * @param axis the axis ({@code null} not permitted). + * + * @see #getDomainAxis() + */ + public void setDomainAxis(ValueAxis axis) { + Args.nullNotPermitted(axis, "axis"); + this.domainAxis = axis; + fireChangeEvent(); + } + + /** + * Returns the range axis for the plot. + * + * @return The range axis (never {@code null}). + * + * @see #setRangeAxis(ValueAxis) + */ + public ValueAxis getRangeAxis() { + return this.rangeAxis; + } + + /** + * Sets the range axis and calls {@link #fireChangeEvent()}. + * + * @param axis the axis ({@code null} not permitted). + * + * @see #getRangeAxis() + */ + public void setRangeAxis(ValueAxis axis) { + Args.nullNotPermitted(axis, "axis"); + this.rangeAxis = axis; + fireChangeEvent(); + } + + /** + * Returns the paint used to plot data points. The default is + * {@code Color.RED}. + * + * @return The paint. + * + * @see #setPaint(Paint) + */ + public Paint getPaint() { + return this.paint; + } + + /** + * Sets the color for the data points and calls {@link #fireChangeEvent()}. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getPaint() + */ + public void setPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.paint = paint; + fireChangeEvent(); + } + + /** + * Returns {@code true} if the domain gridlines are visible, and + * {@code false} otherwise. + * + * @return {@code true} or {@code false}. + * + * @see #setDomainGridlinesVisible(boolean) + * @see #setDomainGridlinePaint(Paint) + */ + public boolean isDomainGridlinesVisible() { + return this.domainGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not the domain grid-lines are + * visible. If the flag value is changed, calls {@link #fireChangeEvent()}. + * + * @param visible the new value of the flag. + * + * @see #getDomainGridlinePaint() + */ + public void setDomainGridlinesVisible(boolean visible) { + if (this.domainGridlinesVisible != visible) { + this.domainGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the stroke for the grid-lines (if any) plotted against the + * domain axis. + * + * @return The stroke (never {@code null}). + * + * @see #setDomainGridlineStroke(Stroke) + */ + public Stroke getDomainGridlineStroke() { + return this.domainGridlineStroke; + } + + /** + * Sets the stroke for the grid lines plotted against the domain axis and + * calls {@link #fireChangeEvent()}. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getDomainGridlineStroke() + */ + public void setDomainGridlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.domainGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the grid lines (if any) plotted against the domain + * axis. + * + * @return The paint (never {@code null}). + * + * @see #setDomainGridlinePaint(Paint) + */ + public Paint getDomainGridlinePaint() { + return this.domainGridlinePaint; + } + + /** + * Sets the paint for the grid lines plotted against the domain axis and + * calls {@link #fireChangeEvent()}. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getDomainGridlinePaint() + */ + public void setDomainGridlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.domainGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns {@code true} if the range axis grid is visible, and + * {@code false} otherwise. + * + * @return {@code true} or {@code false}. + * + * @see #setRangeGridlinesVisible(boolean) + */ + public boolean isRangeGridlinesVisible() { + return this.rangeGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not the range axis grid lines are + * visible. If the flag value is changed, calls {@link #fireChangeEvent()}. + * + * @param visible the new value of the flag. + * + * @see #isRangeGridlinesVisible() + */ + public void setRangeGridlinesVisible(boolean visible) { + if (this.rangeGridlinesVisible != visible) { + this.rangeGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the stroke for the grid lines (if any) plotted against the range + * axis. + * + * @return The stroke (never {@code null}). + * + * @see #setRangeGridlineStroke(Stroke) + */ + public Stroke getRangeGridlineStroke() { + return this.rangeGridlineStroke; + } + + /** + * Sets the stroke for the grid lines plotted against the range axis and + * calls {@link #fireChangeEvent() }. + * + * @param stroke the stroke ({@code null} permitted). + * + * @see #getRangeGridlineStroke() + */ + public void setRangeGridlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.rangeGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the grid lines (if any) plotted against the range + * axis. + * + * @return The paint (never {@code null}). + * + * @see #setRangeGridlinePaint(Paint) + */ + public Paint getRangeGridlinePaint() { + return this.rangeGridlinePaint; + } + + /** + * Sets the paint for the grid lines plotted against the range axis and + * calls {@link #fireChangeEvent() }. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getRangeGridlinePaint() + */ + public void setRangeGridlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.rangeGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Draws the fast scatter plot on a Java 2D graphics device (such as the + * screen or a printer). + * + * @param g2 the graphics device. + * @param area the area within which the plot (including axis labels) + * should be drawn. + * @param anchor the anchor point ({@code null} permitted). + * @param parentState the state from the parent plot (ignored). + * @param info collects chart drawing information ({@code null} + * permitted). + */ + @Override + public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, + PlotState parentState, PlotRenderingInfo info) { + + // set up info collection... + if (info != null) { + info.setPlotArea(area); + } + + // adjust the drawing area for plot insets (if any)... + RectangleInsets insets = getInsets(); + insets.trim(area); + + AxisSpace space = new AxisSpace(); + space = this.domainAxis.reserveSpace(g2, this, area, + RectangleEdge.BOTTOM, space); + space = this.rangeAxis.reserveSpace(g2, this, area, RectangleEdge.LEFT, + space); + Rectangle2D dataArea = space.shrink(area, null); + + if (info != null) { + info.setDataArea(dataArea); + } + + // draw the plot background and axes... + drawBackground(g2, dataArea); + + AxisState domainAxisState = this.domainAxis.draw(g2, + dataArea.getMaxY(), area, dataArea, RectangleEdge.BOTTOM, info); + AxisState rangeAxisState = this.rangeAxis.draw(g2, dataArea.getMinX(), + area, dataArea, RectangleEdge.LEFT, info); + drawDomainGridlines(g2, dataArea, domainAxisState.getTicks()); + drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks()); + + Shape originalClip = g2.getClip(); + Composite originalComposite = g2.getComposite(); + + g2.clip(dataArea); + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + getForegroundAlpha())); + + render(g2, dataArea, info, null); + + g2.setClip(originalClip); + g2.setComposite(originalComposite); + drawOutline(g2, dataArea); + + } + + /** + * Draws a representation of the data within the dataArea region. The + * {@code info} and {@code crosshairState} arguments may be + * {@code null}. + * + * @param g2 the graphics device. + * @param dataArea the region in which the data is to be drawn. + * @param info an optional object for collection dimension information. + * @param crosshairState collects crosshair information ({@code null} + * permitted). + */ + public void render(Graphics2D g2, Rectangle2D dataArea, + PlotRenderingInfo info, CrosshairState crosshairState) { + g2.setPaint(this.paint); + + // if the axes use a linear scale, you can uncomment the code below and + // switch to the alternative transX/transY calculation inside the loop + // that follows - it is a little bit faster then. + // + // int xx = (int) dataArea.getMinX(); + // int ww = (int) dataArea.getWidth(); + // int yy = (int) dataArea.getMaxY(); + // int hh = (int) dataArea.getHeight(); + // double domainMin = this.domainAxis.getLowerBound(); + // double domainLength = this.domainAxis.getUpperBound() - domainMin; + // double rangeMin = this.rangeAxis.getLowerBound(); + // double rangeLength = this.rangeAxis.getUpperBound() - rangeMin; + + if (this.data != null) { + for (int i = 0; i < this.data[0].length; i++) { + float x = this.data[0][i]; + float y = this.data[1][i]; + + //int transX = (int) (xx + ww * (x - domainMin) / domainLength); + //int transY = (int) (yy - hh * (y - rangeMin) / rangeLength); + int transX = (int) this.domainAxis.valueToJava2D(x, dataArea, + RectangleEdge.BOTTOM); + int transY = (int) this.rangeAxis.valueToJava2D(y, dataArea, + RectangleEdge.LEFT); + g2.fillRect(transX, transY, 1, 1); + } + } + } + + /** + * Draws the gridlines for the plot, if they are visible. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param ticks the ticks. + */ + protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea, + List ticks) { + if (!isDomainGridlinesVisible()) { + return; + } + Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_NORMALIZE); + Iterator iterator = ticks.iterator(); + while (iterator.hasNext()) { + ValueTick tick = (ValueTick) iterator.next(); + double v = this.domainAxis.valueToJava2D(tick.getValue(), + dataArea, RectangleEdge.BOTTOM); + Line2D line = new Line2D.Double(v, dataArea.getMinY(), v, + dataArea.getMaxY()); + g2.setPaint(getDomainGridlinePaint()); + g2.setStroke(getDomainGridlineStroke()); + g2.draw(line); + } + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); + } + + /** + * Draws the gridlines for the plot, if they are visible. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param ticks the ticks. + */ + protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea, + List ticks) { + + if (!isRangeGridlinesVisible()) { + return; + } + Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_NORMALIZE); + + Iterator iterator = ticks.iterator(); + while (iterator.hasNext()) { + ValueTick tick = (ValueTick) iterator.next(); + double v = this.rangeAxis.valueToJava2D(tick.getValue(), + dataArea, RectangleEdge.LEFT); + Line2D line = new Line2D.Double(dataArea.getMinX(), v, + dataArea.getMaxX(), v); + g2.setPaint(getRangeGridlinePaint()); + g2.setStroke(getRangeGridlineStroke()); + g2.draw(line); + } + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); + } + + /** + * Returns the range of data values to be plotted along the axis, or + * {@code null} if the specified axis isn't the domain axis or the + * range axis for the plot. + * + * @param axis the axis ({@code null} permitted). + * + * @return The range (possibly {@code null}). + */ + @Override + public Range getDataRange(ValueAxis axis) { + Range result = null; + if (axis == this.domainAxis) { + result = this.xDataRange; + } + else if (axis == this.rangeAxis) { + result = this.yDataRange; + } + return result; + } + + /** + * Calculates the X data range. + * + * @param data the data ({@code null} permitted). + * + * @return The range. + */ + private Range calculateXDataRange(float[][] data) { + + Range result = null; + + if (data != null) { + float lowest = Float.POSITIVE_INFINITY; + float highest = Float.NEGATIVE_INFINITY; + for (int i = 0; i < data[0].length; i++) { + float v = data[0][i]; + if (v < lowest) { + lowest = v; + } + if (v > highest) { + highest = v; + } + } + if (lowest <= highest) { + result = new Range(lowest, highest); + } + } + + return result; + + } + + /** + * Calculates the Y data range. + * + * @param data the data ({@code null} permitted). + * + * @return The range. + */ + private Range calculateYDataRange(float[][] data) { + + Range result = null; + if (data != null) { + float lowest = Float.POSITIVE_INFINITY; + float highest = Float.NEGATIVE_INFINITY; + for (int i = 0; i < data[0].length; i++) { + float v = data[1][i]; + if (v < lowest) { + lowest = v; + } + if (v > highest) { + highest = v; + } + } + if (lowest <= highest) { + result = new Range(lowest, highest); + } + } + return result; + + } + + /** + * Multiplies the range on the domain axis by the specified factor. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point. + */ + @Override + public void zoomDomainAxes(double factor, PlotRenderingInfo info, + Point2D source) { + this.domainAxis.resizeRange(factor); + } + + /** + * Multiplies the range on the domain axis by the specified factor. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point (in Java2D space). + * @param useAnchor use source point as zoom anchor? + * + * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean) + */ + @Override + public void zoomDomainAxes(double factor, PlotRenderingInfo info, + Point2D source, boolean useAnchor) { + + if (useAnchor) { + // get the source coordinate - this plot has always a VERTICAL + // orientation + double sourceX = source.getX(); + double anchorX = this.domainAxis.java2DToValue(sourceX, + info.getDataArea(), RectangleEdge.BOTTOM); + this.domainAxis.resizeRange2(factor, anchorX); + } + else { + this.domainAxis.resizeRange(factor); + } + + } + + /** + * Zooms in on the domain axes. + * + * @param lowerPercent the new lower bound as a percentage of the current + * range. + * @param upperPercent the new upper bound as a percentage of the current + * range. + * @param info the plot rendering info. + * @param source the source point. + */ + @Override + public void zoomDomainAxes(double lowerPercent, double upperPercent, + PlotRenderingInfo info, Point2D source) { + this.domainAxis.zoomRange(lowerPercent, upperPercent); + } + + /** + * Multiplies the range on the range axis/axes by the specified factor. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point. + */ + @Override + public void zoomRangeAxes(double factor, PlotRenderingInfo info, + Point2D source) { + this.rangeAxis.resizeRange(factor); + } + + /** + * Multiplies the range on the range axis by the specified factor. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point (in Java2D space). + * @param useAnchor use source point as zoom anchor? + * + * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) + */ + @Override + public void zoomRangeAxes(double factor, PlotRenderingInfo info, + Point2D source, boolean useAnchor) { + + if (useAnchor) { + // get the source coordinate - this plot has always a VERTICAL + // orientation + double sourceY = source.getY(); + double anchorY = this.rangeAxis.java2DToValue(sourceY, + info.getDataArea(), RectangleEdge.LEFT); + this.rangeAxis.resizeRange2(factor, anchorY); + } + else { + this.rangeAxis.resizeRange(factor); + } + + } + + /** + * Zooms in on the range axes. + * + * @param lowerPercent the new lower bound as a percentage of the current + * range. + * @param upperPercent the new upper bound as a percentage of the current + * range. + * @param info the plot rendering info. + * @param source the source point. + */ + @Override + public void zoomRangeAxes(double lowerPercent, double upperPercent, + PlotRenderingInfo info, Point2D source) { + this.rangeAxis.zoomRange(lowerPercent, upperPercent); + } + + /** + * Returns {@code true}. + * + * @return A boolean. + */ + @Override + public boolean isDomainZoomable() { + return true; + } + + /** + * Returns {@code true}. + * + * @return A boolean. + */ + @Override + public boolean isRangeZoomable() { + return true; + } + + /** + * Returns {@code true} if panning is enabled for the domain axes, + * and {@code false} otherwise. + * + * @return A boolean. + */ + @Override + public boolean isDomainPannable() { + return this.domainPannable; + } + + /** + * Sets the flag that enables or disables panning of the plot along the + * domain axes. + * + * @param pannable the new flag value. + */ + public void setDomainPannable(boolean pannable) { + this.domainPannable = pannable; + } + + /** + * Returns {@code true} if panning is enabled for the range axes, + * and {@code false} otherwise. + * + * @return A boolean. + */ + @Override + public boolean isRangePannable() { + return this.rangePannable; + } + + /** + * Sets the flag that enables or disables panning of the plot along + * the range axes. + * + * @param pannable the new flag value. + */ + public void setRangePannable(boolean pannable) { + this.rangePannable = pannable; + } + + /** + * Pans the domain axes by the specified percentage. + * + * @param percent the distance to pan (as a percentage of the axis length). + * @param info the plot info + * @param source the source point where the pan action started. + */ + @Override + public void panDomainAxes(double percent, PlotRenderingInfo info, + Point2D source) { + if (!isDomainPannable() || this.domainAxis == null) { + return; + } + double length = this.domainAxis.getRange().getLength(); + double adj = percent * length; + if (this.domainAxis.isInverted()) { + adj = -adj; + } + this.domainAxis.setRange(this.domainAxis.getLowerBound() + adj, + this.domainAxis.getUpperBound() + adj); + } + + /** + * Pans the range axes by the specified percentage. + * + * @param percent the distance to pan (as a percentage of the axis length). + * @param info the plot info + * @param source the source point where the pan action started. + */ + @Override + public void panRangeAxes(double percent, PlotRenderingInfo info, + Point2D source) { + if (!isRangePannable() || this.rangeAxis == null) { + return; + } + double length = this.rangeAxis.getRange().getLength(); + double adj = percent * length; + if (this.rangeAxis.isInverted()) { + adj = -adj; + } + this.rangeAxis.setRange(this.rangeAxis.getLowerBound() + adj, + this.rangeAxis.getUpperBound() + adj); + } + + /** + * Tests an arbitrary object for equality with this plot. Note that + * {@code FastScatterPlot} carries its data around with it (rather + * than referencing a dataset), and the data is included in the + * equality test. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof FastScatterPlot)) { + return false; + } + FastScatterPlot that = (FastScatterPlot) obj; + if (this.domainPannable != that.domainPannable) { + return false; + } + if (this.rangePannable != that.rangePannable) { + return false; + } + if (!ArrayUtils.equal(this.data, that.data)) { + return false; + } + if (!Objects.equals(this.domainAxis, that.domainAxis)) { + return false; + } + if (!Objects.equals(this.rangeAxis, that.rangeAxis)) { + return false; + } + if (!PaintUtils.equal(this.paint, that.paint)) { + return false; + } + if (this.domainGridlinesVisible != that.domainGridlinesVisible) { + return false; + } + if (!PaintUtils.equal(this.domainGridlinePaint, + that.domainGridlinePaint)) { + return false; + } + if (!Objects.equals(this.domainGridlineStroke, + that.domainGridlineStroke)) { + return false; + } + if (!this.rangeGridlinesVisible == that.rangeGridlinesVisible) { + return false; + } + if (!PaintUtils.equal(this.rangeGridlinePaint, + that.rangeGridlinePaint)) { + return false; + } + if (!Objects.equals(this.rangeGridlineStroke, + that.rangeGridlineStroke)) { + return false; + } + return true; + } + + /** + * Returns a clone of the plot. + * + * @return A clone. + * + * @throws CloneNotSupportedException if some component of the plot does + * not support cloning. + */ + @Override + public Object clone() throws CloneNotSupportedException { + + FastScatterPlot clone = (FastScatterPlot) super.clone(); + if (this.data != null) { + clone.data = ArrayUtils.clone(this.data); + } + if (this.domainAxis != null) { + clone.domainAxis = (ValueAxis) this.domainAxis.clone(); + clone.domainAxis.setPlot(clone); + clone.domainAxis.addChangeListener(clone); + } + if (this.rangeAxis != null) { + clone.rangeAxis = (ValueAxis) this.rangeAxis.clone(); + clone.rangeAxis.setPlot(clone); + clone.rangeAxis.addChangeListener(clone); + } + return clone; + + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writePaint(this.paint, stream); + SerialUtils.writeStroke(this.domainGridlineStroke, stream); + SerialUtils.writePaint(this.domainGridlinePaint, stream); + SerialUtils.writeStroke(this.rangeGridlineStroke, stream); + SerialUtils.writePaint(this.rangeGridlinePaint, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + + this.paint = SerialUtils.readPaint(stream); + this.domainGridlineStroke = SerialUtils.readStroke(stream); + this.domainGridlinePaint = SerialUtils.readPaint(stream); + + this.rangeGridlineStroke = SerialUtils.readStroke(stream); + this.rangeGridlinePaint = SerialUtils.readPaint(stream); + + if (this.domainAxis != null) { + this.domainAxis.addChangeListener(this); + } + + if (this.rangeAxis != null) { + this.rangeAxis.addChangeListener(this); + } + } + +} diff --git a/src/main/java/org/jfree/chart/plot/MeterPlot.java b/src/main/java/org/jfree/chart/plot/MeterPlot.java index c0346df64..562651e92 100644 --- a/src/main/java/org/jfree/chart/plot/MeterPlot.java +++ b/src/main/java/org/jfree/chart/plot/MeterPlot.java @@ -42,7 +42,6 @@ import org.jfree.chart.LegendItem; import org.jfree.chart.LegendItemCollection; -import org.jfree.chart.event.PlotChangeEvent; import org.jfree.chart.text.TextUtils; import org.jfree.chart.ui.RectangleInsets; import org.jfree.chart.ui.TextAnchor; @@ -224,8 +223,7 @@ public DialShape getDialShape() { } /** - * Sets the dial shape and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the dial shape and calls {@link #fireChangeEvent()}. * * @param shape the shape ({@code null} not permitted). * @@ -250,13 +248,13 @@ public int getMeterAngle() { } /** - * Sets the angle (in degrees) for the whole range of the dial and sends - * a {@link PlotChangeEvent} to all registered listeners. - * - * @param angle the angle (in degrees, in the range 1-360). - * - * @see #getMeterAngle() - */ + * Sets the angle (in degrees) for the whole range of the dial and calls + * {@link #fireChangeEvent()}. + * + * @param angle the angle (in degrees, in the range 1-360). + * + * @see #getMeterAngle() + */ public void setMeterAngle(int angle) { if (angle < 1 || angle > 360) { throw new IllegalArgumentException("Invalid 'angle' (" + angle @@ -278,8 +276,7 @@ public Range getRange() { } /** - * Sets the range for the dial and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the range for the dial and calls {@link #fireChangeEvent()}. * * @param range the range ({@code null} not permitted and zero-length * ranges not permitted). @@ -308,8 +305,7 @@ public double getTickSize() { } /** - * Sets the tick size and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the tick size and calls {@link #fireChangeEvent()}. * * @param size the tick size (must be > 0). * @@ -336,13 +332,13 @@ public Paint getTickPaint() { } /** - * Sets the paint used to draw the tick labels around the dial and sends - * a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getTickPaint() - */ + * Sets the paint used to draw the tick labels around the dial and calls + * {@link #fireChangeEvent()}. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getTickPaint() + */ public void setTickPaint(Paint paint) { Args.nullNotPermitted(paint, "paint"); this.tickPaint = paint; @@ -361,8 +357,7 @@ public String getUnits() { } /** - * Sets the units for the dial and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the units for the dial and calls {@link #fireChangeEvent()}. * * @param units the units ({@code null} permitted). * @@ -385,8 +380,7 @@ public Paint getNeedlePaint() { } /** - * Sets the paint used to display the needle and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the paint used to display the needle and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -411,7 +405,7 @@ public boolean getTickLabelsVisible() { /** * Sets the flag that controls whether or not the tick labels are visible - * and sends a {@link PlotChangeEvent} to all registered listeners. + * and calls {@link #fireChangeEvent()}. * * @param visible the flag. * @@ -436,8 +430,7 @@ public Font getTickLabelFont() { } /** - * Sets the tick label font and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the tick label font and calls {@link #fireChangeEvent()}. * * @param font the font ({@code null} not permitted). * @@ -463,8 +456,7 @@ public Paint getTickLabelPaint() { } /** - * Sets the tick label paint and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the tick label paint and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -517,8 +509,7 @@ public NumberFormat getTickLabelFormat() { } /** - * Sets the format for the tick labels and sends a {@link PlotChangeEvent} - * to all registered listeners. + * Sets the format for the tick labels and calls {@link #fireChangeEvent()}. * * @param format the format ({@code null} not permitted). * @@ -542,8 +533,7 @@ public Font getValueFont() { } /** - * Sets the font used to display the value label and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the font used to display the value label and calls {@link #fireChangeEvent()}. * * @param font the font ({@code null} not permitted). * @@ -567,8 +557,7 @@ public Paint getValuePaint() { } /** - * Sets the paint used to display the value label and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the paint used to display the value label and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -618,8 +607,7 @@ public boolean getDrawBorder() { /** * Sets the flag that controls whether or not a rectangular border is drawn - * around the plot area and sends a {@link PlotChangeEvent} to all - * registered listeners. + * around the plot area and calls {@link #fireChangeEvent()}. * * @param draw the flag. * @@ -643,8 +631,7 @@ public Paint getDialOutlinePaint() { } /** - * Sets the dial outline paint and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the dial outline paint and calls {@link #fireChangeEvent()}. * * @param paint the paint. * @@ -668,7 +655,7 @@ public ValueDataset getDataset() { /** * Sets the dataset for the plot, replacing the existing dataset if there - * is one, and triggers a {@link PlotChangeEvent}. + * is one, and calls {@link #datasetChanged(DatasetChangeEvent)}. * * @param dataset the dataset ({@code null} permitted). * @@ -708,8 +695,7 @@ public List getIntervals() { } /** - * Adds an interval and sends a {@link PlotChangeEvent} to all registered - * listeners. + * Adds an interval and calls {@link #fireChangeEvent()}. * * @param interval the interval ({@code null} not permitted). * @@ -723,8 +709,7 @@ public void addInterval(MeterInterval interval) { } /** - * Clears the intervals for the plot and sends a {@link PlotChangeEvent} to - * all registered listeners. + * Clears the intervals for the plot and calls {@link #fireChangeEvent()}. * * @see #addInterval(MeterInterval) */ diff --git a/src/main/java/org/jfree/chart/plot/MultiplePiePlot.java b/src/main/java/org/jfree/chart/plot/MultiplePiePlot.java index 8c454698d..ddd412fac 100644 --- a/src/main/java/org/jfree/chart/plot/MultiplePiePlot.java +++ b/src/main/java/org/jfree/chart/plot/MultiplePiePlot.java @@ -59,7 +59,6 @@ import org.jfree.chart.JFreeChart; import org.jfree.chart.LegendItem; import org.jfree.chart.LegendItemCollection; -import org.jfree.chart.event.PlotChangeEvent; import org.jfree.chart.title.TextTitle; import org.jfree.chart.ui.RectangleEdge; import org.jfree.chart.ui.RectangleInsets; @@ -156,8 +155,7 @@ public CategoryDataset getDataset() { } /** - * Sets the dataset used by the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. + * Sets the dataset used by the plot and calls {@link #fireChangeEvent()}. * * @param dataset the dataset ({@code null} permitted). */ @@ -220,11 +218,11 @@ public TableOrder getDataExtractOrder() { } /** - * Sets the data extract order (by row or by column) and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param order the order ({@code null} not permitted). - */ + * Sets the data extract order (by row or by column) and calls + * {@link #fireChangeEvent()}. + * + * @param order the order ({@code null} not permitted). + */ public void setDataExtractOrder(TableOrder order) { Args.nullNotPermitted(order, "order"); this.dataExtractOrder = order; @@ -286,7 +284,7 @@ public Paint getAggregatedItemsPaint() { /** * Sets the paint used to draw the pie section representing the aggregated - * items and sends a {@link PlotChangeEvent} to all registered listeners. + * items and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). */ @@ -319,8 +317,7 @@ public Shape getLegendItemShape() { } /** - * Sets the shape used for legend items and sends a {@link PlotChangeEvent} - * to all registered listeners. + * Sets the shape used for legend items and calls {@link #fireChangeEvent()}. * * @param shape the shape ({@code null} not permitted). * diff --git a/src/main/java/org/jfree/chart/plot/Pannable.java b/src/main/java/org/jfree/chart/plot/Pannable.java index 585b9ac64..0bcdd8567 100644 --- a/src/main/java/org/jfree/chart/plot/Pannable.java +++ b/src/main/java/org/jfree/chart/plot/Pannable.java @@ -39,10 +39,8 @@ import java.awt.geom.Point2D; -import org.jfree.chart.ChartPanel; - /** - * An interface that the {@link ChartPanel} class uses to communicate with + * An interface that the {@link org.jfree.chart.ChartPanel} class uses to communicate with * plots that support panning. */ public interface Pannable { diff --git a/src/main/java/org/jfree/chart/plot/PiePlot.java b/src/main/java/org/jfree/chart/plot/PiePlot.java index eddb1e191..8e4d67240 100644 --- a/src/main/java/org/jfree/chart/plot/PiePlot.java +++ b/src/main/java/org/jfree/chart/plot/PiePlot.java @@ -80,7 +80,6 @@ import org.jfree.chart.StrokeMap; import org.jfree.chart.entity.EntityCollection; import org.jfree.chart.entity.PieSectionEntity; -import org.jfree.chart.event.PlotChangeEvent; import org.jfree.chart.labels.PieSectionLabelGenerator; import org.jfree.chart.labels.PieToolTipGenerator; import org.jfree.chart.labels.StandardPieSectionLabelGenerator; @@ -541,10 +540,10 @@ public double getStartAngle() { } /** - * Sets the starting angle and sends a {@link PlotChangeEvent} to all - * registered listeners. The initial default value is 90 degrees, which - * corresponds to 12 o'clock. A value of zero corresponds to 3 o'clock... - * this is the encoding used by Java's Arc2D class. + * Sets the starting angle and calls {@link #fireChangeEvent()}. + * The initial default value is 90 degrees, which corresponds to 12 o'clock. + * A value of zero corresponds to 3 o'clock... this is the encoding used by + * Java's Arc2D class. * * @param angle the angle (in degrees). * @@ -568,8 +567,8 @@ public Rotation getDirection() { } /** - * Sets the direction in which the pie sections are drawn and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the direction in which the pie sections are drawn and calls + * {@link #fireChangeEvent()}. * * @param direction the direction ({@code null} not permitted). * @@ -579,7 +578,6 @@ public void setDirection(Rotation direction) { Args.nullNotPermitted(direction, "direction"); this.direction = direction; fireChangeEvent(); - } /** @@ -595,10 +593,9 @@ public double getInteriorGap() { } /** - * Sets the interior gap and sends a {@link PlotChangeEvent} to all - * registered listeners. This controls the space between the edges of the - * pie plot and the plot area itself (the region where the section labels - * appear). + * Sets the interior gap and calls {@link #fireChangeEvent()}. + * This controls the space between the edges of the pie plot and the plot + * area itself (the region where the section labels appear). * * @param percent the gap (as a percentage of the available drawing space). * @@ -615,7 +612,6 @@ public void setInteriorGap(double percent) { this.interiorGap = percent; fireChangeEvent(); } - } /** @@ -643,8 +639,8 @@ public void setCircular(boolean flag) { } /** - * Sets the circular attribute and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the circular attribute and, if requested, calls + * {@link #fireChangeEvent()}. * * @param circular the new value of the flag. * @param notify notify listeners? @@ -672,9 +668,8 @@ public boolean getIgnoreNullValues() { /** * Sets a flag that controls whether {@code null} values are ignored, - * and sends a {@link PlotChangeEvent} to all registered listeners. At - * present, this only affects whether or not the key is presented in the - * legend. + * and calls {@link #fireChangeEvent()}. At present, this only affects + * whether or not the key is presented in the legend. * * @param flag the flag. * @@ -700,9 +695,8 @@ public boolean getIgnoreZeroValues() { /** * Sets a flag that controls whether zero values are ignored, - * and sends a {@link PlotChangeEvent} to all registered listeners. This - * only affects whether or not a label appears for the non-visible - * pie section. + * and calls {@link #fireChangeEvent()}. This only affects whether or not + * a label appears for the non-visible pie section. * * @param flag the flag. * @@ -813,8 +807,8 @@ public Paint getSectionPaint(Comparable key) { } /** - * Sets the paint associated with the specified key, and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the paint associated with the specified key, and calls + * {@link #fireChangeEvent()}. * * @param key the key ({@code null} not permitted). * @param paint the paint. @@ -831,8 +825,8 @@ public void setSectionPaint(Comparable key, Paint paint) { } /** - * Clears the section paint settings for this plot and, if requested, sends - * a {@link PlotChangeEvent} to all registered listeners. Be aware that + * Clears the section paint settings for this plot and, if requested, + * calls {@link #fireChangeEvent()}. Be aware that * if the {@code autoPopulateSectionPaint} flag is set, the section * paints may be repopulated using the same colours as before. * @@ -860,8 +854,7 @@ public Paint getDefaultSectionPaint() { } /** - * Sets the default section paint and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the default section paint and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -886,7 +879,7 @@ public boolean getAutoPopulateSectionPaint() { /** * Sets the flag that controls whether or not the section paint is * auto-populated by the {@link #lookupSectionPaint(Comparable)} method, - * and sends a {@link PlotChangeEvent} to all registered listeners. + * and calls {@link #fireChangeEvent()}. * * @param auto auto-populate? */ @@ -912,8 +905,7 @@ public boolean getSectionOutlinesVisible() { /** * Sets the flag that controls whether or not the outline is drawn for - * each pie section, and sends a {@link PlotChangeEvent} to all registered - * listeners. + * each pie section, and calls {@link #fireChangeEvent()}. * * @param visible the flag. * @@ -1005,8 +997,8 @@ public Paint getSectionOutlinePaint(Comparable key) { } /** - * Sets the outline paint associated with the specified key, and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the outline paint associated with the specified key, and calls + * {@link #fireChangeEvent()}. * * @param key the key ({@code null} not permitted). * @param paint the paint. @@ -1024,7 +1016,7 @@ public void setSectionOutlinePaint(Comparable key, Paint paint) { /** * Clears the section outline paint settings for this plot and, if - * requested, sends a {@link PlotChangeEvent} to all registered listeners. + * requested, calls {@link #fireChangeEvent()}. * Be aware that if the {@code autoPopulateSectionPaint} flag is set, * the section paints may be repopulated using the same colours as before. * @@ -1078,7 +1070,7 @@ public boolean getAutoPopulateSectionOutlinePaint() { /** * Sets the flag that controls whether or not the section outline paint is * auto-populated by the {@link #lookupSectionOutlinePaint(Comparable)} - * method, and sends a {@link PlotChangeEvent} to all registered listeners. + * method, and calls {@link #fireChangeEvent()}. * * @param auto auto-populate? */ @@ -1170,8 +1162,8 @@ public Stroke getSectionOutlineStroke(Comparable key) { } /** - * Sets the outline stroke associated with the specified key, and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the outline stroke associated with the specified key, and calls + * {@link #fireChangeEvent()}. * * @param key the key ({@code null} not permitted). * @param stroke the stroke. @@ -1189,7 +1181,7 @@ public void setSectionOutlineStroke(Comparable key, Stroke stroke) { /** * Clears the section outline stroke settings for this plot and, if - * requested, sends a {@link PlotChangeEvent} to all registered listeners. + * requested, calls {@link #fireChangeEvent()}. * Be aware that if the {@code autoPopulateSectionPaint} flag is set, * the section paints may be repopulated using the same colours as before. * @@ -1243,7 +1235,7 @@ public boolean getAutoPopulateSectionOutlineStroke() { /** * Sets the flag that controls whether or not the section outline stroke is * auto-populated by the {@link #lookupSectionOutlineStroke(Comparable)} - * method, and sends a {@link PlotChangeEvent} to all registered listeners. + * method, and calls {@link #fireChangeEvent()}. * * @param auto auto-populate? */ @@ -1264,8 +1256,7 @@ public Paint getShadowPaint() { } /** - * Sets the shadow paint and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the shadow paint and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} permitted). * @@ -1288,8 +1279,7 @@ public double getShadowXOffset() { } /** - * Sets the x-offset for the shadow effect and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the x-offset for the shadow effect and calls {@link #fireChangeEvent()}. * * @param offset the offset (in Java2D units). * @@ -1312,8 +1302,7 @@ public double getShadowYOffset() { } /** - * Sets the y-offset for the shadow effect and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the y-offset for the shadow effect and calls {@link #fireChangeEvent()}. * * @param offset the offset (in Java2D units). * @@ -1350,8 +1339,8 @@ public double getExplodePercent(K key) { } /** - * Sets the amount that a pie section should be exploded and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the amount that a pie section should be exploded and calls + * {@link #fireChangeEvent()}. * * @param key the section key ({@code null} not permitted). * @param percent the explode percentage (0.30 = 30 percent). @@ -1398,8 +1387,7 @@ public PieSectionLabelGenerator getLabelGenerator() { } /** - * Sets the section label generator and sends a {@link PlotChangeEvent} to - * all registered listeners. + * Sets the section label generator and calls {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} permitted). * @@ -1424,8 +1412,7 @@ public double getLabelGap() { /** * Sets the gap between the edge of the pie and the labels (expressed as a - * percentage of the plot width) and sends a {@link PlotChangeEvent} to all - * registered listeners. + * percentage of the plot width) and calls {@link #fireChangeEvent()}. * * @param gap the gap (a percentage, where 0.05 = five percent). * @@ -1448,8 +1435,8 @@ public double getMaximumLabelWidth() { } /** - * Sets the maximum label width as a percentage of the plot width and sends - * a {@link PlotChangeEvent} to all registered listeners. + * Sets the maximum label width as a percentage of the plot width and calls + * {@link #fireChangeEvent()}. * * @param width the width (a percentage, where 0.20 = 20 percent). * @@ -1474,7 +1461,7 @@ public boolean getLabelLinksVisible() { /** * Sets the flag that controls whether or not label linking lines are - * visible and sends a {@link PlotChangeEvent} to all registered listeners. + * visible and calls {@link #fireChangeEvent()}. * Please take care when hiding the linking lines - depending on the data * values, the labels can be displayed some distance away from the * corresponding pie section. @@ -1500,8 +1487,7 @@ public PieLabelLinkStyle getLabelLinkStyle() { } /** - * Sets the label link style and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the label link style and calls {@link #fireChangeEvent()}. * * @param style the new style ({@code null} not permitted). * @@ -1526,8 +1512,7 @@ public double getLabelLinkMargin() { } /** - * Sets the link margin and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the link margin and calls {@link #fireChangeEvent()}. * * @param margin the margin. * @@ -1552,8 +1537,7 @@ public Paint getLabelLinkPaint() { /** * Sets the paint used for the lines that connect pie sections to their - * corresponding labels, and sends a {@link PlotChangeEvent} to all - * registered listeners. + * corresponding labels, and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -1577,8 +1561,7 @@ public Stroke getLabelLinkStroke() { } /** - * Sets the link stroke and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the link stroke and calls {@link #fireChangeEvent()}. * * @param stroke the stroke. * @@ -1615,8 +1598,7 @@ public Font getLabelFont() { } /** - * Sets the section label font and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the section label font and calls {@link #fireChangeEvent()}. * * @param font the font ({@code null} not permitted). * @@ -1640,8 +1622,7 @@ public Paint getLabelPaint() { } /** - * Sets the section label paint and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the section label paint and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -1665,8 +1646,8 @@ public Paint getLabelBackgroundPaint() { } /** - * Sets the section label background paint and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the section label background paint and calls + * {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} permitted). * @@ -1689,8 +1670,7 @@ public Paint getLabelOutlinePaint() { } /** - * Sets the section label outline paint and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the section label outline paint and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} permitted). * @@ -1713,8 +1693,7 @@ public Stroke getLabelOutlineStroke() { } /** - * Sets the section label outline stroke and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the section label outline stroke and calls {@link #fireChangeEvent()}. * * @param stroke the stroke ({@code null} permitted). * @@ -1737,8 +1716,7 @@ public Paint getLabelShadowPaint() { } /** - * Sets the section label shadow paint and sends a {@link PlotChangeEvent} - * to all registered listeners. + * Sets the section label shadow paint and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} permitted). * @@ -1761,8 +1739,8 @@ public RectangleInsets getLabelPadding() { } /** - * Sets the padding between each label and its outline and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the padding between each label and its outline and + * calls {@link #fireChangeEvent()}. * * @param padding the padding ({@code null} not permitted). * @@ -1786,8 +1764,7 @@ public boolean getSimpleLabels() { /** * Sets the flag that controls whether simple or extended labels are - * displayed on the plot, and sends a {@link PlotChangeEvent} to all - * registered listeners. + * displayed on the plot, and calls {@link #fireChangeEvent()}. * * @param simple the new flag value. */ @@ -1808,8 +1785,7 @@ public RectangleInsets getSimpleLabelOffset() { } /** - * Sets the offset for the simple labels and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the offset for the simple labels and calls {@link #fireChangeEvent()}. * * @param offset the offset ({@code null} not permitted). * @@ -1832,8 +1808,7 @@ public AbstractPieLabelDistributor getLabelDistributor() { } /** - * Sets the label distributor and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the label distributor and calls {@link #fireChangeEvent()}. * * @param distributor the distributor ({@code null} not permitted). */ @@ -1857,9 +1832,8 @@ public PieToolTipGenerator getToolTipGenerator() { } /** - * Sets the tool tip generator and sends a {@link PlotChangeEvent} to all - * registered listeners. Set the generator to {@code null} if you - * don't want any tool tips. + * Sets the tool tip generator and calls {@link #fireChangeEvent()}. Set + * the generator to {@code null} if you don't want any tool tips. * * @param generator the generator ({@code null} permitted). * @@ -1882,8 +1856,7 @@ public PieURLGenerator getURLGenerator() { } /** - * Sets the URL generator and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the URL generator and calls {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} permitted). * @@ -1940,8 +1913,7 @@ public Shape getLegendItemShape() { } /** - * Sets the shape used for legend items and sends a {@link PlotChangeEvent} - * to all registered listeners. + * Sets the shape used for legend items and calls {@link #fireChangeEvent()}. * * @param shape the shape ({@code null} not permitted). * @@ -1965,8 +1937,7 @@ public PieSectionLabelGenerator getLegendLabelGenerator() { } /** - * Sets the legend label generator and sends a {@link PlotChangeEvent} to - * all registered listeners. + * Sets the legend label generator and calls {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} not permitted). * @@ -1990,8 +1961,7 @@ public PieSectionLabelGenerator getLegendLabelToolTipGenerator() { } /** - * Sets the legend label tool tip generator and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the legend label tool tip generator and calls {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} permitted). * @@ -2015,8 +1985,7 @@ public PieURLGenerator getLegendLabelURLGenerator() { } /** - * Sets the legend label URL generator and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the legend label URL generator and calls {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} permitted). * @@ -2037,8 +2006,8 @@ public ShadowGenerator getShadowGenerator() { } /** - * Sets the shadow generator for the plot and sends a - * {@link PlotChangeEvent} to all registered listeners. Note that this is + * Sets the shadow generator for the plot and calls + * {@link #fireChangeEvent()}. Note that this is * a bitmap drop-shadow generation facility and is separate from the * vector based show option that is controlled via the * {@link #setShadowPaint(java.awt.Paint)} method. diff --git a/src/main/java/org/jfree/chart/plot/Plot.java b/src/main/java/org/jfree/chart/plot/Plot.java index 13b6dae08..00d31e32c 100644 --- a/src/main/java/org/jfree/chart/plot/Plot.java +++ b/src/main/java/org/jfree/chart/plot/Plot.java @@ -1,1518 +1,1516 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------- - * Plot.java - * --------- - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): Sylvain Vieujot; - * Jeremy Bowman; - * Andreas Schneider; - * Gideon Krause; - * Nicolas Brodu; - * Michal Krause; - * Richard West, Advanced Micro Devices, Inc.; - * Peter Kolb - patches 2603321, 2809117; - * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); - * - */ - -package org.jfree.chart.plot; - -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Font; -import java.awt.GradientPaint; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.Paint; -import java.awt.RenderingHints; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Ellipse2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Objects; - -import javax.swing.event.EventListenerList; - -import org.jfree.chart.JFreeChart; -import org.jfree.chart.LegendItemCollection; -import org.jfree.chart.LegendItemSource; -import org.jfree.chart.annotations.Annotation; -import org.jfree.chart.axis.AxisLocation; -import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.entity.PlotEntity; -import org.jfree.chart.event.AnnotationChangeEvent; -import org.jfree.chart.event.AnnotationChangeListener; -import org.jfree.chart.event.AxisChangeEvent; -import org.jfree.chart.event.AxisChangeListener; -import org.jfree.chart.event.ChartChangeEventType; -import org.jfree.chart.event.MarkerChangeEvent; -import org.jfree.chart.event.MarkerChangeListener; -import org.jfree.chart.event.PlotChangeEvent; -import org.jfree.chart.event.PlotChangeListener; -import org.jfree.chart.text.G2TextMeasurer; -import org.jfree.chart.text.TextBlock; -import org.jfree.chart.text.TextBlockAnchor; -import org.jfree.chart.text.TextUtils; -import org.jfree.chart.ui.Align; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.RectangleInsets; -import org.jfree.chart.util.ObjectUtils; -import org.jfree.chart.util.PaintUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.SerialUtils; -import org.jfree.data.general.DatasetChangeEvent; -import org.jfree.data.general.DatasetChangeListener; -import org.jfree.data.general.DatasetGroup; - -/** - * The base class for all plots in JFreeChart. The {@link JFreeChart} class - * delegates the drawing of axes and data to the plot. This base class - * provides facilities common to most plot types. - */ -public abstract class Plot implements AxisChangeListener, - DatasetChangeListener, AnnotationChangeListener, MarkerChangeListener, - LegendItemSource, PublicCloneable, Cloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = -8831571430103671324L; - - /** Useful constant representing zero. */ - public static final Number ZERO = 0; - - /** The default insets. */ - public static final RectangleInsets DEFAULT_INSETS - = new RectangleInsets(4.0, 8.0, 4.0, 8.0); - - /** The default outline stroke. */ - public static final Stroke DEFAULT_OUTLINE_STROKE = new BasicStroke(0.5f, - BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); - - /** The default outline color. */ - public static final Paint DEFAULT_OUTLINE_PAINT = Color.GRAY; - - /** The default foreground alpha transparency. */ - public static final float DEFAULT_FOREGROUND_ALPHA = 1.0f; - - /** The default background alpha transparency. */ - public static final float DEFAULT_BACKGROUND_ALPHA = 1.0f; - - /** The default background color. */ - public static final Paint DEFAULT_BACKGROUND_PAINT = Color.WHITE; - - /** The minimum width at which the plot should be drawn. */ - public static final int MINIMUM_WIDTH_TO_DRAW = 10; - - /** The minimum height at which the plot should be drawn. */ - public static final int MINIMUM_HEIGHT_TO_DRAW = 10; - - /** A default box shape for legend items. */ - public static final Shape DEFAULT_LEGEND_ITEM_BOX - = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0); - - /** A default circle shape for legend items. */ - public static final Shape DEFAULT_LEGEND_ITEM_CIRCLE - = new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0); - - /** - * The chart that the plot is assigned to. It can be {@code null} if the - * plot is not assigned to a chart yet, or if the plot is a subplot of a - * another plot. - */ - private JFreeChart chart; - - /** The parent plot ({@code null} if this is the root plot). */ - private Plot parent; - - /** The dataset group (to be used for thread synchronisation). */ - private DatasetGroup datasetGroup; - - /** The message to display if no data is available. */ - private String noDataMessage; - - /** The font used to display the 'no data' message. */ - private Font noDataMessageFont; - - /** The paint used to draw the 'no data' message. */ - private transient Paint noDataMessagePaint; - - /** Amount of blank space around the plot area. */ - private RectangleInsets insets; - - /** - * A flag that controls whether or not the plot outline is drawn. - */ - private boolean outlineVisible; - - /** The Stroke used to draw an outline around the plot. */ - private transient Stroke outlineStroke; - - /** The Paint used to draw an outline around the plot. */ - private transient Paint outlinePaint; - - /** An optional color used to fill the plot background. */ - private transient Paint backgroundPaint; - - /** An optional image for the plot background. */ - private transient Image backgroundImage; // not currently serialized - - /** The alignment for the background image. */ - private int backgroundImageAlignment = Align.FIT; - - /** The alpha value used to draw the background image. */ - private float backgroundImageAlpha = 0.5f; - - /** The alpha-transparency for the plot. */ - private float foregroundAlpha; - - /** The alpha transparency for the background paint. */ - private float backgroundAlpha; - - /** The drawing supplier. */ - private DrawingSupplier drawingSupplier; - - /** Storage for registered change listeners. */ - private transient EventListenerList listenerList; - - /** - * A flag that controls whether or not the plot will notify listeners - * of changes (defaults to true, but sometimes it is useful to disable - * this). - */ - private boolean notify; - - /** - * Creates a new plot. - */ - protected Plot() { - this.chart = null; - this.parent = null; - this.insets = DEFAULT_INSETS; - this.backgroundPaint = DEFAULT_BACKGROUND_PAINT; - this.backgroundAlpha = DEFAULT_BACKGROUND_ALPHA; - this.backgroundImage = null; - this.outlineVisible = true; - this.outlineStroke = DEFAULT_OUTLINE_STROKE; - this.outlinePaint = DEFAULT_OUTLINE_PAINT; - this.foregroundAlpha = DEFAULT_FOREGROUND_ALPHA; - - this.noDataMessage = null; - this.noDataMessageFont = new Font("SansSerif", Font.PLAIN, 12); - this.noDataMessagePaint = Color.BLACK; - - this.drawingSupplier = new DefaultDrawingSupplier(); - - this.notify = true; - this.listenerList = new EventListenerList(); - } - - /** - * Returns the chart that this plot is assigned to. This method can - * return {@code null} if the plot is not yet assigned to a plot, or if the - * plot is a subplot of another plot. - * - * @return The chart (possibly {@code null}). - */ - public JFreeChart getChart() { - return this.chart; - } - - /** - * Sets the chart that the plot is assigned to. This method is not - * intended for external use. - * - * @param chart the chart ({@code null} permitted). - */ - public void setChart(JFreeChart chart) { - this.chart = chart; - } - - /** - * Fetches the element hinting flag from the chart that this plot is - * assigned to. If the plot is not assigned (directly or indirectly) to - * a chart instance, this method will return {@code false}. - * - * @return A boolean. - */ - public boolean fetchElementHintingFlag() { - if (this.parent != null) { - return this.parent.fetchElementHintingFlag(); - } - if (this.chart != null) { - return this.chart.getElementHinting(); - } - return false; - } - - /** - * Returns the dataset group for the plot (not currently used). - * - * @return The dataset group. - * - * @see #setDatasetGroup(DatasetGroup) - */ - public DatasetGroup getDatasetGroup() { - return this.datasetGroup; - } - - /** - * Sets the dataset group (not currently used). - * - * @param group the dataset group ({@code null} permitted). - * - * @see #getDatasetGroup() - */ - protected void setDatasetGroup(DatasetGroup group) { - this.datasetGroup = group; - } - - /** - * Returns the string that is displayed when the dataset is empty or - * {@code null}. - * - * @return The 'no data' message ({@code null} possible). - * - * @see #setNoDataMessage(String) - * @see #getNoDataMessageFont() - * @see #getNoDataMessagePaint() - */ - public String getNoDataMessage() { - return this.noDataMessage; - } - - /** - * Sets the message that is displayed when the dataset is empty or - * {@code null}, and sends a {@link PlotChangeEvent} to all registered - * listeners. - * - * @param message the message ({@code null} permitted). - * - * @see #getNoDataMessage() - */ - public void setNoDataMessage(String message) { - this.noDataMessage = message; - fireChangeEvent(); - } - - /** - * Returns the font used to display the 'no data' message. - * - * @return The font (never {@code null}). - * - * @see #setNoDataMessageFont(Font) - * @see #getNoDataMessage() - */ - public Font getNoDataMessageFont() { - return this.noDataMessageFont; - } - - /** - * Sets the font used to display the 'no data' message and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param font the font ({@code null} not permitted). - * - * @see #getNoDataMessageFont() - */ - public void setNoDataMessageFont(Font font) { - Args.nullNotPermitted(font, "font"); - this.noDataMessageFont = font; - fireChangeEvent(); - } - - /** - * Returns the paint used to display the 'no data' message. - * - * @return The paint (never {@code null}). - * - * @see #setNoDataMessagePaint(Paint) - * @see #getNoDataMessage() - */ - public Paint getNoDataMessagePaint() { - return this.noDataMessagePaint; - } - - /** - * Sets the paint used to display the 'no data' message and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getNoDataMessagePaint() - */ - public void setNoDataMessagePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.noDataMessagePaint = paint; - fireChangeEvent(); - } - - /** - * Returns a short string describing the plot type. - *

- * Note: this gets used in the chart property editing user interface, - * but there needs to be a better mechanism for identifying the plot type. - * - * @return A short string describing the plot type (never - * {@code null}). - */ - public abstract String getPlotType(); - - /** - * Returns the parent plot (or {@code null} if this plot is not part - * of a combined plot). - * - * @return The parent plot. - * - * @see #setParent(Plot) - * @see #getRootPlot() - */ - public Plot getParent() { - return this.parent; - } - - /** - * Sets the parent plot. This method is intended for internal use, you - * shouldn't need to call it directly. - * - * @param parent the parent plot ({@code null} permitted). - * - * @see #getParent() - */ - public void setParent(Plot parent) { - this.parent = parent; - } - - /** - * Returns the root plot. - * - * @return The root plot. - * - * @see #getParent() - */ - public Plot getRootPlot() { - - Plot p = getParent(); - if (p == null) { - return this; - } - return p.getRootPlot(); - - } - - /** - * Returns {@code true} if this plot is part of a combined plot - * structure (that is, {@link #getParent()} returns a non-{@code null} - * value), and {@code false} otherwise. - * - * @return {@code true} if this plot is part of a combined plot - * structure. - * - * @see #getParent() - */ - public boolean isSubplot() { - return (getParent() != null); - } - - /** - * Returns the insets for the plot area. - * - * @return The insets (never {@code null}). - * - * @see #setInsets(RectangleInsets) - */ - public RectangleInsets getInsets() { - return this.insets; - } - - /** - * Sets the insets for the plot and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param insets the new insets ({@code null} not permitted). - * - * @see #getInsets() - * @see #setInsets(RectangleInsets, boolean) - */ - public void setInsets(RectangleInsets insets) { - setInsets(insets, true); - } - - /** - * Sets the insets for the plot and, if requested, and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param insets the new insets ({@code null} not permitted). - * @param notify a flag that controls whether the registered listeners are - * notified. - * - * @see #getInsets() - * @see #setInsets(RectangleInsets) - */ - public void setInsets(RectangleInsets insets, boolean notify) { - Args.nullNotPermitted(insets, "insets"); - if (!this.insets.equals(insets)) { - this.insets = insets; - if (notify) { - fireChangeEvent(); - } - } - - } - - /** - * Returns the background color of the plot area. - * - * @return The paint (possibly {@code null}). - * - * @see #setBackgroundPaint(Paint) - */ - public Paint getBackgroundPaint() { - return this.backgroundPaint; - } - - /** - * Sets the background color of the plot area and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} permitted). - * - * @see #getBackgroundPaint() - */ - public void setBackgroundPaint(Paint paint) { - - if (paint == null) { - if (this.backgroundPaint != null) { - this.backgroundPaint = null; - fireChangeEvent(); - } - } - else { - if (this.backgroundPaint != null) { - if (this.backgroundPaint.equals(paint)) { - return; // nothing to do - } - } - this.backgroundPaint = paint; - fireChangeEvent(); - } - - } - - /** - * Returns the alpha transparency of the plot area background. - * - * @return The alpha transparency. - * - * @see #setBackgroundAlpha(float) - */ - public float getBackgroundAlpha() { - return this.backgroundAlpha; - } - - /** - * Sets the alpha transparency of the plot area background, and notifies - * registered listeners that the plot has been modified. - * - * @param alpha the new alpha value (in the range 0.0f to 1.0f). - * - * @see #getBackgroundAlpha() - */ - public void setBackgroundAlpha(float alpha) { - if (this.backgroundAlpha != alpha) { - this.backgroundAlpha = alpha; - fireChangeEvent(); - } - } - - /** - * Returns the drawing supplier for the plot. - * - * @return The drawing supplier (possibly {@code null}). - * - * @see #setDrawingSupplier(DrawingSupplier) - */ - public DrawingSupplier getDrawingSupplier() { - DrawingSupplier result; - Plot p = getParent(); - if (p != null) { - result = p.getDrawingSupplier(); - } - else { - result = this.drawingSupplier; - } - return result; - } - - /** - * Sets the drawing supplier for the plot and sends a - * {@link PlotChangeEvent} to all registered listeners. The drawing - * supplier is responsible for supplying a limitless (possibly repeating) - * sequence of {@code Paint}, {@code Stroke} and - * {@code Shape} objects that the plot's renderer(s) can use to - * populate its (their) tables. - * - * @param supplier the new supplier. - * - * @see #getDrawingSupplier() - */ - public void setDrawingSupplier(DrawingSupplier supplier) { - this.drawingSupplier = supplier; - fireChangeEvent(); - } - - /** - * Sets the drawing supplier for the plot and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. The drawing - * supplier is responsible for supplying a limitless (possibly repeating) - * sequence of {@code Paint}, {@code Stroke} and - * {@code Shape} objects that the plot's renderer(s) can use to - * populate its (their) tables. - * - * @param supplier the new supplier. - * @param notify notify listeners? - * - * @see #getDrawingSupplier() - */ - public void setDrawingSupplier(DrawingSupplier supplier, boolean notify) { - this.drawingSupplier = supplier; - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the background image that is used to fill the plot's background - * area. - * - * @return The image (possibly {@code null}). - * - * @see #setBackgroundImage(Image) - */ - public Image getBackgroundImage() { - return this.backgroundImage; - } - - /** - * Sets the background image for the plot and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param image the image ({@code null} permitted). - * - * @see #getBackgroundImage() - */ - public void setBackgroundImage(Image image) { - this.backgroundImage = image; - fireChangeEvent(); - } - - /** - * Returns the background image alignment. Alignment constants are defined - * in the {@code Align} class. - * - * @return The alignment. - * - * @see #setBackgroundImageAlignment(int) - */ - public int getBackgroundImageAlignment() { - return this.backgroundImageAlignment; - } - - /** - * Sets the alignment for the background image and sends a - * {@link PlotChangeEvent} to all registered listeners. Alignment options - * are defined by the {@link org.jfree.chart.ui.Align} class. - * - * @param alignment the alignment. - * - * @see #getBackgroundImageAlignment() - */ - public void setBackgroundImageAlignment(int alignment) { - if (this.backgroundImageAlignment != alignment) { - this.backgroundImageAlignment = alignment; - fireChangeEvent(); - } - } - - /** - * Returns the alpha transparency used to draw the background image. This - * is a value in the range 0.0f to 1.0f, where 0.0f is fully transparent - * and 1.0f is fully opaque. - * - * @return The alpha transparency. - * - * @see #setBackgroundImageAlpha(float) - */ - public float getBackgroundImageAlpha() { - return this.backgroundImageAlpha; - } - - /** - * Sets the alpha transparency used when drawing the background image. - * - * @param alpha the alpha transparency (in the range 0.0f to 1.0f, where - * 0.0f is fully transparent, and 1.0f is fully opaque). - * - * @throws IllegalArgumentException if {@code alpha} is not within - * the specified range. - * - * @see #getBackgroundImageAlpha() - */ - public void setBackgroundImageAlpha(float alpha) { - if (alpha < 0.0f || alpha > 1.0f) { - throw new IllegalArgumentException( - "The 'alpha' value must be in the range 0.0f to 1.0f."); - } - if (this.backgroundImageAlpha != alpha) { - this.backgroundImageAlpha = alpha; - fireChangeEvent(); - } - } - - /** - * Returns the flag that controls whether or not the plot outline is - * drawn. The default value is {@code true}. Note that for - * historical reasons, the plot's outline paint and stroke can take on - * {@code null} values, in which case the outline will not be drawn - * even if this flag is set to {@code true}. - * - * @return The outline visibility flag. - * - * @see #setOutlineVisible(boolean) - */ - public boolean isOutlineVisible() { - return this.outlineVisible; - } - - /** - * Sets the flag that controls whether or not the plot's outline is - * drawn, and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param visible the new flag value. - * - * @see #isOutlineVisible() - */ - public void setOutlineVisible(boolean visible) { - this.outlineVisible = visible; - fireChangeEvent(); - } - - /** - * Returns the stroke used to outline the plot area. - * - * @return The stroke (possibly {@code null}). - * - * @see #setOutlineStroke(Stroke) - */ - public Stroke getOutlineStroke() { - return this.outlineStroke; - } - - /** - * Sets the stroke used to outline the plot area and sends a - * {@link PlotChangeEvent} to all registered listeners. If you set this - * attribute to {@code null}, no outline will be drawn. - * - * @param stroke the stroke ({@code null} permitted). - * - * @see #getOutlineStroke() - */ - public void setOutlineStroke(Stroke stroke) { - if (stroke == null) { - if (this.outlineStroke != null) { - this.outlineStroke = null; - fireChangeEvent(); - } - } - else { - if (this.outlineStroke != null) { - if (this.outlineStroke.equals(stroke)) { - return; // nothing to do - } - } - this.outlineStroke = stroke; - fireChangeEvent(); - } - } - - /** - * Returns the color used to draw the outline of the plot area. - * - * @return The color (possibly {@code null}). - * - * @see #setOutlinePaint(Paint) - */ - public Paint getOutlinePaint() { - return this.outlinePaint; - } - - /** - * Sets the paint used to draw the outline of the plot area and sends a - * {@link PlotChangeEvent} to all registered listeners. If you set this - * attribute to {@code null}, no outline will be drawn. - * - * @param paint the paint ({@code null} permitted). - * - * @see #getOutlinePaint() - */ - public void setOutlinePaint(Paint paint) { - if (paint == null) { - if (this.outlinePaint != null) { - this.outlinePaint = null; - fireChangeEvent(); - } - } - else { - if (this.outlinePaint != null) { - if (this.outlinePaint.equals(paint)) { - return; // nothing to do - } - } - this.outlinePaint = paint; - fireChangeEvent(); - } - } - - /** - * Returns the alpha-transparency for the plot foreground. - * - * @return The alpha-transparency. - * - * @see #setForegroundAlpha(float) - */ - public float getForegroundAlpha() { - return this.foregroundAlpha; - } - - /** - * Sets the alpha-transparency for the plot and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param alpha the new alpha transparency. - * - * @see #getForegroundAlpha() - */ - public void setForegroundAlpha(float alpha) { - if (this.foregroundAlpha != alpha) { - this.foregroundAlpha = alpha; - fireChangeEvent(); - } - } - - /** - * Returns the legend items for the plot. By default, this method returns - * {@code null}. Subclasses should override to return a - * {@link LegendItemCollection}. - * - * @return The legend items for the plot (possibly {@code null}). - */ - @Override - public LegendItemCollection getLegendItems() { - return null; - } - - /** - * Returns a flag that controls whether or not change events are sent to - * registered listeners. - * - * @return A boolean. - * - * @see #setNotify(boolean) - */ - public boolean isNotify() { - return this.notify; - } - - /** - * Sets a flag that controls whether or not listeners receive - * {@link PlotChangeEvent} notifications. - * - * @param notify a boolean. - * - * @see #isNotify() - */ - public void setNotify(boolean notify) { - this.notify = notify; - // if the flag is being set to true, there may be queued up changes... - if (notify) { - notifyListeners(new PlotChangeEvent(this)); - } - } - - /** - * Registers an object for notification of changes to the plot. - * - * @param listener the object to be registered. - * - * @see #removeChangeListener(PlotChangeListener) - */ - public void addChangeListener(PlotChangeListener listener) { - this.listenerList.add(PlotChangeListener.class, listener); - } - - /** - * Unregisters an object for notification of changes to the plot. - * - * @param listener the object to be unregistered. - * - * @see #addChangeListener(PlotChangeListener) - */ - public void removeChangeListener(PlotChangeListener listener) { - this.listenerList.remove(PlotChangeListener.class, listener); - } - - /** - * Notifies all registered listeners that the plot has been modified. - * - * @param event information about the change event. - */ - public void notifyListeners(PlotChangeEvent event) { - // if the 'notify' flag has been switched to false, we don't notify - // the listeners - if (!this.notify) { - return; - } - Object[] listeners = this.listenerList.getListenerList(); - for (int i = listeners.length - 2; i >= 0; i -= 2) { - if (listeners[i] == PlotChangeListener.class) { - ((PlotChangeListener) listeners[i + 1]).plotChanged(event); - } - } - } - - /** - * Sends a {@link PlotChangeEvent} to all registered listeners. - */ - protected void fireChangeEvent() { - notifyListeners(new PlotChangeEvent(this)); - } - - /** - * Draws the plot within the specified area. The anchor is a point on the - * chart that is specified externally (for instance, it may be the last - * point of the last mouse click performed by the user) - plots can use or - * ignore this value as they see fit. - *

- * Subclasses need to provide an implementation of this method, obviously. - * - * @param g2 the graphics device. - * @param area the plot area. - * @param anchor the anchor point ({@code null} permitted). - * @param parentState the parent state (if any, {@code null} permitted). - * @param info carries back plot rendering info. - */ - public abstract void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, - PlotState parentState, PlotRenderingInfo info); - - /** - * Draws the plot background (the background color and/or image). - *

- * This method will be called during the chart drawing process and is - * declared public so that it can be accessed by the renderers used by - * certain subclasses. You shouldn't need to call this method directly. - * - * @param g2 the graphics device. - * @param area the area within which the plot should be drawn. - */ - public void drawBackground(Graphics2D g2, Rectangle2D area) { - // some subclasses override this method completely, so don't put - // anything here that *must* be done - fillBackground(g2, area); - drawBackgroundImage(g2, area); - } - - /** - * Fills the specified area with the background paint. - * - * @param g2 the graphics device. - * @param area the area. - * - * @see #getBackgroundPaint() - * @see #getBackgroundAlpha() - * @see #fillBackground(Graphics2D, Rectangle2D, PlotOrientation) - */ - protected void fillBackground(Graphics2D g2, Rectangle2D area) { - fillBackground(g2, area, PlotOrientation.VERTICAL); - } - - /** - * Fills the specified area with the background paint. If the background - * paint is an instance of {@code GradientPaint}, the gradient will - * run in the direction suggested by the plot's orientation. - * - * @param g2 the graphics target. - * @param area the plot area. - * @param orientation the plot orientation ({@code null} not - * permitted). - */ - protected void fillBackground(Graphics2D g2, Rectangle2D area, - PlotOrientation orientation) { - Args.nullNotPermitted(orientation, "orientation"); - if (this.backgroundPaint == null) { - return; - } - Paint p = this.backgroundPaint; - if (p instanceof GradientPaint) { - GradientPaint gp = (GradientPaint) p; - if (orientation == PlotOrientation.VERTICAL) { - p = new GradientPaint((float) area.getCenterX(), - (float) area.getMaxY(), gp.getColor1(), - (float) area.getCenterX(), (float) area.getMinY(), - gp.getColor2()); - } - else if (orientation == PlotOrientation.HORIZONTAL) { - p = new GradientPaint((float) area.getMinX(), - (float) area.getCenterY(), gp.getColor1(), - (float) area.getMaxX(), (float) area.getCenterY(), - gp.getColor2()); - } - } - Composite originalComposite = g2.getComposite(); - g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - this.backgroundAlpha)); - g2.setPaint(p); - g2.fill(area); - g2.setComposite(originalComposite); - } - - /** - * Draws the background image (if there is one) aligned within the - * specified area. - * - * @param g2 the graphics device. - * @param area the area. - * - * @see #getBackgroundImage() - * @see #getBackgroundImageAlignment() - * @see #getBackgroundImageAlpha() - */ - public void drawBackgroundImage(Graphics2D g2, Rectangle2D area) { - if (this.backgroundImage == null) { - return; // nothing to do - } - Composite savedComposite = g2.getComposite(); - g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - this.backgroundImageAlpha)); - Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0, - this.backgroundImage.getWidth(null), - this.backgroundImage.getHeight(null)); - Align.align(dest, area, this.backgroundImageAlignment); - Shape savedClip = g2.getClip(); - g2.clip(area); - g2.drawImage(this.backgroundImage, (int) dest.getX(), - (int) dest.getY(), (int) dest.getWidth() + 1, - (int) dest.getHeight() + 1, null); - g2.setClip(savedClip); - g2.setComposite(savedComposite); - } - - /** - * Draws the plot outline. This method will be called during the chart - * drawing process and is declared public so that it can be accessed by the - * renderers used by certain subclasses. You shouldn't need to call this - * method directly. - * - * @param g2 the graphics device. - * @param area the area within which the plot should be drawn. - */ - public void drawOutline(Graphics2D g2, Rectangle2D area) { - if (!this.outlineVisible) { - return; - } - if ((this.outlineStroke != null) && (this.outlinePaint != null)) { - g2.setStroke(this.outlineStroke); - g2.setPaint(this.outlinePaint); - Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); - g2.draw(area); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); - } - } - - /** - * Draws a message to state that there is no data to plot. - * - * @param g2 the graphics device. - * @param area the area within which the plot should be drawn. - */ - protected void drawNoDataMessage(Graphics2D g2, Rectangle2D area) { - Shape savedClip = g2.getClip(); - g2.clip(area); - String message = this.noDataMessage; - if (message != null) { - g2.setFont(this.noDataMessageFont); - g2.setPaint(this.noDataMessagePaint); - TextBlock block = TextUtils.createTextBlock( - this.noDataMessage, this.noDataMessageFont, - this.noDataMessagePaint, 0.9f * (float) area.getWidth(), - new G2TextMeasurer(g2)); - block.draw(g2, (float) area.getCenterX(), - (float) area.getCenterY(), TextBlockAnchor.CENTER); - } - g2.setClip(savedClip); - } - - /** - * Creates a plot entity that contains a reference to the plot and the - * data area as shape. - * - * @param dataArea the data area used as hot spot for the entity. - * @param plotState the plot rendering info containing a reference to the - * EntityCollection. - * @param toolTip the tool tip (defined in the respective Plot - * subclass) ({@code null} permitted). - * @param urlText the url (defined in the respective Plot subclass) - * ({@code null} permitted). - */ - protected void createAndAddEntity(Rectangle2D dataArea, - PlotRenderingInfo plotState, String toolTip, String urlText) { - if (plotState != null && plotState.getOwner() != null) { - EntityCollection e = plotState.getOwner().getEntityCollection(); - if (e != null) { - e.add(new PlotEntity(dataArea, this, toolTip, urlText)); - } - } - } - - /** - * Handles a 'click' on the plot. Since the plot does not maintain any - * information about where it has been drawn, the plot rendering info is - * supplied as an argument so that the plot dimensions can be determined. - * - * @param x the x coordinate (in Java2D space). - * @param y the y coordinate (in Java2D space). - * @param info an object containing information about the dimensions of - * the plot. - */ - public void handleClick(int x, int y, PlotRenderingInfo info) { - // provides a 'no action' default - } - - /** - * Performs a zoom on the plot. Subclasses should override if zooming is - * appropriate for the type of plot. - * - * @param percent the zoom percentage. - */ - public void zoom(double percent) { - // do nothing by default. - } - - /** - * Receives notification of a change to an {@link Annotation} added to - * this plot. - * - * @param event information about the event (not used here). - */ - @Override - public void annotationChanged(AnnotationChangeEvent event) { - fireChangeEvent(); - } - - /** - * Receives notification of a change to one of the plot's axes. - * - * @param event information about the event (not used here). - */ - @Override - public void axisChanged(AxisChangeEvent event) { - fireChangeEvent(); - } - - /** - * Receives notification of a change to the plot's dataset. - *

- * The plot reacts by passing on a plot change event to all registered - * listeners. - * - * @param event information about the event (not used here). - */ - @Override - public void datasetChanged(DatasetChangeEvent event) { - PlotChangeEvent newEvent = new PlotChangeEvent(this); - newEvent.setType(ChartChangeEventType.DATASET_UPDATED); - notifyListeners(newEvent); - } - - /** - * Receives notification of a change to a marker that is assigned to the - * plot. - * - * @param event the event. - */ - @Override - public void markerChanged(MarkerChangeEvent event) { - fireChangeEvent(); - } - - /** - * Adjusts the supplied x-value. - * - * @param x the x-value. - * @param w1 width 1. - * @param w2 width 2. - * @param edge the edge (left or right). - * - * @return The adjusted x-value. - */ - protected double getRectX(double x, double w1, double w2, - RectangleEdge edge) { - - double result = x; - if (edge == RectangleEdge.LEFT) { - result = result + w1; - } - else if (edge == RectangleEdge.RIGHT) { - result = result + w2; - } - return result; - - } - - /** - * Adjusts the supplied y-value. - * - * @param y the x-value. - * @param h1 height 1. - * @param h2 height 2. - * @param edge the edge (top or bottom). - * - * @return The adjusted y-value. - */ - protected double getRectY(double y, double h1, double h2, - RectangleEdge edge) { - - double result = y; - if (edge == RectangleEdge.TOP) { - result = result + h1; - } - else if (edge == RectangleEdge.BOTTOM) { - result = result + h2; - } - return result; - - } - - /** - * Tests this plot for equality with another object. - * - * @param obj the object ({@code null} permitted). - * - * @return {@code true} or {@code false}. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Plot)) { - return false; - } - Plot that = (Plot) obj; - // fix the "equals not symmetric" problem - if (!that.canEqual(this)) { - return false; - } - if (!Objects.equals(this.noDataMessage, that.noDataMessage)) { - return false; - } - if (!Objects.equals( - this.noDataMessageFont, that.noDataMessageFont - )) { - return false; - } - if (!PaintUtils.equal(this.noDataMessagePaint, - that.noDataMessagePaint)) { - return false; - } - if (!Objects.equals(this.insets, that.insets)) { - return false; - } - // There's a reason chart is not included in equals/hashCode - doing so - // causes a StackOverflow error during EqualsVerifier's test! -// if (!Objects.equals(this.chart, that.chart)) { -// return false; -// } - if (this.outlineVisible != that.outlineVisible) { - return false; - } - if (!Objects.equals(this.outlineStroke, that.outlineStroke)) { - return false; - } - if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { - return false; - } - if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) { - return false; - } - if (!Objects.equals(this.backgroundImage, that.backgroundImage)) { - return false; - } - if (this.backgroundImageAlignment != that.backgroundImageAlignment) { - return false; - } - if (Float.compare(this.backgroundImageAlpha, - that.backgroundImageAlpha) != 0 ){ - return false; - } - if (Float.compare(this.foregroundAlpha, that.foregroundAlpha) != 0 ) { - return false; - } - if (Float.compare(this.backgroundAlpha, that.backgroundAlpha) != 0 ) { - return false; - } - if (!Objects.equals(this.drawingSupplier, that.drawingSupplier)) { - return false; - } - if (this.notify != that.notify) { - return false; - } - if (!Objects.equals(this.datasetGroup, that.datasetGroup)) { - return false; - } - return true; - } - - /** - * Ensures symmetry between super/subclass implementations of equals. For - * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. - * - * @param other Object - * - * @return true ONLY if the parameter is THIS class type - */ - public boolean canEqual(Object other) { - // Solves Problem: equals not symmetric - return (other instanceof Plot); - } - - @Override - public int hashCode() { - int hash = 7; - hash = 41 * hash + Objects.hashCode(this.noDataMessage); - hash = 41 * hash + Objects.hashCode(this.noDataMessageFont); - hash = 41 * hash + Objects.hashCode(this.noDataMessagePaint); - hash = 41 * hash + Objects.hashCode(this.insets); -// hash = 41 * hash + Objects.hashCode(this.chart); - hash = 41 * hash + (this.outlineVisible ? 1 : 0); - hash = 41 * hash + Objects.hashCode(this.outlineStroke); - hash = 41 * hash + Objects.hashCode(this.outlinePaint); - hash = 41 * hash + Objects.hashCode(this.backgroundPaint); - hash = 41 * hash + Objects.hashCode(this.backgroundImage); - hash = 41 * hash + this.backgroundImageAlignment; - hash = 41 * hash + Float.floatToIntBits(this.backgroundImageAlpha); - hash = 41 * hash + Float.floatToIntBits(this.foregroundAlpha); - hash = 41 * hash + Float.floatToIntBits(this.backgroundAlpha); - hash = 41 * hash + Objects.hashCode(this.drawingSupplier); - hash = 41 * hash + (this.notify ? 1 : 0); - hash = 41 * hash + Objects.hashCode(this.datasetGroup); - return hash; - } - - /** - * Creates a clone of the plot. - * - * @return A clone. - * - * @throws CloneNotSupportedException if some component of the plot does not - * support cloning. - */ - @Override - public Object clone() throws CloneNotSupportedException { - - Plot clone = (Plot) super.clone(); - // private Plot parent <-- don't clone the parent plot, but take care - // childs in combined plots instead - if (this.datasetGroup != null) { - clone.datasetGroup - = (DatasetGroup) ObjectUtils.clone(this.datasetGroup); - } - clone.drawingSupplier - = (DrawingSupplier) ObjectUtils.clone(this.drawingSupplier); - clone.listenerList = new EventListenerList(); - return clone; - - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writePaint(this.noDataMessagePaint, stream); - SerialUtils.writeStroke(this.outlineStroke, stream); - SerialUtils.writePaint(this.outlinePaint, stream); - // backgroundImage - SerialUtils.writePaint(this.backgroundPaint, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - this.noDataMessagePaint = SerialUtils.readPaint(stream); - this.outlineStroke = SerialUtils.readStroke(stream); - this.outlinePaint = SerialUtils.readPaint(stream); - // backgroundImage - this.backgroundPaint = SerialUtils.readPaint(stream); - - this.listenerList = new EventListenerList(); - - } - - /** - * Resolves a domain axis location for a given plot orientation. - * - * @param location the location ({@code null} not permitted). - * @param orientation the orientation ({@code null} not permitted). - * - * @return The edge (never {@code null}). - */ - public static RectangleEdge resolveDomainAxisLocation( - AxisLocation location, PlotOrientation orientation) { - - Args.nullNotPermitted(location, "location"); - Args.nullNotPermitted(orientation, "orientation"); - - RectangleEdge result = null; - if (location == AxisLocation.TOP_OR_RIGHT) { - if (orientation == PlotOrientation.HORIZONTAL) { - result = RectangleEdge.RIGHT; - } - else if (orientation == PlotOrientation.VERTICAL) { - result = RectangleEdge.TOP; - } - } - else if (location == AxisLocation.TOP_OR_LEFT) { - if (orientation == PlotOrientation.HORIZONTAL) { - result = RectangleEdge.LEFT; - } - else if (orientation == PlotOrientation.VERTICAL) { - result = RectangleEdge.TOP; - } - } - else if (location == AxisLocation.BOTTOM_OR_RIGHT) { - if (orientation == PlotOrientation.HORIZONTAL) { - result = RectangleEdge.RIGHT; - } - else if (orientation == PlotOrientation.VERTICAL) { - result = RectangleEdge.BOTTOM; - } - } - else if (location == AxisLocation.BOTTOM_OR_LEFT) { - if (orientation == PlotOrientation.HORIZONTAL) { - result = RectangleEdge.LEFT; - } - else if (orientation == PlotOrientation.VERTICAL) { - result = RectangleEdge.BOTTOM; - } - } - // the above should cover all the options... - if (result == null) { - throw new IllegalStateException("resolveDomainAxisLocation()"); - } - return result; - - } - - /** - * Resolves a range axis location for a given plot orientation. - * - * @param location the location ({@code null} not permitted). - * @param orientation the orientation ({@code null} not permitted). - * - * @return The edge (never {@code null}). - */ - public static RectangleEdge resolveRangeAxisLocation( - AxisLocation location, PlotOrientation orientation) { - - Args.nullNotPermitted(location, "location"); - Args.nullNotPermitted(orientation, "orientation"); - - RectangleEdge result = null; - if (location == AxisLocation.TOP_OR_RIGHT) { - if (orientation == PlotOrientation.HORIZONTAL) { - result = RectangleEdge.TOP; - } - else if (orientation == PlotOrientation.VERTICAL) { - result = RectangleEdge.RIGHT; - } - } - else if (location == AxisLocation.TOP_OR_LEFT) { - if (orientation == PlotOrientation.HORIZONTAL) { - result = RectangleEdge.TOP; - } - else if (orientation == PlotOrientation.VERTICAL) { - result = RectangleEdge.LEFT; - } - } - else if (location == AxisLocation.BOTTOM_OR_RIGHT) { - if (orientation == PlotOrientation.HORIZONTAL) { - result = RectangleEdge.BOTTOM; - } - else if (orientation == PlotOrientation.VERTICAL) { - result = RectangleEdge.RIGHT; - } - } - else if (location == AxisLocation.BOTTOM_OR_LEFT) { - if (orientation == PlotOrientation.HORIZONTAL) { - result = RectangleEdge.BOTTOM; - } - else if (orientation == PlotOrientation.VERTICAL) { - result = RectangleEdge.LEFT; - } - } - - // the above should cover all the options... - if (result == null) { - throw new IllegalStateException("resolveRangeAxisLocation()"); - } - return result; - - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------- + * Plot.java + * --------- + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): Sylvain Vieujot; + * Jeremy Bowman; + * Andreas Schneider; + * Gideon Krause; + * Nicolas Brodu; + * Michal Krause; + * Richard West, Advanced Micro Devices, Inc.; + * Peter Kolb - patches 2603321, 2809117; + * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); + * + */ + +package org.jfree.chart.plot; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Paint; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Objects; + +import javax.swing.event.EventListenerList; + +import org.jfree.chart.JFreeChart; +import org.jfree.chart.LegendItemCollection; +import org.jfree.chart.LegendItemSource; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.entity.EntityCollection; +import org.jfree.chart.entity.PlotEntity; +import org.jfree.chart.event.AnnotationChangeEvent; +import org.jfree.chart.event.AnnotationChangeListener; +import org.jfree.chart.event.AxisChangeEvent; +import org.jfree.chart.event.AxisChangeListener; +import org.jfree.chart.event.ChartChangeEventType; +import org.jfree.chart.event.MarkerChangeEvent; +import org.jfree.chart.event.MarkerChangeListener; +import org.jfree.chart.event.PlotChangeEvent; +import org.jfree.chart.event.PlotChangeListener; +import org.jfree.chart.text.G2TextMeasurer; +import org.jfree.chart.text.TextBlock; +import org.jfree.chart.text.TextBlockAnchor; +import org.jfree.chart.text.TextUtils; +import org.jfree.chart.ui.Align; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.chart.util.ObjectUtils; +import org.jfree.chart.util.PaintUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.SerialUtils; +import org.jfree.data.general.DatasetChangeEvent; +import org.jfree.data.general.DatasetChangeListener; +import org.jfree.data.general.DatasetGroup; + +/** + * The base class for all plots in JFreeChart. The {@link JFreeChart} class + * delegates the drawing of axes and data to the plot. This base class + * provides facilities common to most plot types. + */ +public abstract class Plot implements AxisChangeListener, + DatasetChangeListener, AnnotationChangeListener, MarkerChangeListener, + LegendItemSource, PublicCloneable, Cloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = -8831571430103671324L; + + /** Useful constant representing zero. */ + public static final Number ZERO = 0; + + /** The default insets. */ + public static final RectangleInsets DEFAULT_INSETS + = new RectangleInsets(4.0, 8.0, 4.0, 8.0); + + /** The default outline stroke. */ + public static final Stroke DEFAULT_OUTLINE_STROKE = new BasicStroke(0.5f, + BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); + + /** The default outline color. */ + public static final Paint DEFAULT_OUTLINE_PAINT = Color.GRAY; + + /** The default foreground alpha transparency. */ + public static final float DEFAULT_FOREGROUND_ALPHA = 1.0f; + + /** The default background alpha transparency. */ + public static final float DEFAULT_BACKGROUND_ALPHA = 1.0f; + + /** The default background color. */ + public static final Paint DEFAULT_BACKGROUND_PAINT = Color.WHITE; + + /** The minimum width at which the plot should be drawn. */ + public static final int MINIMUM_WIDTH_TO_DRAW = 10; + + /** The minimum height at which the plot should be drawn. */ + public static final int MINIMUM_HEIGHT_TO_DRAW = 10; + + /** A default box shape for legend items. */ + public static final Shape DEFAULT_LEGEND_ITEM_BOX + = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0); + + /** A default circle shape for legend items. */ + public static final Shape DEFAULT_LEGEND_ITEM_CIRCLE + = new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0); + + /** + * The chart that the plot is assigned to. It can be {@code null} if the + * plot is not assigned to a chart yet, or if the plot is a subplot of a + * another plot. + */ + private JFreeChart chart; + + /** The parent plot ({@code null} if this is the root plot). */ + private Plot parent; + + /** The dataset group (to be used for thread synchronisation). */ + private DatasetGroup datasetGroup; + + /** The message to display if no data is available. */ + private String noDataMessage; + + /** The font used to display the 'no data' message. */ + private Font noDataMessageFont; + + /** The paint used to draw the 'no data' message. */ + private transient Paint noDataMessagePaint; + + /** Amount of blank space around the plot area. */ + private RectangleInsets insets; + + /** + * A flag that controls whether or not the plot outline is drawn. + */ + private boolean outlineVisible; + + /** The Stroke used to draw an outline around the plot. */ + private transient Stroke outlineStroke; + + /** The Paint used to draw an outline around the plot. */ + private transient Paint outlinePaint; + + /** An optional color used to fill the plot background. */ + private transient Paint backgroundPaint; + + /** An optional image for the plot background. */ + private transient Image backgroundImage; // not currently serialized + + /** The alignment for the background image. */ + private int backgroundImageAlignment = Align.FIT; + + /** The alpha value used to draw the background image. */ + private float backgroundImageAlpha = 0.5f; + + /** The alpha-transparency for the plot. */ + private float foregroundAlpha; + + /** The alpha transparency for the background paint. */ + private float backgroundAlpha; + + /** The drawing supplier. */ + private DrawingSupplier drawingSupplier; + + /** Storage for registered change listeners. */ + private transient EventListenerList listenerList; + + /** + * A flag that controls whether or not the plot will notify listeners + * of changes (defaults to true, but sometimes it is useful to disable + * this). + */ + private boolean notify; + + /** + * Creates a new plot. + */ + protected Plot() { + this.chart = null; + this.parent = null; + this.insets = DEFAULT_INSETS; + this.backgroundPaint = DEFAULT_BACKGROUND_PAINT; + this.backgroundAlpha = DEFAULT_BACKGROUND_ALPHA; + this.backgroundImage = null; + this.outlineVisible = true; + this.outlineStroke = DEFAULT_OUTLINE_STROKE; + this.outlinePaint = DEFAULT_OUTLINE_PAINT; + this.foregroundAlpha = DEFAULT_FOREGROUND_ALPHA; + + this.noDataMessage = null; + this.noDataMessageFont = new Font("SansSerif", Font.PLAIN, 12); + this.noDataMessagePaint = Color.BLACK; + + this.drawingSupplier = new DefaultDrawingSupplier(); + + this.notify = true; + this.listenerList = new EventListenerList(); + } + + /** + * Returns the chart that this plot is assigned to. This method can + * return {@code null} if the plot is not yet assigned to a plot, or if the + * plot is a subplot of another plot. + * + * @return The chart (possibly {@code null}). + */ + public JFreeChart getChart() { + return this.chart; + } + + /** + * Sets the chart that the plot is assigned to. This method is not + * intended for external use. + * + * @param chart the chart ({@code null} permitted). + */ + public void setChart(JFreeChart chart) { + this.chart = chart; + } + + /** + * Fetches the element hinting flag from the chart that this plot is + * assigned to. If the plot is not assigned (directly or indirectly) to + * a chart instance, this method will return {@code false}. + * + * @return A boolean. + */ + public boolean fetchElementHintingFlag() { + if (this.parent != null) { + return this.parent.fetchElementHintingFlag(); + } + if (this.chart != null) { + return this.chart.getElementHinting(); + } + return false; + } + + /** + * Returns the dataset group for the plot (not currently used). + * + * @return The dataset group. + * + * @see #setDatasetGroup(DatasetGroup) + */ + public DatasetGroup getDatasetGroup() { + return this.datasetGroup; + } + + /** + * Sets the dataset group (not currently used). + * + * @param group the dataset group ({@code null} permitted). + * + * @see #getDatasetGroup() + */ + protected void setDatasetGroup(DatasetGroup group) { + this.datasetGroup = group; + } + + /** + * Returns the string that is displayed when the dataset is empty or + * {@code null}. + * + * @return The 'no data' message ({@code null} possible). + * + * @see #setNoDataMessage(String) + * @see #getNoDataMessageFont() + * @see #getNoDataMessagePaint() + */ + public String getNoDataMessage() { + return this.noDataMessage; + } + + /** + * Sets the message that is displayed when the dataset is empty or + * {@code null}, and sends a {@link PlotChangeEvent} to all registered + * listeners. + * + * @param message the message ({@code null} permitted). + * + * @see #getNoDataMessage() + */ + public void setNoDataMessage(String message) { + this.noDataMessage = message; + fireChangeEvent(); + } + + /** + * Returns the font used to display the 'no data' message. + * + * @return The font (never {@code null}). + * + * @see #setNoDataMessageFont(Font) + * @see #getNoDataMessage() + */ + public Font getNoDataMessageFont() { + return this.noDataMessageFont; + } + + /** + * Sets the font used to display the 'no data' message and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param font the font ({@code null} not permitted). + * + * @see #getNoDataMessageFont() + */ + public void setNoDataMessageFont(Font font) { + Args.nullNotPermitted(font, "font"); + this.noDataMessageFont = font; + fireChangeEvent(); + } + + /** + * Returns the paint used to display the 'no data' message. + * + * @return The paint (never {@code null}). + * + * @see #setNoDataMessagePaint(Paint) + * @see #getNoDataMessage() + */ + public Paint getNoDataMessagePaint() { + return this.noDataMessagePaint; + } + + /** + * Sets the paint used to display the 'no data' message and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getNoDataMessagePaint() + */ + public void setNoDataMessagePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.noDataMessagePaint = paint; + fireChangeEvent(); + } + + /** + * Returns a short string describing the plot type. + *

+ * Note: this gets used in the chart property editing user interface, + * but there needs to be a better mechanism for identifying the plot type. + * + * @return A short string describing the plot type (never + * {@code null}). + */ + public abstract String getPlotType(); + + /** + * Returns the parent plot (or {@code null} if this plot is not part + * of a combined plot). + * + * @return The parent plot. + * + * @see #setParent(Plot) + * @see #getRootPlot() + */ + public Plot getParent() { + return this.parent; + } + + /** + * Sets the parent plot. This method is intended for internal use, you + * shouldn't need to call it directly. + * + * @param parent the parent plot ({@code null} permitted). + * + * @see #getParent() + */ + public void setParent(Plot parent) { + this.parent = parent; + } + + /** + * Returns the root plot. + * + * @return The root plot. + * + * @see #getParent() + */ + public Plot getRootPlot() { + + Plot p = getParent(); + if (p == null) { + return this; + } + return p.getRootPlot(); + + } + + /** + * Returns {@code true} if this plot is part of a combined plot + * structure (that is, {@link #getParent()} returns a non-{@code null} + * value), and {@code false} otherwise. + * + * @return {@code true} if this plot is part of a combined plot + * structure. + * + * @see #getParent() + */ + public boolean isSubplot() { + return (getParent() != null); + } + + /** + * Returns the insets for the plot area. + * + * @return The insets (never {@code null}). + * + * @see #setInsets(RectangleInsets) + */ + public RectangleInsets getInsets() { + return this.insets; + } + + /** + * Sets the insets for the plot and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param insets the new insets ({@code null} not permitted). + * + * @see #getInsets() + * @see #setInsets(RectangleInsets, boolean) + */ + public void setInsets(RectangleInsets insets) { + setInsets(insets, true); + } + + /** + * Sets the insets for the plot and, if requested, and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param insets the new insets ({@code null} not permitted). + * @param notify a flag that controls whether the registered listeners are + * notified. + * + * @see #getInsets() + * @see #setInsets(RectangleInsets) + */ + public void setInsets(RectangleInsets insets, boolean notify) { + Args.nullNotPermitted(insets, "insets"); + if (!this.insets.equals(insets)) { + this.insets = insets; + if (notify) { + fireChangeEvent(); + } + } + + } + + /** + * Returns the background color of the plot area. + * + * @return The paint (possibly {@code null}). + * + * @see #setBackgroundPaint(Paint) + */ + public Paint getBackgroundPaint() { + return this.backgroundPaint; + } + + /** + * Sets the background color of the plot area and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} permitted). + * + * @see #getBackgroundPaint() + */ + public void setBackgroundPaint(Paint paint) { + + if (paint == null) { + if (this.backgroundPaint != null) { + this.backgroundPaint = null; + fireChangeEvent(); + } + } + else { + if (this.backgroundPaint != null) { + if (this.backgroundPaint.equals(paint)) { + return; // nothing to do + } + } + this.backgroundPaint = paint; + fireChangeEvent(); + } + + } + + /** + * Returns the alpha transparency of the plot area background. + * + * @return The alpha transparency. + * + * @see #setBackgroundAlpha(float) + */ + public float getBackgroundAlpha() { + return this.backgroundAlpha; + } + + /** + * Sets the alpha transparency of the plot area background, and notifies + * registered listeners that the plot has been modified. + * + * @param alpha the new alpha value (in the range 0.0f to 1.0f). + * + * @see #getBackgroundAlpha() + */ + public void setBackgroundAlpha(float alpha) { + if (this.backgroundAlpha != alpha) { + this.backgroundAlpha = alpha; + fireChangeEvent(); + } + } + + /** + * Returns the drawing supplier for the plot. + * + * @return The drawing supplier (possibly {@code null}). + * + * @see #setDrawingSupplier(DrawingSupplier) + */ + public DrawingSupplier getDrawingSupplier() { + DrawingSupplier result; + Plot p = getParent(); + if (p != null) { + result = p.getDrawingSupplier(); + } + else { + result = this.drawingSupplier; + } + return result; + } + + /** + * Sets the drawing supplier for the plot and sends a + * {@link PlotChangeEvent} to all registered listeners. The drawing + * supplier is responsible for supplying a limitless (possibly repeating) + * sequence of {@code Paint}, {@code Stroke} and + * {@code Shape} objects that the plot's renderer(s) can use to + * populate its (their) tables. + * + * @param supplier the new supplier. + * + * @see #getDrawingSupplier() + */ + public void setDrawingSupplier(DrawingSupplier supplier) { + this.drawingSupplier = supplier; + fireChangeEvent(); + } + + /** + * Sets the drawing supplier for the plot and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. The drawing + * supplier is responsible for supplying a limitless (possibly repeating) + * sequence of {@code Paint}, {@code Stroke} and + * {@code Shape} objects that the plot's renderer(s) can use to + * populate its (their) tables. + * + * @param supplier the new supplier. + * @param notify notify listeners? + * + * @see #getDrawingSupplier() + */ + public void setDrawingSupplier(DrawingSupplier supplier, boolean notify) { + this.drawingSupplier = supplier; + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the background image that is used to fill the plot's background + * area. + * + * @return The image (possibly {@code null}). + * + * @see #setBackgroundImage(Image) + */ + public Image getBackgroundImage() { + return this.backgroundImage; + } + + /** + * Sets the background image for the plot and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param image the image ({@code null} permitted). + * + * @see #getBackgroundImage() + */ + public void setBackgroundImage(Image image) { + this.backgroundImage = image; + fireChangeEvent(); + } + + /** + * Returns the background image alignment. Alignment constants are defined + * in the {@code Align} class. + * + * @return The alignment. + * + * @see #setBackgroundImageAlignment(int) + */ + public int getBackgroundImageAlignment() { + return this.backgroundImageAlignment; + } + + /** + * Sets the alignment for the background image and sends a + * {@link PlotChangeEvent} to all registered listeners. Alignment options + * are defined by the {@link org.jfree.chart.ui.Align} class. + * + * @param alignment the alignment. + * + * @see #getBackgroundImageAlignment() + */ + public void setBackgroundImageAlignment(int alignment) { + if (this.backgroundImageAlignment != alignment) { + this.backgroundImageAlignment = alignment; + fireChangeEvent(); + } + } + + /** + * Returns the alpha transparency used to draw the background image. This + * is a value in the range 0.0f to 1.0f, where 0.0f is fully transparent + * and 1.0f is fully opaque. + * + * @return The alpha transparency. + * + * @see #setBackgroundImageAlpha(float) + */ + public float getBackgroundImageAlpha() { + return this.backgroundImageAlpha; + } + + /** + * Sets the alpha transparency used when drawing the background image. + * + * @param alpha the alpha transparency (in the range 0.0f to 1.0f, where + * 0.0f is fully transparent, and 1.0f is fully opaque). + * + * @throws IllegalArgumentException if {@code alpha} is not within + * the specified range. + * + * @see #getBackgroundImageAlpha() + */ + public void setBackgroundImageAlpha(float alpha) { + if (alpha < 0.0f || alpha > 1.0f) { + throw new IllegalArgumentException( + "The 'alpha' value must be in the range 0.0f to 1.0f."); + } + if (this.backgroundImageAlpha != alpha) { + this.backgroundImageAlpha = alpha; + fireChangeEvent(); + } + } + + /** + * Returns the flag that controls whether or not the plot outline is + * drawn. The default value is {@code true}. Note that for + * historical reasons, the plot's outline paint and stroke can take on + * {@code null} values, in which case the outline will not be drawn + * even if this flag is set to {@code true}. + * + * @return The outline visibility flag. + * + * @see #setOutlineVisible(boolean) + */ + public boolean isOutlineVisible() { + return this.outlineVisible; + } + + /** + * Sets the flag that controls whether or not the plot's outline is + * drawn, and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param visible the new flag value. + * + * @see #isOutlineVisible() + */ + public void setOutlineVisible(boolean visible) { + this.outlineVisible = visible; + fireChangeEvent(); + } + + /** + * Returns the stroke used to outline the plot area. + * + * @return The stroke (possibly {@code null}). + * + * @see #setOutlineStroke(Stroke) + */ + public Stroke getOutlineStroke() { + return this.outlineStroke; + } + + /** + * Sets the stroke used to outline the plot area and sends a + * {@link PlotChangeEvent} to all registered listeners. If you set this + * attribute to {@code null}, no outline will be drawn. + * + * @param stroke the stroke ({@code null} permitted). + * + * @see #getOutlineStroke() + */ + public void setOutlineStroke(Stroke stroke) { + if (stroke == null) { + if (this.outlineStroke != null) { + this.outlineStroke = null; + fireChangeEvent(); + } + } + else { + if (this.outlineStroke != null) { + if (this.outlineStroke.equals(stroke)) { + return; // nothing to do + } + } + this.outlineStroke = stroke; + fireChangeEvent(); + } + } + + /** + * Returns the color used to draw the outline of the plot area. + * + * @return The color (possibly {@code null}). + * + * @see #setOutlinePaint(Paint) + */ + public Paint getOutlinePaint() { + return this.outlinePaint; + } + + /** + * Sets the paint used to draw the outline of the plot area and sends a + * {@link PlotChangeEvent} to all registered listeners. If you set this + * attribute to {@code null}, no outline will be drawn. + * + * @param paint the paint ({@code null} permitted). + * + * @see #getOutlinePaint() + */ + public void setOutlinePaint(Paint paint) { + if (paint == null) { + if (this.outlinePaint != null) { + this.outlinePaint = null; + fireChangeEvent(); + } + } + else { + if (this.outlinePaint != null) { + if (this.outlinePaint.equals(paint)) { + return; // nothing to do + } + } + this.outlinePaint = paint; + fireChangeEvent(); + } + } + + /** + * Returns the alpha-transparency for the plot foreground. + * + * @return The alpha-transparency. + * + * @see #setForegroundAlpha(float) + */ + public float getForegroundAlpha() { + return this.foregroundAlpha; + } + + /** + * Sets the alpha-transparency for the plot and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param alpha the new alpha transparency. + * + * @see #getForegroundAlpha() + */ + public void setForegroundAlpha(float alpha) { + if (this.foregroundAlpha != alpha) { + this.foregroundAlpha = alpha; + fireChangeEvent(); + } + } + + /** + * Returns the legend items for the plot. By default, this method returns + * {@code null}. Subclasses should override to return a + * {@link LegendItemCollection}. + * + * @return The legend items for the plot (possibly {@code null}). + */ + @Override + public LegendItemCollection getLegendItems() { + return null; + } + + /** + * Returns a flag that controls whether or not change events are sent to + * registered listeners. + * + * @return A boolean. + * + * @see #setNotify(boolean) + */ + public boolean isNotify() { + return this.notify; + } + + /** + * Sets a flag that controls whether or not listeners receive + * {@link PlotChangeEvent} notifications. + * + * @param notify a boolean. + * + * @see #isNotify() + */ + public void setNotify(boolean notify) { + this.notify = notify; + // if the flag is being set to true, there may be queued up changes... + if (notify) { + notifyListeners(new PlotChangeEvent(this)); + } + } + + /** + * Registers an object for notification of changes to the plot. + * + * @param listener the object to be registered. + * + * @see #removeChangeListener(PlotChangeListener) + */ + public void addChangeListener(PlotChangeListener listener) { + this.listenerList.add(PlotChangeListener.class, listener); + } + + /** + * Unregisters an object for notification of changes to the plot. + * + * @param listener the object to be unregistered. + * + * @see #addChangeListener(PlotChangeListener) + */ + public void removeChangeListener(PlotChangeListener listener) { + this.listenerList.remove(PlotChangeListener.class, listener); + } + + /** + * Notifies all registered listeners that the plot has been modified. + * + * @param event information about the change event. + */ + public void notifyListeners(PlotChangeEvent event) { + // if the 'notify' flag has been switched to false, we don't notify + // the listeners + if (!this.notify) { + return; + } + Object[] listeners = this.listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == PlotChangeListener.class) { + ((PlotChangeListener) listeners[i + 1]).plotChanged(event); + } + } + } + + /** + * Sends a {@link PlotChangeEvent} to all registered listeners. + */ + protected void fireChangeEvent() { + notifyListeners(new PlotChangeEvent(this)); + } + + /** + * Draws the plot within the specified area. The anchor is a point on the + * chart that is specified externally (for instance, it may be the last + * point of the last mouse click performed by the user) - plots can use or + * ignore this value as they see fit. + *

+ * Subclasses need to provide an implementation of this method, obviously. + * + * @param g2 the graphics device. + * @param area the plot area. + * @param anchor the anchor point ({@code null} permitted). + * @param parentState the parent state (if any, {@code null} permitted). + * @param info carries back plot rendering info. + */ + public abstract void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, + PlotState parentState, PlotRenderingInfo info); + + /** + * Draws the plot background (the background color and/or image). + *

+ * This method will be called during the chart drawing process and is + * declared public so that it can be accessed by the renderers used by + * certain subclasses. You shouldn't need to call this method directly. + * + * @param g2 the graphics device. + * @param area the area within which the plot should be drawn. + */ + public void drawBackground(Graphics2D g2, Rectangle2D area) { + // some subclasses override this method completely, so don't put + // anything here that *must* be done + fillBackground(g2, area); + drawBackgroundImage(g2, area); + } + + /** + * Fills the specified area with the background paint. + * + * @param g2 the graphics device. + * @param area the area. + * + * @see #getBackgroundPaint() + * @see #getBackgroundAlpha() + * @see #fillBackground(Graphics2D, Rectangle2D, PlotOrientation) + */ + protected void fillBackground(Graphics2D g2, Rectangle2D area) { + fillBackground(g2, area, PlotOrientation.VERTICAL); + } + + /** + * Fills the specified area with the background paint. If the background + * paint is an instance of {@code GradientPaint}, the gradient will + * run in the direction suggested by the plot's orientation. + * + * @param g2 the graphics target. + * @param area the plot area. + * @param orientation the plot orientation ({@code null} not + * permitted). + */ + protected void fillBackground(Graphics2D g2, Rectangle2D area, + PlotOrientation orientation) { + Args.nullNotPermitted(orientation, "orientation"); + if (this.backgroundPaint == null) { + return; + } + Paint p = this.backgroundPaint; + if (p instanceof GradientPaint) { + GradientPaint gp = (GradientPaint) p; + if (orientation == PlotOrientation.VERTICAL) { + p = new GradientPaint((float) area.getCenterX(), + (float) area.getMaxY(), gp.getColor1(), + (float) area.getCenterX(), (float) area.getMinY(), + gp.getColor2()); + } + else if (orientation == PlotOrientation.HORIZONTAL) { + p = new GradientPaint((float) area.getMinX(), + (float) area.getCenterY(), gp.getColor1(), + (float) area.getMaxX(), (float) area.getCenterY(), + gp.getColor2()); + } + } + Composite originalComposite = g2.getComposite(); + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + this.backgroundAlpha)); + g2.setPaint(p); + g2.fill(area); + g2.setComposite(originalComposite); + } + + /** + * Draws the background image (if there is one) aligned within the + * specified area. + * + * @param g2 the graphics device. + * @param area the area. + * + * @see #getBackgroundImage() + * @see #getBackgroundImageAlignment() + * @see #getBackgroundImageAlpha() + */ + public void drawBackgroundImage(Graphics2D g2, Rectangle2D area) { + if (this.backgroundImage == null) { + return; // nothing to do + } + Composite savedComposite = g2.getComposite(); + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + this.backgroundImageAlpha)); + Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0, + this.backgroundImage.getWidth(null), + this.backgroundImage.getHeight(null)); + Align.align(dest, area, this.backgroundImageAlignment); + Shape savedClip = g2.getClip(); + g2.clip(area); + g2.drawImage(this.backgroundImage, (int) dest.getX(), + (int) dest.getY(), (int) dest.getWidth() + 1, + (int) dest.getHeight() + 1, null); + g2.setClip(savedClip); + g2.setComposite(savedComposite); + } + + /** + * Draws the plot outline. This method will be called during the chart + * drawing process and is declared public so that it can be accessed by the + * renderers used by certain subclasses. You shouldn't need to call this + * method directly. + * + * @param g2 the graphics device. + * @param area the area within which the plot should be drawn. + */ + public void drawOutline(Graphics2D g2, Rectangle2D area) { + if (!this.outlineVisible) { + return; + } + if ((this.outlineStroke != null) && (this.outlinePaint != null)) { + g2.setStroke(this.outlineStroke); + g2.setPaint(this.outlinePaint); + Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); + g2.draw(area); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); + } + } + + /** + * Draws a message to state that there is no data to plot. + * + * @param g2 the graphics device. + * @param area the area within which the plot should be drawn. + */ + protected void drawNoDataMessage(Graphics2D g2, Rectangle2D area) { + Shape savedClip = g2.getClip(); + g2.clip(area); + String message = this.noDataMessage; + if (message != null) { + g2.setFont(this.noDataMessageFont); + g2.setPaint(this.noDataMessagePaint); + TextBlock block = TextUtils.createTextBlock( + this.noDataMessage, this.noDataMessageFont, + this.noDataMessagePaint, 0.9f * (float) area.getWidth(), + new G2TextMeasurer(g2)); + block.draw(g2, (float) area.getCenterX(), + (float) area.getCenterY(), TextBlockAnchor.CENTER); + } + g2.setClip(savedClip); + } + + /** + * Creates a plot entity that contains a reference to the plot and the + * data area as shape. + * + * @param dataArea the data area used as hot spot for the entity. + * @param plotState the plot rendering info containing a reference to the + * EntityCollection. + * @param toolTip the tool tip (defined in the respective Plot + * subclass) ({@code null} permitted). + * @param urlText the url (defined in the respective Plot subclass) + * ({@code null} permitted). + */ + protected void createAndAddEntity(Rectangle2D dataArea, + PlotRenderingInfo plotState, String toolTip, String urlText) { + if (plotState != null && plotState.getOwner() != null) { + EntityCollection e = plotState.getOwner().getEntityCollection(); + if (e != null) { + e.add(new PlotEntity(dataArea, this, toolTip, urlText)); + } + } + } + + /** + * Handles a 'click' on the plot. Since the plot does not maintain any + * information about where it has been drawn, the plot rendering info is + * supplied as an argument so that the plot dimensions can be determined. + * + * @param x the x coordinate (in Java2D space). + * @param y the y coordinate (in Java2D space). + * @param info an object containing information about the dimensions of + * the plot. + */ + public void handleClick(int x, int y, PlotRenderingInfo info) { + // provides a 'no action' default + } + + /** + * Performs a zoom on the plot. Subclasses should override if zooming is + * appropriate for the type of plot. + * + * @param percent the zoom percentage. + */ + public void zoom(double percent) { + // do nothing by default. + } + + /** + * Receives notification of a change to an annotation on the plot. + * + * @param event information about the event (not used here). + */ + @Override + public void annotationChanged(AnnotationChangeEvent event) { + fireChangeEvent(); + } + + /** + * Receives notification of a change to one of the plot's axes. + * + * @param event information about the event (not used here). + */ + @Override + public void axisChanged(AxisChangeEvent event) { + fireChangeEvent(); + } + + /** + * Receives notification of a change to the plot's dataset. + *

+ * The plot reacts by passing on a plot change event to all registered + * listeners. + * + * @param event information about the event (not used here). + */ + @Override + public void datasetChanged(DatasetChangeEvent event) { + PlotChangeEvent newEvent = new PlotChangeEvent(this); + newEvent.setType(ChartChangeEventType.DATASET_UPDATED); + notifyListeners(newEvent); + } + + /** + * Receives notification of a change to a marker that is assigned to the + * plot. + * + * @param event the event. + */ + @Override + public void markerChanged(MarkerChangeEvent event) { + fireChangeEvent(); + } + + /** + * Adjusts the supplied x-value. + * + * @param x the x-value. + * @param w1 width 1. + * @param w2 width 2. + * @param edge the edge (left or right). + * + * @return The adjusted x-value. + */ + protected double getRectX(double x, double w1, double w2, + RectangleEdge edge) { + + double result = x; + if (edge == RectangleEdge.LEFT) { + result = result + w1; + } + else if (edge == RectangleEdge.RIGHT) { + result = result + w2; + } + return result; + + } + + /** + * Adjusts the supplied y-value. + * + * @param y the x-value. + * @param h1 height 1. + * @param h2 height 2. + * @param edge the edge (top or bottom). + * + * @return The adjusted y-value. + */ + protected double getRectY(double y, double h1, double h2, + RectangleEdge edge) { + + double result = y; + if (edge == RectangleEdge.TOP) { + result = result + h1; + } + else if (edge == RectangleEdge.BOTTOM) { + result = result + h2; + } + return result; + + } + + /** + * Tests this plot for equality with another object. + * + * @param obj the object ({@code null} permitted). + * + * @return {@code true} or {@code false}. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Plot)) { + return false; + } + Plot that = (Plot) obj; + // fix the "equals not symmetric" problem + if (!that.canEqual(this)) { + return false; + } + if (!Objects.equals(this.noDataMessage, that.noDataMessage)) { + return false; + } + if (!Objects.equals( + this.noDataMessageFont, that.noDataMessageFont + )) { + return false; + } + if (!PaintUtils.equal(this.noDataMessagePaint, + that.noDataMessagePaint)) { + return false; + } + if (!Objects.equals(this.insets, that.insets)) { + return false; + } + // There's a reason chart is not included in equals/hashCode - doing so + // causes a StackOverflow error during EqualsVerifier's test! +// if (!Objects.equals(this.chart, that.chart)) { +// return false; +// } + if (this.outlineVisible != that.outlineVisible) { + return false; + } + if (!Objects.equals(this.outlineStroke, that.outlineStroke)) { + return false; + } + if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { + return false; + } + if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) { + return false; + } + if (!Objects.equals(this.backgroundImage, that.backgroundImage)) { + return false; + } + if (this.backgroundImageAlignment != that.backgroundImageAlignment) { + return false; + } + if (Float.compare(this.backgroundImageAlpha, + that.backgroundImageAlpha) != 0 ){ + return false; + } + if (Float.compare(this.foregroundAlpha, that.foregroundAlpha) != 0 ) { + return false; + } + if (Float.compare(this.backgroundAlpha, that.backgroundAlpha) != 0 ) { + return false; + } + if (!Objects.equals(this.drawingSupplier, that.drawingSupplier)) { + return false; + } + if (this.notify != that.notify) { + return false; + } + if (!Objects.equals(this.datasetGroup, that.datasetGroup)) { + return false; + } + return true; + } + + /** + * Ensures symmetry between super/subclass implementations of equals. For + * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. + * + * @param other Object + * + * @return true ONLY if the parameter is THIS class type + */ + public boolean canEqual(Object other) { + // Solves Problem: equals not symmetric + return (other instanceof Plot); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 41 * hash + Objects.hashCode(this.noDataMessage); + hash = 41 * hash + Objects.hashCode(this.noDataMessageFont); + hash = 41 * hash + Objects.hashCode(this.noDataMessagePaint); + hash = 41 * hash + Objects.hashCode(this.insets); +// hash = 41 * hash + Objects.hashCode(this.chart); + hash = 41 * hash + (this.outlineVisible ? 1 : 0); + hash = 41 * hash + Objects.hashCode(this.outlineStroke); + hash = 41 * hash + Objects.hashCode(this.outlinePaint); + hash = 41 * hash + Objects.hashCode(this.backgroundPaint); + hash = 41 * hash + Objects.hashCode(this.backgroundImage); + hash = 41 * hash + this.backgroundImageAlignment; + hash = 41 * hash + Float.floatToIntBits(this.backgroundImageAlpha); + hash = 41 * hash + Float.floatToIntBits(this.foregroundAlpha); + hash = 41 * hash + Float.floatToIntBits(this.backgroundAlpha); + hash = 41 * hash + Objects.hashCode(this.drawingSupplier); + hash = 41 * hash + (this.notify ? 1 : 0); + hash = 41 * hash + Objects.hashCode(this.datasetGroup); + return hash; + } + + /** + * Creates a clone of the plot. + * + * @return A clone. + * + * @throws CloneNotSupportedException if some component of the plot does not + * support cloning. + */ + @Override + public Object clone() throws CloneNotSupportedException { + + Plot clone = (Plot) super.clone(); + // private Plot parent <-- don't clone the parent plot, but take care + // childs in combined plots instead + if (this.datasetGroup != null) { + clone.datasetGroup + = (DatasetGroup) ObjectUtils.clone(this.datasetGroup); + } + clone.drawingSupplier + = (DrawingSupplier) ObjectUtils.clone(this.drawingSupplier); + clone.listenerList = new EventListenerList(); + return clone; + + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writePaint(this.noDataMessagePaint, stream); + SerialUtils.writeStroke(this.outlineStroke, stream); + SerialUtils.writePaint(this.outlinePaint, stream); + // backgroundImage + SerialUtils.writePaint(this.backgroundPaint, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + this.noDataMessagePaint = SerialUtils.readPaint(stream); + this.outlineStroke = SerialUtils.readStroke(stream); + this.outlinePaint = SerialUtils.readPaint(stream); + // backgroundImage + this.backgroundPaint = SerialUtils.readPaint(stream); + + this.listenerList = new EventListenerList(); + + } + + /** + * Resolves a domain axis location for a given plot orientation. + * + * @param location the location ({@code null} not permitted). + * @param orientation the orientation ({@code null} not permitted). + * + * @return The edge (never {@code null}). + */ + public static RectangleEdge resolveDomainAxisLocation( + AxisLocation location, PlotOrientation orientation) { + + Args.nullNotPermitted(location, "location"); + Args.nullNotPermitted(orientation, "orientation"); + + RectangleEdge result = null; + if (location == AxisLocation.TOP_OR_RIGHT) { + if (orientation == PlotOrientation.HORIZONTAL) { + result = RectangleEdge.RIGHT; + } + else if (orientation == PlotOrientation.VERTICAL) { + result = RectangleEdge.TOP; + } + } + else if (location == AxisLocation.TOP_OR_LEFT) { + if (orientation == PlotOrientation.HORIZONTAL) { + result = RectangleEdge.LEFT; + } + else if (orientation == PlotOrientation.VERTICAL) { + result = RectangleEdge.TOP; + } + } + else if (location == AxisLocation.BOTTOM_OR_RIGHT) { + if (orientation == PlotOrientation.HORIZONTAL) { + result = RectangleEdge.RIGHT; + } + else if (orientation == PlotOrientation.VERTICAL) { + result = RectangleEdge.BOTTOM; + } + } + else if (location == AxisLocation.BOTTOM_OR_LEFT) { + if (orientation == PlotOrientation.HORIZONTAL) { + result = RectangleEdge.LEFT; + } + else if (orientation == PlotOrientation.VERTICAL) { + result = RectangleEdge.BOTTOM; + } + } + // the above should cover all the options... + if (result == null) { + throw new IllegalStateException("resolveDomainAxisLocation()"); + } + return result; + + } + + /** + * Resolves a range axis location for a given plot orientation. + * + * @param location the location ({@code null} not permitted). + * @param orientation the orientation ({@code null} not permitted). + * + * @return The edge (never {@code null}). + */ + public static RectangleEdge resolveRangeAxisLocation( + AxisLocation location, PlotOrientation orientation) { + + Args.nullNotPermitted(location, "location"); + Args.nullNotPermitted(orientation, "orientation"); + + RectangleEdge result = null; + if (location == AxisLocation.TOP_OR_RIGHT) { + if (orientation == PlotOrientation.HORIZONTAL) { + result = RectangleEdge.TOP; + } + else if (orientation == PlotOrientation.VERTICAL) { + result = RectangleEdge.RIGHT; + } + } + else if (location == AxisLocation.TOP_OR_LEFT) { + if (orientation == PlotOrientation.HORIZONTAL) { + result = RectangleEdge.TOP; + } + else if (orientation == PlotOrientation.VERTICAL) { + result = RectangleEdge.LEFT; + } + } + else if (location == AxisLocation.BOTTOM_OR_RIGHT) { + if (orientation == PlotOrientation.HORIZONTAL) { + result = RectangleEdge.BOTTOM; + } + else if (orientation == PlotOrientation.VERTICAL) { + result = RectangleEdge.RIGHT; + } + } + else if (location == AxisLocation.BOTTOM_OR_LEFT) { + if (orientation == PlotOrientation.HORIZONTAL) { + result = RectangleEdge.BOTTOM; + } + else if (orientation == PlotOrientation.VERTICAL) { + result = RectangleEdge.LEFT; + } + } + + // the above should cover all the options... + if (result == null) { + throw new IllegalStateException("resolveRangeAxisLocation()"); + } + return result; + + } + +} diff --git a/src/main/java/org/jfree/chart/plot/PolarPlot.java b/src/main/java/org/jfree/chart/plot/PolarPlot.java index cfce81813..9e24b0cbd 100644 --- a/src/main/java/org/jfree/chart/plot/PolarPlot.java +++ b/src/main/java/org/jfree/chart/plot/PolarPlot.java @@ -1,2052 +1,2040 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------- - * PolarPlot.java - * -------------- - * (C) Copyright 2004-present, by Solution Engineering, Inc. and Contributors. - * - * Original Author: Daniel Bridenbecker, Solution Engineering, Inc.; - * Contributor(s): David Gilbert; - * Martin Hoeller (patches 1871902 and 2850344); - * - */ - -package org.jfree.chart.plot; - -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Point; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.ResourceBundle; -import java.util.TreeMap; - -import org.jfree.chart.LegendItem; -import org.jfree.chart.LegendItemCollection; -import org.jfree.chart.axis.Axis; -import org.jfree.chart.axis.AxisState; -import org.jfree.chart.axis.NumberTick; -import org.jfree.chart.axis.NumberTickUnit; -import org.jfree.chart.axis.TickType; -import org.jfree.chart.axis.TickUnit; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.axis.ValueTick; -import org.jfree.chart.event.PlotChangeEvent; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.event.RendererChangeListener; -import org.jfree.chart.renderer.PolarItemRenderer; -import org.jfree.chart.text.TextUtils; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.RectangleInsets; -import org.jfree.chart.ui.TextAnchor; -import org.jfree.chart.util.ObjectList; -import org.jfree.chart.util.ObjectUtils; -import org.jfree.chart.util.PaintUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.ResourceBundleWrapper; -import org.jfree.chart.util.SerialUtils; -import org.jfree.data.Range; -import org.jfree.data.general.Dataset; -import org.jfree.data.general.DatasetChangeEvent; -import org.jfree.data.general.DatasetUtils; -import org.jfree.data.xy.XYDataset; - -/** - * Plots data that is in (theta, radius) pairs where - * theta equal to zero is due north and increases clockwise. - */ -public class PolarPlot extends Plot implements ValueAxisPlot, Zoomable, - RendererChangeListener, Cloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = 3794383185924179525L; - - /** The default margin. */ - private static final int DEFAULT_MARGIN = 20; - - /** The annotation margin. */ - private static final double ANNOTATION_MARGIN = 7.0; - - /** - * The default angle tick unit size. - */ - public static final double DEFAULT_ANGLE_TICK_UNIT_SIZE = 45.0; - - /** - * The default angle offset. - */ - public static final double DEFAULT_ANGLE_OFFSET = -90.0; - - /** The default grid line stroke. */ - public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke( - 0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, - 0.0f, new float[]{2.0f, 2.0f}, 0.0f); - - /** The default grid line paint. */ - public static final Paint DEFAULT_GRIDLINE_PAINT = Color.GRAY; - - /** The resourceBundle for the localization. */ - protected static ResourceBundle localizationResources - = ResourceBundleWrapper.getBundle( - "org.jfree.chart.plot.LocalizationBundle"); - - /** The angles that are marked with gridlines. */ - private List angleTicks; - - /** The range axis (used for the y-values). */ - private ObjectList axes; - - /** The axis locations. */ - private ObjectList axisLocations; - - /** Storage for the datasets. */ - private ObjectList datasets; - - /** Storage for the renderers. */ - private ObjectList renderers; - - /** - * The tick unit that controls the spacing between the angular grid lines. - */ - private TickUnit angleTickUnit; - - /** - * An offset for the angles, to start with 0 degrees at north, east, south - * or west. - */ - private double angleOffset; - - /** - * A flag indicating if the angles increase counterclockwise or clockwise. - */ - private boolean counterClockwise; - - /** A flag that controls whether or not the angle labels are visible. */ - private boolean angleLabelsVisible = true; - - /** The font used to display the angle labels - never null. */ - private Font angleLabelFont = new Font("SansSerif", Font.PLAIN, 12); - - /** The paint used to display the angle labels. */ - private transient Paint angleLabelPaint = Color.BLACK; - - /** A flag that controls whether the angular grid-lines are visible. */ - private boolean angleGridlinesVisible; - - /** The stroke used to draw the angular grid-lines. */ - private transient Stroke angleGridlineStroke; - - /** The paint used to draw the angular grid-lines. */ - private transient Paint angleGridlinePaint; - - /** A flag that controls whether the radius grid-lines are visible. */ - private boolean radiusGridlinesVisible; - - /** The stroke used to draw the radius grid-lines. */ - private transient Stroke radiusGridlineStroke; - - /** The paint used to draw the radius grid-lines. */ - private transient Paint radiusGridlinePaint; - - /** - * A flag that controls whether the radial minor grid-lines are visible. - */ - private boolean radiusMinorGridlinesVisible; - - /** The annotations for the plot. */ - private List cornerTextItems = new ArrayList(); - - /** - * The actual margin in pixels. - */ - private int margin; - - /** - * An optional collection of legend items that can be returned by the - * getLegendItems() method. - */ - private LegendItemCollection fixedLegendItems; - - /** - * Storage for the mapping between datasets/renderers and range axes. The - * keys in the map are Integer objects, corresponding to the dataset - * index. The values in the map are List objects containing Integer - * objects (corresponding to the axis indices). If the map contains no - * entry for a dataset, it is assumed to map to the primary domain axis - * (index = 0). - */ - private Map datasetToAxesMap; - - /** - * Default constructor. - */ - public PolarPlot() { - this(null, null, null); - } - - /** - * Creates a new plot. - * - * @param dataset the dataset ({@code null} permitted). - * @param radiusAxis the radius axis ({@code null} permitted). - * @param renderer the renderer ({@code null} permitted). - */ - public PolarPlot(XYDataset dataset, ValueAxis radiusAxis, - PolarItemRenderer renderer) { - - super(); - - this.datasets = new ObjectList(); - this.datasets.set(0, dataset); - if (dataset != null) { - dataset.addChangeListener(this); - } - this.angleTickUnit = new NumberTickUnit(DEFAULT_ANGLE_TICK_UNIT_SIZE); - - this.axes = new ObjectList(); - this.datasetToAxesMap = new TreeMap(); - this.axes.set(0, radiusAxis); - if (radiusAxis != null) { - radiusAxis.setPlot(this); - radiusAxis.addChangeListener(this); - } - - // define the default locations for up to 8 axes... - this.axisLocations = new ObjectList(); - this.axisLocations.set(0, PolarAxisLocation.EAST_ABOVE); - this.axisLocations.set(1, PolarAxisLocation.NORTH_LEFT); - this.axisLocations.set(2, PolarAxisLocation.WEST_BELOW); - this.axisLocations.set(3, PolarAxisLocation.SOUTH_RIGHT); - this.axisLocations.set(4, PolarAxisLocation.EAST_BELOW); - this.axisLocations.set(5, PolarAxisLocation.NORTH_RIGHT); - this.axisLocations.set(6, PolarAxisLocation.WEST_ABOVE); - this.axisLocations.set(7, PolarAxisLocation.SOUTH_LEFT); - - this.renderers = new ObjectList(); - this.renderers.set(0, renderer); - if (renderer != null) { - renderer.setPlot(this); - renderer.addChangeListener(this); - } - - this.angleOffset = DEFAULT_ANGLE_OFFSET; - this.counterClockwise = false; - this.angleGridlinesVisible = true; - this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE; - this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT; - - this.radiusGridlinesVisible = true; - this.radiusMinorGridlinesVisible = true; - this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE; - this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT; - this.margin = DEFAULT_MARGIN; - } - - /** - * Returns the plot type as a string. - * - * @return A short string describing the type of plot. - */ - @Override - public String getPlotType() { - return PolarPlot.localizationResources.getString("Polar_Plot"); - } - - /** - * Returns the primary axis for the plot. - * - * @return The primary axis (possibly {@code null}). - * - * @see #setAxis(ValueAxis) - */ - public ValueAxis getAxis() { - return getAxis(0); - } - - /** - * Returns an axis for the plot. - * - * @param index the axis index. - * - * @return The axis ({@code null} possible). - * - * @see #setAxis(int, ValueAxis) - */ - public ValueAxis getAxis(int index) { - ValueAxis result = null; - if (index < this.axes.size()) { - result = (ValueAxis) this.axes.get(index); - } - return result; - } - - /** - * Sets the primary axis for the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param axis the new primary axis ({@code null} permitted). - */ - public void setAxis(ValueAxis axis) { - setAxis(0, axis); - } - - /** - * Sets an axis for the plot and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param index the axis index. - * @param axis the axis ({@code null} permitted). - * - * @see #getAxis(int) - */ - public void setAxis(int index, ValueAxis axis) { - setAxis(index, axis, true); - } - - /** - * Sets an axis for the plot and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the axis index. - * @param axis the axis ({@code null} permitted). - * @param notify notify listeners? - * - * @see #getAxis(int) - */ - public void setAxis(int index, ValueAxis axis, boolean notify) { - ValueAxis existing = getAxis(index); - if (existing != null) { - existing.removeChangeListener(this); - } - if (axis != null) { - axis.setPlot(this); - } - this.axes.set(index, axis); - if (axis != null) { - axis.configure(); - axis.addChangeListener(this); - } - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the location of the primary axis. - * - * @return The location (never {@code null}). - * - * @see #setAxisLocation(PolarAxisLocation) - */ - public PolarAxisLocation getAxisLocation() { - return getAxisLocation(0); - } - - /** - * Returns the location for an axis. - * - * @param index the axis index. - * - * @return The location (never {@code null}). - * - * @see #setAxisLocation(int, PolarAxisLocation) - */ - public PolarAxisLocation getAxisLocation(int index) { - PolarAxisLocation result = null; - if (index < this.axisLocations.size()) { - result = (PolarAxisLocation) this.axisLocations.get(index); - } - return result; - } - - /** - * Sets the location of the primary axis and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param location the location ({@code null} not permitted). - * - * @see #getAxisLocation() - */ - public void setAxisLocation(PolarAxisLocation location) { - // delegate... - setAxisLocation(0, location, true); - } - - /** - * Sets the location of the primary axis and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param location the location ({@code null} not permitted). - * @param notify notify listeners? - * - * @see #getAxisLocation() - */ - public void setAxisLocation(PolarAxisLocation location, boolean notify) { - // delegate... - setAxisLocation(0, location, notify); - } - - /** - * Sets the location for an axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param index the axis index. - * @param location the location ({@code null} not permitted). - * - * @see #getAxisLocation(int) - */ - public void setAxisLocation(int index, PolarAxisLocation location) { - // delegate... - setAxisLocation(index, location, true); - } - - /** - * Sets the axis location for an axis and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the axis index. - * @param location the location ({@code null} not permitted). - * @param notify notify listeners? - */ - public void setAxisLocation(int index, PolarAxisLocation location, - boolean notify) { - Args.nullNotPermitted(location, "location"); - this.axisLocations.set(index, location); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the number of domain axes. - * - * @return The axis count. - **/ - public int getAxisCount() { - return this.axes.size(); - } - - /** - * Returns the primary dataset for the plot. - * - * @return The primary dataset (possibly {@code null}). - * - * @see #setDataset(XYDataset) - */ - public XYDataset getDataset() { - return getDataset(0); - } - - /** - * Returns the dataset with the specified index, if any. - * - * @param index the dataset index. - * - * @return The dataset (possibly {@code null}). - * - * @see #setDataset(int, XYDataset) - */ - public XYDataset getDataset(int index) { - XYDataset result = null; - if (index < this.datasets.size()) { - result = (XYDataset) this.datasets.get(index); - } - return result; - } - - /** - * Sets the primary dataset for the plot, replacing the existing dataset - * if there is one, and sends a {@code link PlotChangeEvent} to all - * registered listeners. - * - * @param dataset the dataset ({@code null} permitted). - * - * @see #getDataset() - */ - public void setDataset(XYDataset dataset) { - setDataset(0, dataset); - } - - /** - * Sets a dataset for the plot, replacing the existing dataset at the same - * index if there is one, and sends a {@code link PlotChangeEvent} to all - * registered listeners. - * - * @param index the dataset index. - * @param dataset the dataset ({@code null} permitted). - * - * @see #getDataset(int) - */ - public void setDataset(int index, XYDataset dataset) { - XYDataset existing = getDataset(index); - if (existing != null) { - existing.removeChangeListener(this); - } - this.datasets.set(index, dataset); - if (dataset != null) { - dataset.addChangeListener(this); - } - - // send a dataset change event to self... - DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); - datasetChanged(event); - } - - /** - * Returns the number of datasets. - * - * @return The number of datasets. - */ - public int getDatasetCount() { - return this.datasets.size(); - } - - /** - * Returns the index of the specified dataset, or {@code -1} if the - * dataset does not belong to the plot. - * - * @param dataset the dataset ({@code null} not permitted). - * - * @return The index. - */ - public int indexOf(XYDataset dataset) { - int result = -1; - for (int i = 0; i < this.datasets.size(); i++) { - if (dataset == this.datasets.get(i)) { - result = i; - break; - } - } - return result; - } - - /** - * Returns the primary renderer. - * - * @return The renderer (possibly {@code null}). - * - * @see #setRenderer(PolarItemRenderer) - */ - public PolarItemRenderer getRenderer() { - return getRenderer(0); - } - - /** - * Returns the renderer at the specified index, if there is one. - * - * @param index the renderer index. - * - * @return The renderer (possibly {@code null}). - * - * @see #setRenderer(int, PolarItemRenderer) - */ - public PolarItemRenderer getRenderer(int index) { - PolarItemRenderer result = null; - if (index < this.renderers.size()) { - result = (PolarItemRenderer) this.renderers.get(index); - } - return result; - } - - /** - * Sets the primary renderer, and notifies all listeners of a change to the - * plot. If the renderer is set to {@code null}, no data items will - * be drawn for the corresponding dataset. - * - * @param renderer the new renderer ({@code null} permitted). - * - * @see #getRenderer() - */ - public void setRenderer(PolarItemRenderer renderer) { - setRenderer(0, renderer); - } - - /** - * Sets a renderer and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param index the index. - * @param renderer the renderer. - * - * @see #getRenderer(int) - */ - public void setRenderer(int index, PolarItemRenderer renderer) { - setRenderer(index, renderer, true); - } - - /** - * Sets a renderer and, if requested, sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param index the index. - * @param renderer the renderer. - * @param notify notify listeners? - * - * @see #getRenderer(int) - */ - public void setRenderer(int index, PolarItemRenderer renderer, - boolean notify) { - PolarItemRenderer existing = getRenderer(index); - if (existing != null) { - existing.removeChangeListener(this); - } - this.renderers.set(index, renderer); - if (renderer != null) { - renderer.setPlot(this); - renderer.addChangeListener(this); - } - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the tick unit that controls the spacing of the angular grid - * lines. - * - * @return The tick unit (never {@code null}). - */ - public TickUnit getAngleTickUnit() { - return this.angleTickUnit; - } - - /** - * Sets the tick unit that controls the spacing of the angular grid - * lines, and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param unit the tick unit ({@code null} not permitted). - */ - public void setAngleTickUnit(TickUnit unit) { - Args.nullNotPermitted(unit, "unit"); - this.angleTickUnit = unit; - fireChangeEvent(); - } - - /** - * Returns the offset that is used for all angles. - * - * @return The offset for the angles. - */ - public double getAngleOffset() { - return this.angleOffset; - } - - /** - * Sets the offset that is used for all angles and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * This is useful to let 0 degrees be at the north, east, south or west - * side of the chart. - * - * @param offset The offset - */ - public void setAngleOffset(double offset) { - this.angleOffset = offset; - fireChangeEvent(); - } - - /** - * Get the direction for growing angle degrees. - * - * @return {@code true} if angle increases counterclockwise, - * {@code false} otherwise. - */ - public boolean isCounterClockwise() { - return this.counterClockwise; - } - - /** - * Sets the flag for increasing angle degrees direction. - * - * {@code true} for counterclockwise, {@code false} for - * clockwise. - * - * @param counterClockwise The flag. - */ - public void setCounterClockwise(boolean counterClockwise) - { - this.counterClockwise = counterClockwise; - } - - /** - * Returns a flag that controls whether or not the angle labels are visible. - * - * @return A boolean. - * - * @see #setAngleLabelsVisible(boolean) - */ - public boolean isAngleLabelsVisible() { - return this.angleLabelsVisible; - } - - /** - * Sets the flag that controls whether or not the angle labels are visible, - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param visible the flag. - * - * @see #isAngleLabelsVisible() - */ - public void setAngleLabelsVisible(boolean visible) { - if (this.angleLabelsVisible != visible) { - this.angleLabelsVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the font used to display the angle labels. - * - * @return A font (never {@code null}). - * - * @see #setAngleLabelFont(Font) - */ - public Font getAngleLabelFont() { - return this.angleLabelFont; - } - - /** - * Sets the font used to display the angle labels and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param font the font ({@code null} not permitted). - * - * @see #getAngleLabelFont() - */ - public void setAngleLabelFont(Font font) { - Args.nullNotPermitted(font, "font"); - this.angleLabelFont = font; - fireChangeEvent(); - } - - /** - * Returns the paint used to display the angle labels. - * - * @return A paint (never {@code null}). - * - * @see #setAngleLabelPaint(Paint) - */ - public Paint getAngleLabelPaint() { - return this.angleLabelPaint; - } - - /** - * Sets the paint used to display the angle labels and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - */ - public void setAngleLabelPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.angleLabelPaint = paint; - fireChangeEvent(); - } - - /** - * Returns {@code true} if the angular gridlines are visible, and - * {@code false} otherwise. - * - * @return {@code true} or {@code false}. - * - * @see #setAngleGridlinesVisible(boolean) - */ - public boolean isAngleGridlinesVisible() { - return this.angleGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not the angular grid-lines are - * visible. - *

- * If the flag value is changed, a {@link PlotChangeEvent} is sent to all - * registered listeners. - * - * @param visible the new value of the flag. - * - * @see #isAngleGridlinesVisible() - */ - public void setAngleGridlinesVisible(boolean visible) { - if (this.angleGridlinesVisible != visible) { - this.angleGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the stroke for the grid-lines (if any) plotted against the - * angular axis. - * - * @return The stroke (possibly {@code null}). - * - * @see #setAngleGridlineStroke(Stroke) - */ - public Stroke getAngleGridlineStroke() { - return this.angleGridlineStroke; - } - - /** - * Sets the stroke for the grid lines plotted against the angular axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - *

- * If you set this to {@code null}, no grid lines will be drawn. - * - * @param stroke the stroke ({@code null} permitted). - * - * @see #getAngleGridlineStroke() - */ - public void setAngleGridlineStroke(Stroke stroke) { - this.angleGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the grid lines (if any) plotted against the - * angular axis. - * - * @return The paint (possibly {@code null}). - * - * @see #setAngleGridlinePaint(Paint) - */ - public Paint getAngleGridlinePaint() { - return this.angleGridlinePaint; - } - - /** - * Sets the paint for the grid lines plotted against the angular axis. - *

- * If you set this to {@code null}, no grid lines will be drawn. - * - * @param paint the paint ({@code null} permitted). - * - * @see #getAngleGridlinePaint() - */ - public void setAngleGridlinePaint(Paint paint) { - this.angleGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns {@code true} if the radius axis grid is visible, and - * {@code false} otherwise. - * - * @return {@code true} or {@code false}. - * - * @see #setRadiusGridlinesVisible(boolean) - */ - public boolean isRadiusGridlinesVisible() { - return this.radiusGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not the radius axis grid lines - * are visible. - *

- * If the flag value is changed, a {@link PlotChangeEvent} is sent to all - * registered listeners. - * - * @param visible the new value of the flag. - * - * @see #isRadiusGridlinesVisible() - */ - public void setRadiusGridlinesVisible(boolean visible) { - if (this.radiusGridlinesVisible != visible) { - this.radiusGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the stroke for the grid lines (if any) plotted against the - * radius axis. - * - * @return The stroke (possibly {@code null}). - * - * @see #setRadiusGridlineStroke(Stroke) - */ - public Stroke getRadiusGridlineStroke() { - return this.radiusGridlineStroke; - } - - /** - * Sets the stroke for the grid lines plotted against the radius axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - *

- * If you set this to {@code null}, no grid lines will be drawn. - * - * @param stroke the stroke ({@code null} permitted). - * - * @see #getRadiusGridlineStroke() - */ - public void setRadiusGridlineStroke(Stroke stroke) { - this.radiusGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the grid lines (if any) plotted against the radius - * axis. - * - * @return The paint (possibly {@code null}). - * - * @see #setRadiusGridlinePaint(Paint) - */ - public Paint getRadiusGridlinePaint() { - return this.radiusGridlinePaint; - } - - /** - * Sets the paint for the grid lines plotted against the radius axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - *

- * If you set this to {@code null}, no grid lines will be drawn. - * - * @param paint the paint ({@code null} permitted). - * - * @see #getRadiusGridlinePaint() - */ - public void setRadiusGridlinePaint(Paint paint) { - this.radiusGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Return the current value of the flag indicating if radial minor - * grid-lines will be drawn or not. - * - * @return Returns {@code true} if radial minor grid-lines are drawn. - */ - public boolean isRadiusMinorGridlinesVisible() { - return this.radiusMinorGridlinesVisible; - } - - /** - * Set the flag that determines if radial minor grid-lines will be drawn, - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param flag {@code true} to draw the radial minor grid-lines, - * {@code false} to hide them. - */ - public void setRadiusMinorGridlinesVisible(boolean flag) { - this.radiusMinorGridlinesVisible = flag; - fireChangeEvent(); - } - - /** - * Returns the margin around the plot area. - * - * @return The actual margin in pixels. - */ - public int getMargin() { - return this.margin; - } - - /** - * Set the margin around the plot area and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param margin The new margin in pixels. - */ - public void setMargin(int margin) { - this.margin = margin; - fireChangeEvent(); - } - - /** - * Returns the fixed legend items, if any. - * - * @return The legend items (possibly {@code null}). - * - * @see #setFixedLegendItems(LegendItemCollection) - */ - public LegendItemCollection getFixedLegendItems() { - return this.fixedLegendItems; - } - - /** - * Sets the fixed legend items for the plot. Leave this set to - * {@code null} if you prefer the legend items to be created - * automatically. - * - * @param items the legend items ({@code null} permitted). - * - * @see #getFixedLegendItems() - */ - public void setFixedLegendItems(LegendItemCollection items) { - this.fixedLegendItems = items; - fireChangeEvent(); - } - - /** - * Add text to be displayed in the lower right hand corner and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param text the text to display ({@code null} not permitted). - * - * @see #removeCornerTextItem(String) - */ - public void addCornerTextItem(String text) { - Args.nullNotPermitted(text, "text"); - this.cornerTextItems.add(text); - fireChangeEvent(); - } - - /** - * Remove the given text from the list of corner text items and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param text the text to remove ({@code null} ignored). - * - * @see #addCornerTextItem(String) - */ - public void removeCornerTextItem(String text) { - boolean removed = this.cornerTextItems.remove(text); - if (removed) { - fireChangeEvent(); - } - } - - /** - * Clear the list of corner text items and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @see #addCornerTextItem(String) - * @see #removeCornerTextItem(String) - */ - public void clearCornerTextItems() { - if (this.cornerTextItems.size() > 0) { - this.cornerTextItems.clear(); - fireChangeEvent(); - } - } - - /** - * Generates a list of tick values for the angular tick marks. - * - * @return A list of {@link NumberTick} instances. - */ - protected List refreshAngleTicks() { - List ticks = new ArrayList(); - for (double currentTickVal = 0.0; currentTickVal < 360.0; - currentTickVal += this.angleTickUnit.getSize()) { - - TextAnchor ta = calculateTextAnchor(currentTickVal); - NumberTick tick = new NumberTick(currentTickVal, - this.angleTickUnit.valueToString(currentTickVal), - ta, TextAnchor.CENTER, 0.0); - ticks.add(tick); - } - return ticks; - } - - /** - * Calculate the text position for the given degrees. - * - * @param angleDegrees the angle in degrees. - * - * @return The optimal text anchor. - */ - protected TextAnchor calculateTextAnchor(double angleDegrees) { - TextAnchor ta = TextAnchor.CENTER; - - // normalize angle - double offset = this.angleOffset; - while (offset < 0.0) { - offset += 360.0; - } - double normalizedAngle = (((this.counterClockwise ? -1 : 1) - * angleDegrees) + offset) % 360; - while (this.counterClockwise && (normalizedAngle < 0.0)) { - normalizedAngle += 360.0; - } - - if (normalizedAngle == 0.0) { - ta = TextAnchor.CENTER_LEFT; - } - else if (normalizedAngle > 0.0 && normalizedAngle < 90.0) { - ta = TextAnchor.TOP_LEFT; - } - else if (normalizedAngle == 90.0) { - ta = TextAnchor.TOP_CENTER; - } - else if (normalizedAngle > 90.0 && normalizedAngle < 180.0) { - ta = TextAnchor.TOP_RIGHT; - } - else if (normalizedAngle == 180) { - ta = TextAnchor.CENTER_RIGHT; - } - else if (normalizedAngle > 180.0 && normalizedAngle < 270.0) { - ta = TextAnchor.BOTTOM_RIGHT; - } - else if (normalizedAngle == 270) { - ta = TextAnchor.BOTTOM_CENTER; - } - else if (normalizedAngle > 270.0 && normalizedAngle < 360.0) { - ta = TextAnchor.BOTTOM_LEFT; - } - return ta; - } - - /** - * Maps a dataset to a particular axis. All data will be plotted - * against axis zero by default, no mapping is required for this case. - * - * @param index the dataset index (zero-based). - * @param axisIndex the axis index. - */ - public void mapDatasetToAxis(int index, int axisIndex) { - List axisIndices = new ArrayList<>(1); - axisIndices.add(axisIndex); - mapDatasetToAxes(index, axisIndices); - } - - /** - * Maps the specified dataset to the axes in the list. Note that the - * conversion of data values into Java2D space is always performed using - * the first axis in the list. - * - * @param index the dataset index (zero-based). - * @param axisIndices the axis indices ({@code null} permitted). - */ - public void mapDatasetToAxes(int index, List axisIndices) { - if (index < 0) { - throw new IllegalArgumentException("Requires 'index' >= 0."); - } - checkAxisIndices(axisIndices); - Integer key = index; - this.datasetToAxesMap.put(key, new ArrayList(axisIndices)); - // fake a dataset change event to update axes... - datasetChanged(new DatasetChangeEvent(this, getDataset(index))); - } - - /** - * This method is used to perform argument checking on the list of - * axis indices passed to mapDatasetToAxes(). - * - * @param indices the list of indices ({@code null} permitted). - */ - private void checkAxisIndices(List indices) { - // axisIndices can be: - // 1. null; - // 2. non-empty, containing only Integer objects that are unique. - if (indices == null) { - return; // OK - } - int count = indices.size(); - if (count == 0) { - throw new IllegalArgumentException("Empty list not permitted."); - } - HashSet set = new HashSet(); - for (int i = 0; i < count; i++) { - Object item = indices.get(i); - if (!(item instanceof Integer)) { - throw new IllegalArgumentException( - "Indices must be Integer instances."); - } - if (set.contains(item)) { - throw new IllegalArgumentException("Indices must be unique."); - } - set.add(item); - } - } - - /** - * Returns the axis for a dataset. - * - * @param index the dataset index. - * - * @return The axis. - */ - public ValueAxis getAxisForDataset(int index) { - ValueAxis valueAxis; - List axisIndices = (List) this.datasetToAxesMap.get(index); - if (axisIndices != null) { - // the first axis in the list is used for data <--> Java2D - Integer axisIndex = (Integer) axisIndices.get(0); - valueAxis = getAxis(axisIndex); - } - else { - valueAxis = getAxis(0); - } - return valueAxis; - } - - /** - * Returns the index of the given axis. - * - * @param axis the axis. - * - * @return The axis index or -1 if axis is not used in this plot. - */ - public int getAxisIndex(ValueAxis axis) { - int result = this.axes.indexOf(axis); - if (result < 0) { - // try the parent plot - Plot parent = getParent(); - if (parent instanceof PolarPlot) { - PolarPlot p = (PolarPlot) parent; - result = p.getAxisIndex(axis); - } - } - return result; - } - - /** - * Returns the index of the specified renderer, or {@code -1} if the - * renderer is not assigned to this plot. - * - * @param renderer the renderer ({@code null} permitted). - * - * @return The renderer index. - */ - public int getIndexOf(PolarItemRenderer renderer) { - return this.renderers.indexOf(renderer); - } - - /** - * Draws the plot on a Java 2D graphics device (such as the screen or a - * printer). - *

- * This plot relies on a {@link PolarItemRenderer} to draw each - * item in the plot. This allows the visual representation of the data to - * be changed easily. - *

- * The optional info argument collects information about the rendering of - * the plot (dimensions, tooltip information etc). Just pass in - * {@code null} if you do not need this information. - * - * @param g2 the graphics device. - * @param area the area within which the plot (including axes and - * labels) should be drawn. - * @param anchor the anchor point ({@code null} permitted). - * @param parentState ignored. - * @param info collects chart drawing information ({@code null} - * permitted). - */ - @Override - public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, - PlotState parentState, PlotRenderingInfo info) { - - // if the plot area is too small, just return... - boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); - boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); - if (b1 || b2) { - return; - } - - // record the plot area... - if (info != null) { - info.setPlotArea(area); - } - - // adjust the drawing area for the plot insets (if any)... - RectangleInsets insets = getInsets(); - insets.trim(area); - - Rectangle2D dataArea = area; - if (info != null) { - info.setDataArea(dataArea); - } - - // draw the plot background and axes... - drawBackground(g2, dataArea); - int axisCount = this.axes.size(); - AxisState state = null; - for (int i = 0; i < axisCount; i++) { - ValueAxis axis = getAxis(i); - if (axis != null) { - PolarAxisLocation location - = (PolarAxisLocation) this.axisLocations.get(i); - AxisState s = this.drawAxis(axis, location, g2, dataArea); - if (i == 0) { - state = s; - } - } - } - - // now for each dataset, get the renderer and the appropriate axis - // and render the dataset... - Shape originalClip = g2.getClip(); - Composite originalComposite = g2.getComposite(); - - g2.clip(dataArea); - g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - getForegroundAlpha())); - this.angleTicks = refreshAngleTicks(); - drawGridlines(g2, dataArea, this.angleTicks, state.getTicks()); - render(g2, dataArea, info); - g2.setClip(originalClip); - g2.setComposite(originalComposite); - drawOutline(g2, dataArea); - drawCornerTextItems(g2, dataArea); - } - - /** - * Draws the corner text items. - * - * @param g2 the drawing surface. - * @param area the area. - */ - protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) { - if (this.cornerTextItems.isEmpty()) { - return; - } - - g2.setColor(Color.BLACK); - double width = 0.0; - double height = 0.0; - for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) { - String msg = (String) it.next(); - FontMetrics fm = g2.getFontMetrics(); - Rectangle2D bounds = TextUtils.getTextBounds(msg, g2, fm); - width = Math.max(width, bounds.getWidth()); - height += bounds.getHeight(); - } - - double xadj = ANNOTATION_MARGIN * 2.0; - double yadj = ANNOTATION_MARGIN; - width += xadj; - height += yadj; - - double x = area.getMaxX() - width; - double y = area.getMaxY() - height; - g2.drawRect((int) x, (int) y, (int) width, (int) height); - x += ANNOTATION_MARGIN; - for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) { - String msg = (String) it.next(); - Rectangle2D bounds = TextUtils.getTextBounds(msg, g2, - g2.getFontMetrics()); - y += bounds.getHeight(); - g2.drawString(msg, (int) x, (int) y); - } - } - - /** - * Draws the axis with the specified index. - * - * @param axis the axis. - * @param location the axis location. - * @param g2 the graphics target. - * @param plotArea the plot area. - * - * @return The axis state. - */ - protected AxisState drawAxis(ValueAxis axis, PolarAxisLocation location, - Graphics2D g2, Rectangle2D plotArea) { - - double centerX = plotArea.getCenterX(); - double centerY = plotArea.getCenterY(); - double r = Math.min(plotArea.getWidth() / 2.0, - plotArea.getHeight() / 2.0) - this.margin; - double x = centerX - r; - double y = centerY - r; - - Rectangle2D dataArea; - AxisState result = null; - if (location == PolarAxisLocation.NORTH_RIGHT) { - dataArea = new Rectangle2D.Double(x, y, r, r); - result = axis.draw(g2, centerX, plotArea, dataArea, - RectangleEdge.RIGHT, null); - } - else if (location == PolarAxisLocation.NORTH_LEFT) { - dataArea = new Rectangle2D.Double(centerX, y, r, r); - result = axis.draw(g2, centerX, plotArea, dataArea, - RectangleEdge.LEFT, null); - } - else if (location == PolarAxisLocation.SOUTH_LEFT) { - dataArea = new Rectangle2D.Double(centerX, centerY, r, r); - result = axis.draw(g2, centerX, plotArea, dataArea, - RectangleEdge.LEFT, null); - } - else if (location == PolarAxisLocation.SOUTH_RIGHT) { - dataArea = new Rectangle2D.Double(x, centerY, r, r); - result = axis.draw(g2, centerX, plotArea, dataArea, - RectangleEdge.RIGHT, null); - } - else if (location == PolarAxisLocation.EAST_ABOVE) { - dataArea = new Rectangle2D.Double(centerX, centerY, r, r); - result = axis.draw(g2, centerY, plotArea, dataArea, - RectangleEdge.TOP, null); - } - else if (location == PolarAxisLocation.EAST_BELOW) { - dataArea = new Rectangle2D.Double(centerX, y, r, r); - result = axis.draw(g2, centerY, plotArea, dataArea, - RectangleEdge.BOTTOM, null); - } - else if (location == PolarAxisLocation.WEST_ABOVE) { - dataArea = new Rectangle2D.Double(x, centerY, r, r); - result = axis.draw(g2, centerY, plotArea, dataArea, - RectangleEdge.TOP, null); - } - else if (location == PolarAxisLocation.WEST_BELOW) { - dataArea = new Rectangle2D.Double(x, y, r, r); - result = axis.draw(g2, centerY, plotArea, dataArea, - RectangleEdge.BOTTOM, null); - } - - return result; - } - - /** - * Draws a representation of the data within the dataArea region, using the - * current m_Renderer. - * - * @param g2 the graphics device. - * @param dataArea the region in which the data is to be drawn. - * @param info an optional object for collection dimension - * information ({@code null} permitted). - */ - protected void render(Graphics2D g2, Rectangle2D dataArea, - PlotRenderingInfo info) { - - // now get the data and plot it (the visual representation will depend - // on the m_Renderer that has been set)... - boolean hasData = false; - int datasetCount = this.datasets.size(); - for (int i = datasetCount - 1; i >= 0; i--) { - XYDataset dataset = getDataset(i); - if (dataset == null) { - continue; - } - PolarItemRenderer renderer = getRenderer(i); - if (renderer == null) { - continue; - } - if (!DatasetUtils.isEmptyOrNull(dataset)) { - hasData = true; - int seriesCount = dataset.getSeriesCount(); - for (int series = 0; series < seriesCount; series++) { - renderer.drawSeries(g2, dataArea, info, this, dataset, - series); - } - } - } - if (!hasData) { - drawNoDataMessage(g2, dataArea); - } - } - - /** - * Draws the gridlines for the plot, if they are visible. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param angularTicks the ticks for the angular axis. - * @param radialTicks the ticks for the radial axis. - */ - protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea, - List angularTicks, List radialTicks) { - - PolarItemRenderer renderer = getRenderer(); - // no renderer, no gridlines... - if (renderer == null) { - return; - } - - // draw the domain grid lines, if any... - if (isAngleGridlinesVisible()) { - Stroke gridStroke = getAngleGridlineStroke(); - Paint gridPaint = getAngleGridlinePaint(); - if ((gridStroke != null) && (gridPaint != null)) { - renderer.drawAngularGridLines(g2, this, angularTicks, - dataArea); - } - } - - // draw the radius grid lines, if any... - if (isRadiusGridlinesVisible()) { - Stroke gridStroke = getRadiusGridlineStroke(); - Paint gridPaint = getRadiusGridlinePaint(); - if ((gridStroke != null) && (gridPaint != null)) { - List ticks = buildRadialTicks(radialTicks); - renderer.drawRadialGridLines(g2, this, getAxis(), - ticks, dataArea); - } - } - } - - /** - * Create a list of ticks based on the given list and plot properties. - * Only ticks of a specific type may be in the result list. - * - * @param allTicks A list of all available ticks for the primary axis. - * {@code null} not permitted. - * @return Ticks to use for radial gridlines. - */ - protected List buildRadialTicks(List allTicks) - { - List ticks = new ArrayList(); - Iterator it = allTicks.iterator(); - while (it.hasNext()) { - ValueTick tick = (ValueTick) it.next(); - if (isRadiusMinorGridlinesVisible() || - TickType.MAJOR.equals(tick.getTickType())) { - ticks.add(tick); - } - } - return ticks; - } - - /** - * Zooms the axis ranges by the specified percentage about the anchor point. - * - * @param percent the amount of the zoom. - */ - @Override - public void zoom(double percent) { - for (int axisIdx = 0; axisIdx < getAxisCount(); axisIdx++) { - final ValueAxis axis = getAxis(axisIdx); - if (axis != null) { - if (percent > 0.0) { - double radius = axis.getUpperBound(); - double scaledRadius = radius * percent; - axis.setUpperBound(scaledRadius); - axis.setAutoRange(false); - } - else { - axis.setAutoRange(true); - } - } - } - } - - /** - * A utility method that returns a list of datasets that are mapped to a - * particular axis. - * - * @param axisIndex the axis index ({@code null} not permitted). - * - * @return A list of datasets. - */ - private List getDatasetsMappedToAxis(Integer axisIndex) { - Args.nullNotPermitted(axisIndex, "axisIndex"); - List result = new ArrayList(); - for (int i = 0; i < this.datasets.size(); i++) { - List mappedAxes = (List) this.datasetToAxesMap.get(i); - if (mappedAxes == null) { - if (axisIndex.equals(ZERO)) { - result.add(this.datasets.get(i)); - } - } - else { - if (mappedAxes.contains(axisIndex)) { - result.add(this.datasets.get(i)); - } - } - } - return result; - } - - /** - * Returns the range for the specified axis. - * - * @param axis the axis. - * - * @return The range. - */ - @Override - public Range getDataRange(ValueAxis axis) { - Range result = null; - int axisIdx = getAxisIndex(axis); - List mappedDatasets = new ArrayList(); - - if (axisIdx >= 0) { - mappedDatasets = getDatasetsMappedToAxis(axisIdx); - } - - // iterate through the datasets that map to the axis and get the union - // of the ranges. - Iterator iterator = mappedDatasets.iterator(); - int datasetIdx = -1; - while (iterator.hasNext()) { - datasetIdx++; - XYDataset d = (XYDataset) iterator.next(); - if (d != null) { - // FIXME better ask the renderer instead of DatasetUtilities - result = Range.combine(result, - DatasetUtils.findRangeBounds(d)); - } - } - - return result; - } - - /** - * Receives notification of a change to the plot's m_Dataset. - *

- * The axis ranges are updated if necessary. - * - * @param event information about the event (not used here). - */ - @Override - public void datasetChanged(DatasetChangeEvent event) { - for (int i = 0; i < this.axes.size(); i++) { - final ValueAxis axis = (ValueAxis) this.axes.get(i); - if (axis != null) { - axis.configure(); - } - } - if (getParent() != null) { - getParent().datasetChanged(event); - } - else { - super.datasetChanged(event); - } - } - - /** - * Notifies all registered listeners of a property change. - *

- * One source of property change events is the plot's m_Renderer. - * - * @param event information about the property change. - */ - @Override - public void rendererChanged(RendererChangeEvent event) { - fireChangeEvent(); - } - - /** - * Returns the legend items for the plot. Each legend item is generated by - * the plot's m_Renderer, since the m_Renderer is responsible for the visual - * representation of the data. - * - * @return The legend items. - */ - @Override - public LegendItemCollection getLegendItems() { - if (this.fixedLegendItems != null) { - return this.fixedLegendItems; - } - LegendItemCollection result = new LegendItemCollection(); - int count = this.datasets.size(); - for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) { - XYDataset dataset = getDataset(datasetIndex); - PolarItemRenderer renderer = getRenderer(datasetIndex); - if (dataset != null && renderer != null) { - int seriesCount = dataset.getSeriesCount(); - for (int i = 0; i < seriesCount; i++) { - LegendItem item = renderer.getLegendItem(i); - result.add(item); - } - } - } - return result; - } - - /** - * Tests this plot for equality with another object. - * - * @param obj the object ({@code null} permitted). - * - * @return {@code true} or {@code false}. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof PolarPlot)) { - return false; - } - PolarPlot that = (PolarPlot) obj; - if (!this.axes.equals(that.axes)) { - return false; - } - if (!this.axisLocations.equals(that.axisLocations)) { - return false; - } - if (!this.renderers.equals(that.renderers)) { - return false; - } - if (!this.angleTickUnit.equals(that.angleTickUnit)) { - return false; - } - if (this.angleGridlinesVisible != that.angleGridlinesVisible) { - return false; - } - if (this.angleOffset != that.angleOffset) - { - return false; - } - if (this.counterClockwise != that.counterClockwise) - { - return false; - } - if (this.angleLabelsVisible != that.angleLabelsVisible) { - return false; - } - if (!this.angleLabelFont.equals(that.angleLabelFont)) { - return false; - } - if (!PaintUtils.equal(this.angleLabelPaint, that.angleLabelPaint)) { - return false; - } - if (!Objects.equals(this.angleGridlineStroke, - that.angleGridlineStroke)) { - return false; - } - if (!PaintUtils.equal( - this.angleGridlinePaint, that.angleGridlinePaint - )) { - return false; - } - if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) { - return false; - } - if (!Objects.equals(this.radiusGridlineStroke, - that.radiusGridlineStroke)) { - return false; - } - if (!PaintUtils.equal(this.radiusGridlinePaint, - that.radiusGridlinePaint)) { - return false; - } - if (this.radiusMinorGridlinesVisible != - that.radiusMinorGridlinesVisible) { - return false; - } - if (!this.cornerTextItems.equals(that.cornerTextItems)) { - return false; - } - if (this.margin != that.margin) { - return false; - } - if (!Objects.equals(this.fixedLegendItems, - that.fixedLegendItems)) { - return false; - } - return super.equals(obj); - } - - /** - * Returns a clone of the plot. - * - * @return A clone. - * - * @throws CloneNotSupportedException this can occur if some component of - * the plot cannot be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - PolarPlot clone = (PolarPlot) super.clone(); - clone.axes = (ObjectList) ObjectUtils.clone(this.axes); - for (int i = 0; i < this.axes.size(); i++) { - ValueAxis axis = (ValueAxis) this.axes.get(i); - if (axis != null) { - ValueAxis clonedAxis = (ValueAxis) axis.clone(); - clone.axes.set(i, clonedAxis); - clonedAxis.setPlot(clone); - clonedAxis.addChangeListener(clone); - } - } - - // the datasets are not cloned, but listeners need to be added... - clone.datasets = (ObjectList) ObjectUtils.clone(this.datasets); - for (int i = 0; i < clone.datasets.size(); ++i) { - XYDataset d = getDataset(i); - if (d != null) { - d.addChangeListener(clone); - } - } - - clone.renderers = (ObjectList) ObjectUtils.clone(this.renderers); - for (int i = 0; i < this.renderers.size(); i++) { - PolarItemRenderer renderer2 = (PolarItemRenderer) this.renderers.get(i); - if (renderer2 instanceof PublicCloneable) { - PublicCloneable pc = (PublicCloneable) renderer2; - PolarItemRenderer rc = (PolarItemRenderer) pc.clone(); - clone.renderers.set(i, rc); - rc.setPlot(clone); - rc.addChangeListener(clone); - } - } - - clone.cornerTextItems = new ArrayList(this.cornerTextItems); - - return clone; - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writeStroke(this.angleGridlineStroke, stream); - SerialUtils.writePaint(this.angleGridlinePaint, stream); - SerialUtils.writeStroke(this.radiusGridlineStroke, stream); - SerialUtils.writePaint(this.radiusGridlinePaint, stream); - SerialUtils.writePaint(this.angleLabelPaint, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - - stream.defaultReadObject(); - this.angleGridlineStroke = SerialUtils.readStroke(stream); - this.angleGridlinePaint = SerialUtils.readPaint(stream); - this.radiusGridlineStroke = SerialUtils.readStroke(stream); - this.radiusGridlinePaint = SerialUtils.readPaint(stream); - this.angleLabelPaint = SerialUtils.readPaint(stream); - - int rangeAxisCount = this.axes.size(); - for (int i = 0; i < rangeAxisCount; i++) { - Axis axis = (Axis) this.axes.get(i); - if (axis != null) { - axis.setPlot(this); - axis.addChangeListener(this); - } - } - int datasetCount = this.datasets.size(); - for (int i = 0; i < datasetCount; i++) { - Dataset dataset = (Dataset) this.datasets.get(i); - if (dataset != null) { - dataset.addChangeListener(this); - } - } - int rendererCount = this.renderers.size(); - for (int i = 0; i < rendererCount; i++) { - PolarItemRenderer renderer = (PolarItemRenderer) this.renderers.get(i); - if (renderer != null) { - renderer.addChangeListener(this); - } - } - } - - /** - * This method is required by the {@link Zoomable} interface, but since - * the plot does not have any domain axes, it does nothing. - * - * @param factor the zoom factor. - * @param state the plot state. - * @param source the source point (in Java2D coordinates). - */ - @Override - public void zoomDomainAxes(double factor, PlotRenderingInfo state, - Point2D source) { - // do nothing - } - - /** - * This method is required by the {@link Zoomable} interface, but since - * the plot does not have any domain axes, it does nothing. - * - * @param factor the zoom factor. - * @param state the plot state. - * @param source the source point (in Java2D coordinates). - * @param useAnchor use source point as zoom anchor? - */ - @Override - public void zoomDomainAxes(double factor, PlotRenderingInfo state, - Point2D source, boolean useAnchor) { - // do nothing - } - - /** - * This method is required by the {@link Zoomable} interface, but since - * the plot does not have any domain axes, it does nothing. - * - * @param lowerPercent the new lower bound. - * @param upperPercent the new upper bound. - * @param state the plot state. - * @param source the source point (in Java2D coordinates). - */ - @Override - public void zoomDomainAxes(double lowerPercent, double upperPercent, - PlotRenderingInfo state, Point2D source) { - // do nothing - } - - /** - * Multiplies the range on the range axis/axes by the specified factor. - * - * @param factor the zoom factor. - * @param state the plot state. - * @param source the source point (in Java2D coordinates). - */ - @Override - public void zoomRangeAxes(double factor, PlotRenderingInfo state, - Point2D source) { - zoom(factor); - } - - /** - * Multiplies the range on the range axis by the specified factor. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point (in Java2D space). - * @param useAnchor use source point as zoom anchor? - * - * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) - */ - @Override - public void zoomRangeAxes(double factor, PlotRenderingInfo info, - Point2D source, boolean useAnchor) { - // get the source coordinate - this plot has always a VERTICAL - // orientation - final double sourceX = source.getX(); - - for (int axisIdx = 0; axisIdx < getAxisCount(); axisIdx++) { - final ValueAxis axis = getAxis(axisIdx); - if (axis != null) { - if (useAnchor) { - double anchorX = axis.java2DToValue(sourceX, - info.getDataArea(), RectangleEdge.BOTTOM); - axis.resizeRange(factor, anchorX); - } - else { - axis.resizeRange(factor); - } - } - } - } - - /** - * Zooms in on the range axes. - * - * @param lowerPercent the new lower bound. - * @param upperPercent the new upper bound. - * @param state the plot state. - * @param source the source point (in Java2D coordinates). - */ - @Override - public void zoomRangeAxes(double lowerPercent, double upperPercent, - PlotRenderingInfo state, Point2D source) { - zoom((upperPercent + lowerPercent) / 2.0); - } - - /** - * Returns {@code false} always. - * - * @return {@code false} always. - */ - @Override - public boolean isDomainZoomable() { - return false; - } - - /** - * Returns {@code true} to indicate that the range axis is zoomable. - * - * @return {@code true}. - */ - @Override - public boolean isRangeZoomable() { - return true; - } - - /** - * Returns the orientation of the plot. - * - * @return The orientation. - */ - @Override - public PlotOrientation getOrientation() { - return PlotOrientation.HORIZONTAL; - } - - /** - * Translates a (theta, radius) pair into Java2D coordinates. If - * {@code radius} is less than the lower bound of the axis, then - * this method returns the centre point. - * - * @param angleDegrees the angle in degrees. - * @param radius the radius. - * @param axis the axis. - * @param dataArea the data area. - * - * @return A point in Java2D space. - */ - public Point translateToJava2D(double angleDegrees, double radius, - ValueAxis axis, Rectangle2D dataArea) { - - if (counterClockwise) { - angleDegrees = -angleDegrees; - } - double radians = Math.toRadians(angleDegrees + this.angleOffset); - - double minx = dataArea.getMinX() + this.margin; - double maxx = dataArea.getMaxX() - this.margin; - double miny = dataArea.getMinY() + this.margin; - double maxy = dataArea.getMaxY() - this.margin; - - double halfWidth = (maxx - minx) / 2.0; - double halfHeight = (maxy - miny) / 2.0; - - double midX = minx + halfWidth; - double midY = miny + halfHeight; - - double l = Math.min(halfWidth, halfHeight); - Rectangle2D quadrant = new Rectangle2D.Double(midX, midY, l, l); - - double axisMin = axis.getLowerBound(); - double adjustedRadius = Math.max(radius, axisMin); - - double length = axis.valueToJava2D(adjustedRadius, quadrant, RectangleEdge.BOTTOM) - midX; - float x = (float) (midX + Math.cos(radians) * length); - float y = (float) (midY + Math.sin(radians) * length); - - int ix = Math.round(x); - int iy = Math.round(y); - - Point p = new Point(ix, iy); - return p; - - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * -------------- + * PolarPlot.java + * -------------- + * (C) Copyright 2004-present, by Solution Engineering, Inc. and Contributors. + * + * Original Author: Daniel Bridenbecker, Solution Engineering, Inc.; + * Contributor(s): David Gilbert; + * Martin Hoeller (patches 1871902 and 2850344); + * + */ + +package org.jfree.chart.plot; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Point; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.TreeMap; + +import org.jfree.chart.LegendItem; +import org.jfree.chart.LegendItemCollection; +import org.jfree.chart.axis.Axis; +import org.jfree.chart.axis.AxisState; +import org.jfree.chart.axis.NumberTick; +import org.jfree.chart.axis.NumberTickUnit; +import org.jfree.chart.axis.TickType; +import org.jfree.chart.axis.TickUnit; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.axis.ValueTick; +import org.jfree.chart.event.RendererChangeEvent; +import org.jfree.chart.event.RendererChangeListener; +import org.jfree.chart.renderer.PolarItemRenderer; +import org.jfree.chart.text.TextUtils; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.chart.ui.TextAnchor; +import org.jfree.chart.util.ObjectList; +import org.jfree.chart.util.ObjectUtils; +import org.jfree.chart.util.PaintUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.ResourceBundleWrapper; +import org.jfree.chart.util.SerialUtils; +import org.jfree.data.Range; +import org.jfree.data.general.Dataset; +import org.jfree.data.general.DatasetChangeEvent; +import org.jfree.data.general.DatasetUtils; +import org.jfree.data.xy.XYDataset; + +/** + * Plots data that is in (theta, radius) pairs where + * theta equal to zero is due north and increases clockwise. + */ +public class PolarPlot extends Plot implements ValueAxisPlot, Zoomable, + RendererChangeListener, Cloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = 3794383185924179525L; + + /** The default margin. */ + private static final int DEFAULT_MARGIN = 20; + + /** The annotation margin. */ + private static final double ANNOTATION_MARGIN = 7.0; + + /** + * The default angle tick unit size. + */ + public static final double DEFAULT_ANGLE_TICK_UNIT_SIZE = 45.0; + + /** + * The default angle offset. + */ + public static final double DEFAULT_ANGLE_OFFSET = -90.0; + + /** The default grid line stroke. */ + public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke( + 0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 0.0f, new float[]{2.0f, 2.0f}, 0.0f); + + /** The default grid line paint. */ + public static final Paint DEFAULT_GRIDLINE_PAINT = Color.GRAY; + + /** The resourceBundle for the localization. */ + protected static ResourceBundle localizationResources + = ResourceBundleWrapper.getBundle( + "org.jfree.chart.plot.LocalizationBundle"); + + /** The angles that are marked with gridlines. */ + private List angleTicks; + + /** The range axis (used for the y-values). */ + private ObjectList axes; + + /** The axis locations. */ + private ObjectList axisLocations; + + /** Storage for the datasets. */ + private ObjectList datasets; + + /** Storage for the renderers. */ + private ObjectList renderers; + + /** + * The tick unit that controls the spacing between the angular grid lines. + */ + private TickUnit angleTickUnit; + + /** + * An offset for the angles, to start with 0 degrees at north, east, south + * or west. + */ + private double angleOffset; + + /** + * A flag indicating if the angles increase counterclockwise or clockwise. + */ + private boolean counterClockwise; + + /** A flag that controls whether or not the angle labels are visible. */ + private boolean angleLabelsVisible = true; + + /** The font used to display the angle labels - never null. */ + private Font angleLabelFont = new Font("SansSerif", Font.PLAIN, 12); + + /** The paint used to display the angle labels. */ + private transient Paint angleLabelPaint = Color.BLACK; + + /** A flag that controls whether the angular grid-lines are visible. */ + private boolean angleGridlinesVisible; + + /** The stroke used to draw the angular grid-lines. */ + private transient Stroke angleGridlineStroke; + + /** The paint used to draw the angular grid-lines. */ + private transient Paint angleGridlinePaint; + + /** A flag that controls whether the radius grid-lines are visible. */ + private boolean radiusGridlinesVisible; + + /** The stroke used to draw the radius grid-lines. */ + private transient Stroke radiusGridlineStroke; + + /** The paint used to draw the radius grid-lines. */ + private transient Paint radiusGridlinePaint; + + /** + * A flag that controls whether the radial minor grid-lines are visible. + */ + private boolean radiusMinorGridlinesVisible; + + /** The annotations for the plot. */ + private List cornerTextItems = new ArrayList(); + + /** + * The actual margin in pixels. + */ + private int margin; + + /** + * An optional collection of legend items that can be returned by the + * getLegendItems() method. + */ + private LegendItemCollection fixedLegendItems; + + /** + * Storage for the mapping between datasets/renderers and range axes. The + * keys in the map are Integer objects, corresponding to the dataset + * index. The values in the map are List objects containing Integer + * objects (corresponding to the axis indices). If the map contains no + * entry for a dataset, it is assumed to map to the primary domain axis + * (index = 0). + */ + private Map datasetToAxesMap; + + /** + * Default constructor. + */ + public PolarPlot() { + this(null, null, null); + } + + /** + * Creates a new plot. + * + * @param dataset the dataset ({@code null} permitted). + * @param radiusAxis the radius axis ({@code null} permitted). + * @param renderer the renderer ({@code null} permitted). + */ + public PolarPlot(XYDataset dataset, ValueAxis radiusAxis, + PolarItemRenderer renderer) { + + super(); + + this.datasets = new ObjectList(); + this.datasets.set(0, dataset); + if (dataset != null) { + dataset.addChangeListener(this); + } + this.angleTickUnit = new NumberTickUnit(DEFAULT_ANGLE_TICK_UNIT_SIZE); + + this.axes = new ObjectList(); + this.datasetToAxesMap = new TreeMap(); + this.axes.set(0, radiusAxis); + if (radiusAxis != null) { + radiusAxis.setPlot(this); + radiusAxis.addChangeListener(this); + } + + // define the default locations for up to 8 axes... + this.axisLocations = new ObjectList(); + this.axisLocations.set(0, PolarAxisLocation.EAST_ABOVE); + this.axisLocations.set(1, PolarAxisLocation.NORTH_LEFT); + this.axisLocations.set(2, PolarAxisLocation.WEST_BELOW); + this.axisLocations.set(3, PolarAxisLocation.SOUTH_RIGHT); + this.axisLocations.set(4, PolarAxisLocation.EAST_BELOW); + this.axisLocations.set(5, PolarAxisLocation.NORTH_RIGHT); + this.axisLocations.set(6, PolarAxisLocation.WEST_ABOVE); + this.axisLocations.set(7, PolarAxisLocation.SOUTH_LEFT); + + this.renderers = new ObjectList(); + this.renderers.set(0, renderer); + if (renderer != null) { + renderer.setPlot(this); + renderer.addChangeListener(this); + } + + this.angleOffset = DEFAULT_ANGLE_OFFSET; + this.counterClockwise = false; + this.angleGridlinesVisible = true; + this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE; + this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT; + + this.radiusGridlinesVisible = true; + this.radiusMinorGridlinesVisible = true; + this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE; + this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT; + this.margin = DEFAULT_MARGIN; + } + + /** + * Returns the plot type as a string. + * + * @return A short string describing the type of plot. + */ + @Override + public String getPlotType() { + return PolarPlot.localizationResources.getString("Polar_Plot"); + } + + /** + * Returns the primary axis for the plot. + * + * @return The primary axis (possibly {@code null}). + * + * @see #setAxis(ValueAxis) + */ + public ValueAxis getAxis() { + return getAxis(0); + } + + /** + * Returns an axis for the plot. + * + * @param index the axis index. + * + * @return The axis ({@code null} possible). + * + * @see #setAxis(int, ValueAxis) + */ + public ValueAxis getAxis(int index) { + ValueAxis result = null; + if (index < this.axes.size()) { + result = (ValueAxis) this.axes.get(index); + } + return result; + } + + /** + * Sets the primary axis for the plot and calls {@link #fireChangeEvent()}. + * + * @param axis the new primary axis ({@code null} permitted). + */ + public void setAxis(ValueAxis axis) { + setAxis(0, axis); + } + + /** + * Sets an axis for the plot and calls {@link #fireChangeEvent()}. + * + * @param index the axis index. + * @param axis the axis ({@code null} permitted). + * + * @see #getAxis(int) + */ + public void setAxis(int index, ValueAxis axis) { + setAxis(index, axis, true); + } + + /** + * Sets an axis for the plot and, if requested, calls + * {@link #fireChangeEvent()}. + * + * @param index the axis index. + * @param axis the axis ({@code null} permitted). + * @param notify notify listeners? + * + * @see #getAxis(int) + */ + public void setAxis(int index, ValueAxis axis, boolean notify) { + ValueAxis existing = getAxis(index); + if (existing != null) { + existing.removeChangeListener(this); + } + if (axis != null) { + axis.setPlot(this); + } + this.axes.set(index, axis); + if (axis != null) { + axis.configure(); + axis.addChangeListener(this); + } + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the location of the primary axis. + * + * @return The location (never {@code null}). + * + * @see #setAxisLocation(PolarAxisLocation) + */ + public PolarAxisLocation getAxisLocation() { + return getAxisLocation(0); + } + + /** + * Returns the location for an axis. + * + * @param index the axis index. + * + * @return The location (never {@code null}). + * + * @see #setAxisLocation(int, PolarAxisLocation) + */ + public PolarAxisLocation getAxisLocation(int index) { + PolarAxisLocation result = null; + if (index < this.axisLocations.size()) { + result = (PolarAxisLocation) this.axisLocations.get(index); + } + return result; + } + + /** + * Sets the location of the primary axis and calls {@link #fireChangeEvent()}. + * + * @param location the location ({@code null} not permitted). + * + * @see #getAxisLocation() + */ + public void setAxisLocation(PolarAxisLocation location) { + // delegate... + setAxisLocation(0, location, true); + } + + /** + * Sets the location of the primary axis and, if requested, calls + * {@link #fireChangeEvent()}. + * + * @param location the location ({@code null} not permitted). + * @param notify notify listeners? + * + * @see #getAxisLocation() + */ + public void setAxisLocation(PolarAxisLocation location, boolean notify) { + // delegate... + setAxisLocation(0, location, notify); + } + + /** + * Sets the location for an axis and calls {@link #fireChangeEvent()}. + * + * @param index the axis index. + * @param location the location ({@code null} not permitted). + * + * @see #getAxisLocation(int) + */ + public void setAxisLocation(int index, PolarAxisLocation location) { + // delegate... + setAxisLocation(index, location, true); + } + + /** + * Sets the axis location for an axis and, if requested, calls + * {@link #fireChangeEvent()}. + * + * @param index the axis index. + * @param location the location ({@code null} not permitted). + * @param notify notify listeners? + */ + public void setAxisLocation(int index, PolarAxisLocation location, + boolean notify) { + Args.nullNotPermitted(location, "location"); + this.axisLocations.set(index, location); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the number of domain axes. + * + * @return The axis count. + **/ + public int getAxisCount() { + return this.axes.size(); + } + + /** + * Returns the primary dataset for the plot. + * + * @return The primary dataset (possibly {@code null}). + * + * @see #setDataset(XYDataset) + */ + public XYDataset getDataset() { + return getDataset(0); + } + + /** + * Returns the dataset with the specified index, if any. + * + * @param index the dataset index. + * + * @return The dataset (possibly {@code null}). + * + * @see #setDataset(int, XYDataset) + */ + public XYDataset getDataset(int index) { + XYDataset result = null; + if (index < this.datasets.size()) { + result = (XYDataset) this.datasets.get(index); + } + return result; + } + + /** + * Sets the primary dataset for the plot, replacing the existing dataset + * if there is one, and sends a {@code link PlotChangeEvent} to all + * registered listeners. + * + * @param dataset the dataset ({@code null} permitted). + * + * @see #getDataset() + */ + public void setDataset(XYDataset dataset) { + setDataset(0, dataset); + } + + /** + * Sets a dataset for the plot, replacing the existing dataset at the same + * index if there is one, and sends a {@code link PlotChangeEvent} to all + * registered listeners. + * + * @param index the dataset index. + * @param dataset the dataset ({@code null} permitted). + * + * @see #getDataset(int) + */ + public void setDataset(int index, XYDataset dataset) { + XYDataset existing = getDataset(index); + if (existing != null) { + existing.removeChangeListener(this); + } + this.datasets.set(index, dataset); + if (dataset != null) { + dataset.addChangeListener(this); + } + + // send a dataset change event to self... + DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); + datasetChanged(event); + } + + /** + * Returns the number of datasets. + * + * @return The number of datasets. + */ + public int getDatasetCount() { + return this.datasets.size(); + } + + /** + * Returns the index of the specified dataset, or {@code -1} if the + * dataset does not belong to the plot. + * + * @param dataset the dataset ({@code null} not permitted). + * + * @return The index. + */ + public int indexOf(XYDataset dataset) { + int result = -1; + for (int i = 0; i < this.datasets.size(); i++) { + if (dataset == this.datasets.get(i)) { + result = i; + break; + } + } + return result; + } + + /** + * Returns the primary renderer. + * + * @return The renderer (possibly {@code null}). + * + * @see #setRenderer(PolarItemRenderer) + */ + public PolarItemRenderer getRenderer() { + return getRenderer(0); + } + + /** + * Returns the renderer at the specified index, if there is one. + * + * @param index the renderer index. + * + * @return The renderer (possibly {@code null}). + * + * @see #setRenderer(int, PolarItemRenderer) + */ + public PolarItemRenderer getRenderer(int index) { + PolarItemRenderer result = null; + if (index < this.renderers.size()) { + result = (PolarItemRenderer) this.renderers.get(index); + } + return result; + } + + /** + * Sets the primary renderer, and notifies all listeners of a change to the + * plot. If the renderer is set to {@code null}, no data items will + * be drawn for the corresponding dataset. + * + * @param renderer the new renderer ({@code null} permitted). + * + * @see #getRenderer() + */ + public void setRenderer(PolarItemRenderer renderer) { + setRenderer(0, renderer); + } + + /** + * Sets a renderer and calls {@link #fireChangeEvent()}. + * + * @param index the index. + * @param renderer the renderer. + * + * @see #getRenderer(int) + */ + public void setRenderer(int index, PolarItemRenderer renderer) { + setRenderer(index, renderer, true); + } + + /** + * Sets a renderer and, if requested, calls {@link #fireChangeEvent()}. + * + * @param index the index. + * @param renderer the renderer. + * @param notify notify listeners? + * + * @see #getRenderer(int) + */ + public void setRenderer(int index, PolarItemRenderer renderer, + boolean notify) { + PolarItemRenderer existing = getRenderer(index); + if (existing != null) { + existing.removeChangeListener(this); + } + this.renderers.set(index, renderer); + if (renderer != null) { + renderer.setPlot(this); + renderer.addChangeListener(this); + } + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the tick unit that controls the spacing of the angular grid + * lines. + * + * @return The tick unit (never {@code null}). + */ + public TickUnit getAngleTickUnit() { + return this.angleTickUnit; + } + + /** + * Sets the tick unit that controls the spacing of the angular grid + * lines, and calls {@link #fireChangeEvent()}. + * + * @param unit the tick unit ({@code null} not permitted). + */ + public void setAngleTickUnit(TickUnit unit) { + Args.nullNotPermitted(unit, "unit"); + this.angleTickUnit = unit; + fireChangeEvent(); + } + + /** + * Returns the offset that is used for all angles. + * + * @return The offset for the angles. + */ + public double getAngleOffset() { + return this.angleOffset; + } + + /** + * Sets the offset that is used for all angles and calls {@link #fireChangeEvent()}. + * + * This is useful to let 0 degrees be at the north, east, south or west + * side of the chart. + * + * @param offset The offset + */ + public void setAngleOffset(double offset) { + this.angleOffset = offset; + fireChangeEvent(); + } + + /** + * Get the direction for growing angle degrees. + * + * @return {@code true} if angle increases counterclockwise, + * {@code false} otherwise. + */ + public boolean isCounterClockwise() { + return this.counterClockwise; + } + + /** + * Sets the flag for increasing angle degrees direction. + * + * {@code true} for counterclockwise, {@code false} for + * clockwise. + * + * @param counterClockwise The flag. + */ + public void setCounterClockwise(boolean counterClockwise) + { + this.counterClockwise = counterClockwise; + } + + /** + * Returns a flag that controls whether or not the angle labels are visible. + * + * @return A boolean. + * + * @see #setAngleLabelsVisible(boolean) + */ + public boolean isAngleLabelsVisible() { + return this.angleLabelsVisible; + } + + /** + * Sets the flag that controls whether or not the angle labels are visible, + * and calls {@link #fireChangeEvent()}. + * + * @param visible the flag. + * + * @see #isAngleLabelsVisible() + */ + public void setAngleLabelsVisible(boolean visible) { + if (this.angleLabelsVisible != visible) { + this.angleLabelsVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the font used to display the angle labels. + * + * @return A font (never {@code null}). + * + * @see #setAngleLabelFont(Font) + */ + public Font getAngleLabelFont() { + return this.angleLabelFont; + } + + /** + * Sets the font used to display the angle labels and calls + * {@link #fireChangeEvent()}. + * + * @param font the font ({@code null} not permitted). + * + * @see #getAngleLabelFont() + */ + public void setAngleLabelFont(Font font) { + Args.nullNotPermitted(font, "font"); + this.angleLabelFont = font; + fireChangeEvent(); + } + + /** + * Returns the paint used to display the angle labels. + * + * @return A paint (never {@code null}). + * + * @see #setAngleLabelPaint(Paint) + */ + public Paint getAngleLabelPaint() { + return this.angleLabelPaint; + } + + /** + * Sets the paint used to display the angle labels and calls + * {@link #fireChangeEvent()}. + * + * @param paint the paint ({@code null} not permitted). + */ + public void setAngleLabelPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.angleLabelPaint = paint; + fireChangeEvent(); + } + + /** + * Returns {@code true} if the angular gridlines are visible, and + * {@code false} otherwise. + * + * @return {@code true} or {@code false}. + * + * @see #setAngleGridlinesVisible(boolean) + */ + public boolean isAngleGridlinesVisible() { + return this.angleGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not the angular grid-lines are + * visible. + *

+ * If the flag value is changed, {@link #fireChangeEvent()} is called. + * + * @param visible the new value of the flag. + * + * @see #isAngleGridlinesVisible() + */ + public void setAngleGridlinesVisible(boolean visible) { + if (this.angleGridlinesVisible != visible) { + this.angleGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the stroke for the grid-lines (if any) plotted against the + * angular axis. + * + * @return The stroke (possibly {@code null}). + * + * @see #setAngleGridlineStroke(Stroke) + */ + public Stroke getAngleGridlineStroke() { + return this.angleGridlineStroke; + } + + /** + * Sets the stroke for the grid lines plotted against the angular axis and + * calls {@link #fireChangeEvent()}. + *

+ * If you set this to {@code null}, no grid lines will be drawn. + * + * @param stroke the stroke ({@code null} permitted). + * + * @see #getAngleGridlineStroke() + */ + public void setAngleGridlineStroke(Stroke stroke) { + this.angleGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the grid lines (if any) plotted against the + * angular axis. + * + * @return The paint (possibly {@code null}). + * + * @see #setAngleGridlinePaint(Paint) + */ + public Paint getAngleGridlinePaint() { + return this.angleGridlinePaint; + } + + /** + * Sets the paint for the grid lines plotted against the angular axis. + *

+ * If you set this to {@code null}, no grid lines will be drawn. + * + * @param paint the paint ({@code null} permitted). + * + * @see #getAngleGridlinePaint() + */ + public void setAngleGridlinePaint(Paint paint) { + this.angleGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns {@code true} if the radius axis grid is visible, and + * {@code false} otherwise. + * + * @return {@code true} or {@code false}. + * + * @see #setRadiusGridlinesVisible(boolean) + */ + public boolean isRadiusGridlinesVisible() { + return this.radiusGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not the radius axis grid lines + * are visible. + *

+ * If the flag value is changed, {@link #fireChangeEvent()} is called. + * + * @param visible the new value of the flag. + * + * @see #isRadiusGridlinesVisible() + */ + public void setRadiusGridlinesVisible(boolean visible) { + if (this.radiusGridlinesVisible != visible) { + this.radiusGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the stroke for the grid lines (if any) plotted against the + * radius axis. + * + * @return The stroke (possibly {@code null}). + * + * @see #setRadiusGridlineStroke(Stroke) + */ + public Stroke getRadiusGridlineStroke() { + return this.radiusGridlineStroke; + } + + /** + * Sets the stroke for the grid lines plotted against the radius axis and + * calls {@link #fireChangeEvent()}. + *

+ * If you set this to {@code null}, no grid lines will be drawn. + * + * @param stroke the stroke ({@code null} permitted). + * + * @see #getRadiusGridlineStroke() + */ + public void setRadiusGridlineStroke(Stroke stroke) { + this.radiusGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the grid lines (if any) plotted against the radius + * axis. + * + * @return The paint (possibly {@code null}). + * + * @see #setRadiusGridlinePaint(Paint) + */ + public Paint getRadiusGridlinePaint() { + return this.radiusGridlinePaint; + } + + /** + * Sets the paint for the grid lines plotted against the radius axis and + * calls {@link #fireChangeEvent()}. + *

+ * If you set this to {@code null}, no grid lines will be drawn. + * + * @param paint the paint ({@code null} permitted). + * + * @see #getRadiusGridlinePaint() + */ + public void setRadiusGridlinePaint(Paint paint) { + this.radiusGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Return the current value of the flag indicating if radial minor + * grid-lines will be drawn or not. + * + * @return Returns {@code true} if radial minor grid-lines are drawn. + */ + public boolean isRadiusMinorGridlinesVisible() { + return this.radiusMinorGridlinesVisible; + } + + /** + * Set the flag that determines if radial minor grid-lines will be drawn, + * and calls {@link #fireChangeEvent()}. + * + * @param flag {@code true} to draw the radial minor grid-lines, + * {@code false} to hide them. + */ + public void setRadiusMinorGridlinesVisible(boolean flag) { + this.radiusMinorGridlinesVisible = flag; + fireChangeEvent(); + } + + /** + * Returns the margin around the plot area. + * + * @return The actual margin in pixels. + */ + public int getMargin() { + return this.margin; + } + + /** + * Set the margin around the plot area and calls {@link #fireChangeEvent()}. + * + * @param margin The new margin in pixels. + */ + public void setMargin(int margin) { + this.margin = margin; + fireChangeEvent(); + } + + /** + * Returns the fixed legend items, if any. + * + * @return The legend items (possibly {@code null}). + * + * @see #setFixedLegendItems(LegendItemCollection) + */ + public LegendItemCollection getFixedLegendItems() { + return this.fixedLegendItems; + } + + /** + * Sets the fixed legend items for the plot. Leave this set to + * {@code null} if you prefer the legend items to be created + * automatically. + * + * @param items the legend items ({@code null} permitted). + * + * @see #getFixedLegendItems() + */ + public void setFixedLegendItems(LegendItemCollection items) { + this.fixedLegendItems = items; + fireChangeEvent(); + } + + /** + * Add text to be displayed in the lower right hand corner and calls + * {@link #fireChangeEvent()}. + * + * @param text the text to display ({@code null} not permitted). + * + * @see #removeCornerTextItem(String) + */ + public void addCornerTextItem(String text) { + Args.nullNotPermitted(text, "text"); + this.cornerTextItems.add(text); + fireChangeEvent(); + } + + /** + * Remove the given text from the list of corner text items and + * calls {@link #fireChangeEvent()}. + * + * @param text the text to remove ({@code null} ignored). + * + * @see #addCornerTextItem(String) + */ + public void removeCornerTextItem(String text) { + boolean removed = this.cornerTextItems.remove(text); + if (removed) { + fireChangeEvent(); + } + } + + /** + * Clear the list of corner text items and calls {@link #fireChangeEvent()}. + * + * @see #addCornerTextItem(String) + * @see #removeCornerTextItem(String) + */ + public void clearCornerTextItems() { + if (this.cornerTextItems.size() > 0) { + this.cornerTextItems.clear(); + fireChangeEvent(); + } + } + + /** + * Generates a list of tick values for the angular tick marks. + * + * @return A list of {@link NumberTick} instances. + */ + protected List refreshAngleTicks() { + List ticks = new ArrayList(); + for (double currentTickVal = 0.0; currentTickVal < 360.0; + currentTickVal += this.angleTickUnit.getSize()) { + + TextAnchor ta = calculateTextAnchor(currentTickVal); + NumberTick tick = new NumberTick(currentTickVal, + this.angleTickUnit.valueToString(currentTickVal), + ta, TextAnchor.CENTER, 0.0); + ticks.add(tick); + } + return ticks; + } + + /** + * Calculate the text position for the given degrees. + * + * @param angleDegrees the angle in degrees. + * + * @return The optimal text anchor. + */ + protected TextAnchor calculateTextAnchor(double angleDegrees) { + TextAnchor ta = TextAnchor.CENTER; + + // normalize angle + double offset = this.angleOffset; + while (offset < 0.0) { + offset += 360.0; + } + double normalizedAngle = (((this.counterClockwise ? -1 : 1) + * angleDegrees) + offset) % 360; + while (this.counterClockwise && (normalizedAngle < 0.0)) { + normalizedAngle += 360.0; + } + + if (normalizedAngle == 0.0) { + ta = TextAnchor.CENTER_LEFT; + } + else if (normalizedAngle > 0.0 && normalizedAngle < 90.0) { + ta = TextAnchor.TOP_LEFT; + } + else if (normalizedAngle == 90.0) { + ta = TextAnchor.TOP_CENTER; + } + else if (normalizedAngle > 90.0 && normalizedAngle < 180.0) { + ta = TextAnchor.TOP_RIGHT; + } + else if (normalizedAngle == 180) { + ta = TextAnchor.CENTER_RIGHT; + } + else if (normalizedAngle > 180.0 && normalizedAngle < 270.0) { + ta = TextAnchor.BOTTOM_RIGHT; + } + else if (normalizedAngle == 270) { + ta = TextAnchor.BOTTOM_CENTER; + } + else if (normalizedAngle > 270.0 && normalizedAngle < 360.0) { + ta = TextAnchor.BOTTOM_LEFT; + } + return ta; + } + + /** + * Maps a dataset to a particular axis. All data will be plotted + * against axis zero by default, no mapping is required for this case. + * + * @param index the dataset index (zero-based). + * @param axisIndex the axis index. + */ + public void mapDatasetToAxis(int index, int axisIndex) { + List axisIndices = new ArrayList<>(1); + axisIndices.add(axisIndex); + mapDatasetToAxes(index, axisIndices); + } + + /** + * Maps the specified dataset to the axes in the list. Note that the + * conversion of data values into Java2D space is always performed using + * the first axis in the list. + * + * @param index the dataset index (zero-based). + * @param axisIndices the axis indices ({@code null} permitted). + */ + public void mapDatasetToAxes(int index, List axisIndices) { + if (index < 0) { + throw new IllegalArgumentException("Requires 'index' >= 0."); + } + checkAxisIndices(axisIndices); + Integer key = index; + this.datasetToAxesMap.put(key, new ArrayList(axisIndices)); + // fake a dataset change event to update axes... + datasetChanged(new DatasetChangeEvent(this, getDataset(index))); + } + + /** + * This method is used to perform argument checking on the list of + * axis indices passed to mapDatasetToAxes(). + * + * @param indices the list of indices ({@code null} permitted). + */ + private void checkAxisIndices(List indices) { + // axisIndices can be: + // 1. null; + // 2. non-empty, containing only Integer objects that are unique. + if (indices == null) { + return; // OK + } + int count = indices.size(); + if (count == 0) { + throw new IllegalArgumentException("Empty list not permitted."); + } + HashSet set = new HashSet(); + for (int i = 0; i < count; i++) { + Object item = indices.get(i); + if (!(item instanceof Integer)) { + throw new IllegalArgumentException( + "Indices must be Integer instances."); + } + if (set.contains(item)) { + throw new IllegalArgumentException("Indices must be unique."); + } + set.add(item); + } + } + + /** + * Returns the axis for a dataset. + * + * @param index the dataset index. + * + * @return The axis. + */ + public ValueAxis getAxisForDataset(int index) { + ValueAxis valueAxis; + List axisIndices = (List) this.datasetToAxesMap.get(index); + if (axisIndices != null) { + // the first axis in the list is used for data <--> Java2D + Integer axisIndex = (Integer) axisIndices.get(0); + valueAxis = getAxis(axisIndex); + } + else { + valueAxis = getAxis(0); + } + return valueAxis; + } + + /** + * Returns the index of the given axis. + * + * @param axis the axis. + * + * @return The axis index or -1 if axis is not used in this plot. + */ + public int getAxisIndex(ValueAxis axis) { + int result = this.axes.indexOf(axis); + if (result < 0) { + // try the parent plot + Plot parent = getParent(); + if (parent instanceof PolarPlot) { + PolarPlot p = (PolarPlot) parent; + result = p.getAxisIndex(axis); + } + } + return result; + } + + /** + * Returns the index of the specified renderer, or {@code -1} if the + * renderer is not assigned to this plot. + * + * @param renderer the renderer ({@code null} permitted). + * + * @return The renderer index. + */ + public int getIndexOf(PolarItemRenderer renderer) { + return this.renderers.indexOf(renderer); + } + + /** + * Draws the plot on a Java 2D graphics device (such as the screen or a + * printer). + *

+ * This plot relies on a {@link PolarItemRenderer} to draw each + * item in the plot. This allows the visual representation of the data to + * be changed easily. + *

+ * The optional info argument collects information about the rendering of + * the plot (dimensions, tooltip information etc). Just pass in + * {@code null} if you do not need this information. + * + * @param g2 the graphics device. + * @param area the area within which the plot (including axes and + * labels) should be drawn. + * @param anchor the anchor point ({@code null} permitted). + * @param parentState ignored. + * @param info collects chart drawing information ({@code null} + * permitted). + */ + @Override + public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, + PlotState parentState, PlotRenderingInfo info) { + + // if the plot area is too small, just return... + boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); + boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); + if (b1 || b2) { + return; + } + + // record the plot area... + if (info != null) { + info.setPlotArea(area); + } + + // adjust the drawing area for the plot insets (if any)... + RectangleInsets insets = getInsets(); + insets.trim(area); + + Rectangle2D dataArea = area; + if (info != null) { + info.setDataArea(dataArea); + } + + // draw the plot background and axes... + drawBackground(g2, dataArea); + int axisCount = this.axes.size(); + AxisState state = null; + for (int i = 0; i < axisCount; i++) { + ValueAxis axis = getAxis(i); + if (axis != null) { + PolarAxisLocation location + = (PolarAxisLocation) this.axisLocations.get(i); + AxisState s = this.drawAxis(axis, location, g2, dataArea); + if (i == 0) { + state = s; + } + } + } + + // now for each dataset, get the renderer and the appropriate axis + // and render the dataset... + Shape originalClip = g2.getClip(); + Composite originalComposite = g2.getComposite(); + + g2.clip(dataArea); + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + getForegroundAlpha())); + this.angleTicks = refreshAngleTicks(); + drawGridlines(g2, dataArea, this.angleTicks, state.getTicks()); + render(g2, dataArea, info); + g2.setClip(originalClip); + g2.setComposite(originalComposite); + drawOutline(g2, dataArea); + drawCornerTextItems(g2, dataArea); + } + + /** + * Draws the corner text items. + * + * @param g2 the drawing surface. + * @param area the area. + */ + protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) { + if (this.cornerTextItems.isEmpty()) { + return; + } + + g2.setColor(Color.BLACK); + double width = 0.0; + double height = 0.0; + for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) { + String msg = (String) it.next(); + FontMetrics fm = g2.getFontMetrics(); + Rectangle2D bounds = TextUtils.getTextBounds(msg, g2, fm); + width = Math.max(width, bounds.getWidth()); + height += bounds.getHeight(); + } + + double xadj = ANNOTATION_MARGIN * 2.0; + double yadj = ANNOTATION_MARGIN; + width += xadj; + height += yadj; + + double x = area.getMaxX() - width; + double y = area.getMaxY() - height; + g2.drawRect((int) x, (int) y, (int) width, (int) height); + x += ANNOTATION_MARGIN; + for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) { + String msg = (String) it.next(); + Rectangle2D bounds = TextUtils.getTextBounds(msg, g2, + g2.getFontMetrics()); + y += bounds.getHeight(); + g2.drawString(msg, (int) x, (int) y); + } + } + + /** + * Draws the axis with the specified index. + * + * @param axis the axis. + * @param location the axis location. + * @param g2 the graphics target. + * @param plotArea the plot area. + * + * @return The axis state. + */ + protected AxisState drawAxis(ValueAxis axis, PolarAxisLocation location, + Graphics2D g2, Rectangle2D plotArea) { + + double centerX = plotArea.getCenterX(); + double centerY = plotArea.getCenterY(); + double r = Math.min(plotArea.getWidth() / 2.0, + plotArea.getHeight() / 2.0) - this.margin; + double x = centerX - r; + double y = centerY - r; + + Rectangle2D dataArea; + AxisState result = null; + if (location == PolarAxisLocation.NORTH_RIGHT) { + dataArea = new Rectangle2D.Double(x, y, r, r); + result = axis.draw(g2, centerX, plotArea, dataArea, + RectangleEdge.RIGHT, null); + } + else if (location == PolarAxisLocation.NORTH_LEFT) { + dataArea = new Rectangle2D.Double(centerX, y, r, r); + result = axis.draw(g2, centerX, plotArea, dataArea, + RectangleEdge.LEFT, null); + } + else if (location == PolarAxisLocation.SOUTH_LEFT) { + dataArea = new Rectangle2D.Double(centerX, centerY, r, r); + result = axis.draw(g2, centerX, plotArea, dataArea, + RectangleEdge.LEFT, null); + } + else if (location == PolarAxisLocation.SOUTH_RIGHT) { + dataArea = new Rectangle2D.Double(x, centerY, r, r); + result = axis.draw(g2, centerX, plotArea, dataArea, + RectangleEdge.RIGHT, null); + } + else if (location == PolarAxisLocation.EAST_ABOVE) { + dataArea = new Rectangle2D.Double(centerX, centerY, r, r); + result = axis.draw(g2, centerY, plotArea, dataArea, + RectangleEdge.TOP, null); + } + else if (location == PolarAxisLocation.EAST_BELOW) { + dataArea = new Rectangle2D.Double(centerX, y, r, r); + result = axis.draw(g2, centerY, plotArea, dataArea, + RectangleEdge.BOTTOM, null); + } + else if (location == PolarAxisLocation.WEST_ABOVE) { + dataArea = new Rectangle2D.Double(x, centerY, r, r); + result = axis.draw(g2, centerY, plotArea, dataArea, + RectangleEdge.TOP, null); + } + else if (location == PolarAxisLocation.WEST_BELOW) { + dataArea = new Rectangle2D.Double(x, y, r, r); + result = axis.draw(g2, centerY, plotArea, dataArea, + RectangleEdge.BOTTOM, null); + } + + return result; + } + + /** + * Draws a representation of the data within the dataArea region, using the + * current m_Renderer. + * + * @param g2 the graphics device. + * @param dataArea the region in which the data is to be drawn. + * @param info an optional object for collection dimension + * information ({@code null} permitted). + */ + protected void render(Graphics2D g2, Rectangle2D dataArea, + PlotRenderingInfo info) { + + // now get the data and plot it (the visual representation will depend + // on the m_Renderer that has been set)... + boolean hasData = false; + int datasetCount = this.datasets.size(); + for (int i = datasetCount - 1; i >= 0; i--) { + XYDataset dataset = getDataset(i); + if (dataset == null) { + continue; + } + PolarItemRenderer renderer = getRenderer(i); + if (renderer == null) { + continue; + } + if (!DatasetUtils.isEmptyOrNull(dataset)) { + hasData = true; + int seriesCount = dataset.getSeriesCount(); + for (int series = 0; series < seriesCount; series++) { + renderer.drawSeries(g2, dataArea, info, this, dataset, + series); + } + } + } + if (!hasData) { + drawNoDataMessage(g2, dataArea); + } + } + + /** + * Draws the gridlines for the plot, if they are visible. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param angularTicks the ticks for the angular axis. + * @param radialTicks the ticks for the radial axis. + */ + protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea, + List angularTicks, List radialTicks) { + + PolarItemRenderer renderer = getRenderer(); + // no renderer, no gridlines... + if (renderer == null) { + return; + } + + // draw the domain grid lines, if any... + if (isAngleGridlinesVisible()) { + Stroke gridStroke = getAngleGridlineStroke(); + Paint gridPaint = getAngleGridlinePaint(); + if ((gridStroke != null) && (gridPaint != null)) { + renderer.drawAngularGridLines(g2, this, angularTicks, + dataArea); + } + } + + // draw the radius grid lines, if any... + if (isRadiusGridlinesVisible()) { + Stroke gridStroke = getRadiusGridlineStroke(); + Paint gridPaint = getRadiusGridlinePaint(); + if ((gridStroke != null) && (gridPaint != null)) { + List ticks = buildRadialTicks(radialTicks); + renderer.drawRadialGridLines(g2, this, getAxis(), + ticks, dataArea); + } + } + } + + /** + * Create a list of ticks based on the given list and plot properties. + * Only ticks of a specific type may be in the result list. + * + * @param allTicks A list of all available ticks for the primary axis. + * {@code null} not permitted. + * @return Ticks to use for radial gridlines. + */ + protected List buildRadialTicks(List allTicks) + { + List ticks = new ArrayList(); + Iterator it = allTicks.iterator(); + while (it.hasNext()) { + ValueTick tick = (ValueTick) it.next(); + if (isRadiusMinorGridlinesVisible() || + TickType.MAJOR.equals(tick.getTickType())) { + ticks.add(tick); + } + } + return ticks; + } + + /** + * Zooms the axis ranges by the specified percentage about the anchor point. + * + * @param percent the amount of the zoom. + */ + @Override + public void zoom(double percent) { + for (int axisIdx = 0; axisIdx < getAxisCount(); axisIdx++) { + final ValueAxis axis = getAxis(axisIdx); + if (axis != null) { + if (percent > 0.0) { + double radius = axis.getUpperBound(); + double scaledRadius = radius * percent; + axis.setUpperBound(scaledRadius); + axis.setAutoRange(false); + } + else { + axis.setAutoRange(true); + } + } + } + } + + /** + * A utility method that returns a list of datasets that are mapped to a + * particular axis. + * + * @param axisIndex the axis index ({@code null} not permitted). + * + * @return A list of datasets. + */ + private List getDatasetsMappedToAxis(Integer axisIndex) { + Args.nullNotPermitted(axisIndex, "axisIndex"); + List result = new ArrayList(); + for (int i = 0; i < this.datasets.size(); i++) { + List mappedAxes = (List) this.datasetToAxesMap.get(i); + if (mappedAxes == null) { + if (axisIndex.equals(ZERO)) { + result.add(this.datasets.get(i)); + } + } + else { + if (mappedAxes.contains(axisIndex)) { + result.add(this.datasets.get(i)); + } + } + } + return result; + } + + /** + * Returns the range for the specified axis. + * + * @param axis the axis. + * + * @return The range. + */ + @Override + public Range getDataRange(ValueAxis axis) { + Range result = null; + int axisIdx = getAxisIndex(axis); + List mappedDatasets = new ArrayList(); + + if (axisIdx >= 0) { + mappedDatasets = getDatasetsMappedToAxis(axisIdx); + } + + // iterate through the datasets that map to the axis and get the union + // of the ranges. + Iterator iterator = mappedDatasets.iterator(); + int datasetIdx = -1; + while (iterator.hasNext()) { + datasetIdx++; + XYDataset d = (XYDataset) iterator.next(); + if (d != null) { + // FIXME better ask the renderer instead of DatasetUtilities + result = Range.combine(result, + DatasetUtils.findRangeBounds(d)); + } + } + + return result; + } + + /** + * Receives notification of a change to the plot's m_Dataset. + *

+ * The axis ranges are updated if necessary. + * + * @param event information about the event (not used here). + */ + @Override + public void datasetChanged(DatasetChangeEvent event) { + for (int i = 0; i < this.axes.size(); i++) { + final ValueAxis axis = (ValueAxis) this.axes.get(i); + if (axis != null) { + axis.configure(); + } + } + if (getParent() != null) { + getParent().datasetChanged(event); + } + else { + super.datasetChanged(event); + } + } + + /** + * Notifies all registered listeners of a property change. + *

+ * One source of property change events is the plot's m_Renderer. + * + * @param event information about the property change. + */ + @Override + public void rendererChanged(RendererChangeEvent event) { + fireChangeEvent(); + } + + /** + * Returns the legend items for the plot. Each legend item is generated by + * the plot's m_Renderer, since the m_Renderer is responsible for the visual + * representation of the data. + * + * @return The legend items. + */ + @Override + public LegendItemCollection getLegendItems() { + if (this.fixedLegendItems != null) { + return this.fixedLegendItems; + } + LegendItemCollection result = new LegendItemCollection(); + int count = this.datasets.size(); + for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) { + XYDataset dataset = getDataset(datasetIndex); + PolarItemRenderer renderer = getRenderer(datasetIndex); + if (dataset != null && renderer != null) { + int seriesCount = dataset.getSeriesCount(); + for (int i = 0; i < seriesCount; i++) { + LegendItem item = renderer.getLegendItem(i); + result.add(item); + } + } + } + return result; + } + + /** + * Tests this plot for equality with another object. + * + * @param obj the object ({@code null} permitted). + * + * @return {@code true} or {@code false}. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof PolarPlot)) { + return false; + } + PolarPlot that = (PolarPlot) obj; + if (!this.axes.equals(that.axes)) { + return false; + } + if (!this.axisLocations.equals(that.axisLocations)) { + return false; + } + if (!this.renderers.equals(that.renderers)) { + return false; + } + if (!this.angleTickUnit.equals(that.angleTickUnit)) { + return false; + } + if (this.angleGridlinesVisible != that.angleGridlinesVisible) { + return false; + } + if (this.angleOffset != that.angleOffset) + { + return false; + } + if (this.counterClockwise != that.counterClockwise) + { + return false; + } + if (this.angleLabelsVisible != that.angleLabelsVisible) { + return false; + } + if (!this.angleLabelFont.equals(that.angleLabelFont)) { + return false; + } + if (!PaintUtils.equal(this.angleLabelPaint, that.angleLabelPaint)) { + return false; + } + if (!Objects.equals(this.angleGridlineStroke, + that.angleGridlineStroke)) { + return false; + } + if (!PaintUtils.equal( + this.angleGridlinePaint, that.angleGridlinePaint + )) { + return false; + } + if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) { + return false; + } + if (!Objects.equals(this.radiusGridlineStroke, + that.radiusGridlineStroke)) { + return false; + } + if (!PaintUtils.equal(this.radiusGridlinePaint, + that.radiusGridlinePaint)) { + return false; + } + if (this.radiusMinorGridlinesVisible != + that.radiusMinorGridlinesVisible) { + return false; + } + if (!this.cornerTextItems.equals(that.cornerTextItems)) { + return false; + } + if (this.margin != that.margin) { + return false; + } + if (!Objects.equals(this.fixedLegendItems, + that.fixedLegendItems)) { + return false; + } + return super.equals(obj); + } + + /** + * Returns a clone of the plot. + * + * @return A clone. + * + * @throws CloneNotSupportedException this can occur if some component of + * the plot cannot be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + PolarPlot clone = (PolarPlot) super.clone(); + clone.axes = (ObjectList) ObjectUtils.clone(this.axes); + for (int i = 0; i < this.axes.size(); i++) { + ValueAxis axis = (ValueAxis) this.axes.get(i); + if (axis != null) { + ValueAxis clonedAxis = (ValueAxis) axis.clone(); + clone.axes.set(i, clonedAxis); + clonedAxis.setPlot(clone); + clonedAxis.addChangeListener(clone); + } + } + + // the datasets are not cloned, but listeners need to be added... + clone.datasets = (ObjectList) ObjectUtils.clone(this.datasets); + for (int i = 0; i < clone.datasets.size(); ++i) { + XYDataset d = getDataset(i); + if (d != null) { + d.addChangeListener(clone); + } + } + + clone.renderers = (ObjectList) ObjectUtils.clone(this.renderers); + for (int i = 0; i < this.renderers.size(); i++) { + PolarItemRenderer renderer2 = (PolarItemRenderer) this.renderers.get(i); + if (renderer2 instanceof PublicCloneable) { + PublicCloneable pc = (PublicCloneable) renderer2; + PolarItemRenderer rc = (PolarItemRenderer) pc.clone(); + clone.renderers.set(i, rc); + rc.setPlot(clone); + rc.addChangeListener(clone); + } + } + + clone.cornerTextItems = new ArrayList(this.cornerTextItems); + + return clone; + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writeStroke(this.angleGridlineStroke, stream); + SerialUtils.writePaint(this.angleGridlinePaint, stream); + SerialUtils.writeStroke(this.radiusGridlineStroke, stream); + SerialUtils.writePaint(this.radiusGridlinePaint, stream); + SerialUtils.writePaint(this.angleLabelPaint, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + + stream.defaultReadObject(); + this.angleGridlineStroke = SerialUtils.readStroke(stream); + this.angleGridlinePaint = SerialUtils.readPaint(stream); + this.radiusGridlineStroke = SerialUtils.readStroke(stream); + this.radiusGridlinePaint = SerialUtils.readPaint(stream); + this.angleLabelPaint = SerialUtils.readPaint(stream); + + int rangeAxisCount = this.axes.size(); + for (int i = 0; i < rangeAxisCount; i++) { + Axis axis = (Axis) this.axes.get(i); + if (axis != null) { + axis.setPlot(this); + axis.addChangeListener(this); + } + } + int datasetCount = this.datasets.size(); + for (int i = 0; i < datasetCount; i++) { + Dataset dataset = (Dataset) this.datasets.get(i); + if (dataset != null) { + dataset.addChangeListener(this); + } + } + int rendererCount = this.renderers.size(); + for (int i = 0; i < rendererCount; i++) { + PolarItemRenderer renderer = (PolarItemRenderer) this.renderers.get(i); + if (renderer != null) { + renderer.addChangeListener(this); + } + } + } + + /** + * This method is required by the {@link Zoomable} interface, but since + * the plot does not have any domain axes, it does nothing. + * + * @param factor the zoom factor. + * @param state the plot state. + * @param source the source point (in Java2D coordinates). + */ + @Override + public void zoomDomainAxes(double factor, PlotRenderingInfo state, + Point2D source) { + // do nothing + } + + /** + * This method is required by the {@link Zoomable} interface, but since + * the plot does not have any domain axes, it does nothing. + * + * @param factor the zoom factor. + * @param state the plot state. + * @param source the source point (in Java2D coordinates). + * @param useAnchor use source point as zoom anchor? + */ + @Override + public void zoomDomainAxes(double factor, PlotRenderingInfo state, + Point2D source, boolean useAnchor) { + // do nothing + } + + /** + * This method is required by the {@link Zoomable} interface, but since + * the plot does not have any domain axes, it does nothing. + * + * @param lowerPercent the new lower bound. + * @param upperPercent the new upper bound. + * @param state the plot state. + * @param source the source point (in Java2D coordinates). + */ + @Override + public void zoomDomainAxes(double lowerPercent, double upperPercent, + PlotRenderingInfo state, Point2D source) { + // do nothing + } + + /** + * Multiplies the range on the range axis/axes by the specified factor. + * + * @param factor the zoom factor. + * @param state the plot state. + * @param source the source point (in Java2D coordinates). + */ + @Override + public void zoomRangeAxes(double factor, PlotRenderingInfo state, + Point2D source) { + zoom(factor); + } + + /** + * Multiplies the range on the range axis by the specified factor. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point (in Java2D space). + * @param useAnchor use source point as zoom anchor? + * + * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) + */ + @Override + public void zoomRangeAxes(double factor, PlotRenderingInfo info, + Point2D source, boolean useAnchor) { + // get the source coordinate - this plot has always a VERTICAL + // orientation + final double sourceX = source.getX(); + + for (int axisIdx = 0; axisIdx < getAxisCount(); axisIdx++) { + final ValueAxis axis = getAxis(axisIdx); + if (axis != null) { + if (useAnchor) { + double anchorX = axis.java2DToValue(sourceX, + info.getDataArea(), RectangleEdge.BOTTOM); + axis.resizeRange(factor, anchorX); + } + else { + axis.resizeRange(factor); + } + } + } + } + + /** + * Zooms in on the range axes. + * + * @param lowerPercent the new lower bound. + * @param upperPercent the new upper bound. + * @param state the plot state. + * @param source the source point (in Java2D coordinates). + */ + @Override + public void zoomRangeAxes(double lowerPercent, double upperPercent, + PlotRenderingInfo state, Point2D source) { + zoom((upperPercent + lowerPercent) / 2.0); + } + + /** + * Returns {@code false} always. + * + * @return {@code false} always. + */ + @Override + public boolean isDomainZoomable() { + return false; + } + + /** + * Returns {@code true} to indicate that the range axis is zoomable. + * + * @return {@code true}. + */ + @Override + public boolean isRangeZoomable() { + return true; + } + + /** + * Returns the orientation of the plot. + * + * @return The orientation. + */ + @Override + public PlotOrientation getOrientation() { + return PlotOrientation.HORIZONTAL; + } + + /** + * Translates a (theta, radius) pair into Java2D coordinates. If + * {@code radius} is less than the lower bound of the axis, then + * this method returns the centre point. + * + * @param angleDegrees the angle in degrees. + * @param radius the radius. + * @param axis the axis. + * @param dataArea the data area. + * + * @return A point in Java2D space. + */ + public Point translateToJava2D(double angleDegrees, double radius, + ValueAxis axis, Rectangle2D dataArea) { + + if (counterClockwise) { + angleDegrees = -angleDegrees; + } + double radians = Math.toRadians(angleDegrees + this.angleOffset); + + double minx = dataArea.getMinX() + this.margin; + double maxx = dataArea.getMaxX() - this.margin; + double miny = dataArea.getMinY() + this.margin; + double maxy = dataArea.getMaxY() - this.margin; + + double halfWidth = (maxx - minx) / 2.0; + double halfHeight = (maxy - miny) / 2.0; + + double midX = minx + halfWidth; + double midY = miny + halfHeight; + + double l = Math.min(halfWidth, halfHeight); + Rectangle2D quadrant = new Rectangle2D.Double(midX, midY, l, l); + + double axisMin = axis.getLowerBound(); + double adjustedRadius = Math.max(radius, axisMin); + + double length = axis.valueToJava2D(adjustedRadius, quadrant, RectangleEdge.BOTTOM) - midX; + float x = (float) (midX + Math.cos(radians) * length); + float y = (float) (midY + Math.sin(radians) * length); + + int ix = Math.round(x); + int iy = Math.round(y); + + Point p = new Point(ix, iy); + return p; + + } + +} diff --git a/src/main/java/org/jfree/chart/plot/SpiderWebPlot.java b/src/main/java/org/jfree/chart/plot/SpiderWebPlot.java index 6c6475395..9e47109d5 100644 --- a/src/main/java/org/jfree/chart/plot/SpiderWebPlot.java +++ b/src/main/java/org/jfree/chart/plot/SpiderWebPlot.java @@ -69,7 +69,6 @@ import org.jfree.chart.LegendItemCollection; import org.jfree.chart.entity.CategoryItemEntity; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.PlotChangeEvent; import org.jfree.chart.labels.CategoryItemLabelGenerator; import org.jfree.chart.labels.CategoryToolTipGenerator; import org.jfree.chart.labels.StandardCategoryItemLabelGenerator; @@ -314,8 +313,7 @@ public CategoryDataset getDataset() { } /** - * Sets the dataset used by the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. + * Sets the dataset used by the plot and calls {@link #fireChangeEvent()}. * * @param dataset the dataset ({@code null} permitted). * @@ -351,8 +349,7 @@ public boolean isWebFilled() { } /** - * Sets the webFilled flag and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the webFilled flag and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -375,8 +372,8 @@ public float getWebFillAlpha() { } /** - * Sets the alpha value for the fill of a plot polygon and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the alpha value for the fill of a plot polygon and calls + * {@link #fireChangeEvent()}. * * @param alpha the new alpha value. If it is outside [0,1] it will be corrected to fit the range. * @see #getWebFillAlpha() @@ -403,8 +400,8 @@ public TableOrder getDataExtractOrder() { } /** - * Sets the data extract order (by row or by column) and sends a - * {@link PlotChangeEvent}to all registered listeners. + * Sets the data extract order (by row or by column) and calls + * {@link #fireChangeEvent()}. * * @param order the order ({@code null} not permitted). * @@ -431,8 +428,8 @@ public double getHeadPercent() { } /** - * Sets the head percent and sends a {@link PlotChangeEvent} to all - * registered listeners. Note that 0.10 is 10 percent. + * Sets the head percent and calls {@link #fireChangeEvent()}. + * Note that 0.10 is 10 percent. * * @param percent the percent (must be greater than zero). * @@ -459,8 +456,7 @@ public double getStartAngle() { } /** - * Sets the starting angle and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the starting angle and calls {@link #fireChangeEvent()}. *

* The initial default value is 90 degrees, which corresponds to 12 o'clock. * A value of zero corresponds to 3 o'clock... this is the encoding used by @@ -487,8 +483,8 @@ public double getMaxValue() { } /** - * Sets the maximum value any category axis can take and sends - * a {@link PlotChangeEvent} to all registered listeners. + * Sets the maximum value any category axis can take and calls + * {@link #fireChangeEvent()}. * * @param value the maximum value. * @@ -512,13 +508,13 @@ public Rotation getDirection() { } /** - * Sets the direction in which the radar axes are drawn and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param direction the direction ({@code null} not permitted). - * - * @see #getDirection() - */ + * Sets the direction in which the radar axes are drawn and calls + * {@link #fireChangeEvent()}. + * + * @param direction the direction ({@code null} not permitted). + * + * @see #getDirection() + */ public void setDirection(Rotation direction) { Args.nullNotPermitted(direction, "direction"); this.direction = direction; @@ -538,14 +534,14 @@ public double getInteriorGap() { } /** - * Sets the interior gap and sends a {@link PlotChangeEvent} to all - * registered listeners. This controls the space between the edges of the - * plot and the plot area itself (the region where the axis labels appear). - * - * @param percent the gap (as a percentage of the available drawing space). - * - * @see #getInteriorGap() - */ + * Sets the interior gap and calls {@link #fireChangeEvent()}. This controls + * the space between the edges of the plot and the plot area itself (the + * region where the axis labels appear). + * + * @param percent the gap (as a percentage of the available drawing space). + * + * @see #getInteriorGap() + */ public void setInteriorGap(double percent) { if ((percent < 0.0) || (percent > MAX_INTERIOR_GAP)) { throw new IllegalArgumentException( @@ -569,8 +565,7 @@ public double getAxisLabelGap() { } /** - * Sets the axis label gap and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the axis label gap and calls {@link #fireChangeEvent()}. * * @param gap the gap. * @@ -594,13 +589,13 @@ public Paint getAxisLinePaint() { } /** - * Sets the paint used to draw the axis lines and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getAxisLinePaint() - */ + * Sets the paint used to draw the axis lines and calls + * {@link #fireChangeEvent()}. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getAxisLinePaint() + */ public void setAxisLinePaint(Paint paint) { Args.nullNotPermitted(paint, "paint"); this.axisLinePaint = paint; @@ -620,13 +615,13 @@ public Stroke getAxisLineStroke() { } /** - * Sets the stroke used to draw the axis lines and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getAxisLineStroke() - */ + * Sets the stroke used to draw the axis lines and calls + * {@link #fireChangeEvent()}. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getAxisLineStroke() + */ public void setAxisLineStroke(Stroke stroke) { Args.nullNotPermitted(stroke, "stroke"); this.axisLineStroke = stroke; @@ -694,14 +689,14 @@ public Paint getSeriesPaint(int series) { } /** - * Sets the paint used to fill a series of the radar and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param paint the paint ({@code null} permitted). - * - * @see #getSeriesPaint(int) - */ + * Sets the paint used to fill a series of the radar and calls + * {@link #fireChangeEvent()}. + * + * @param series the series index (zero-based). + * @param paint the paint ({@code null} permitted). + * + * @see #getSeriesPaint(int) + */ public void setSeriesPaint(int series, Paint paint) { this.seriesPaintList.setPaint(series, paint); fireChangeEvent(); @@ -776,12 +771,12 @@ public Paint getSeriesOutlinePaint(int series) { } /** - * Sets the paint used to fill a series of the radar and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param paint the paint ({@code null} permitted). - */ + * Sets the paint used to fill a series of the radar and calls + * {@link #fireChangeEvent()}. + * + * @param series the series index (zero-based). + * @param paint the paint ({@code null} permitted). + */ public void setSeriesOutlinePaint(int series, Paint paint) { this.seriesOutlinePaintList.setPaint(series, paint); fireChangeEvent(); @@ -855,12 +850,12 @@ public Stroke getSeriesOutlineStroke(int series) { } /** - * Sets the stroke used to fill a series of the radar and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param stroke the stroke ({@code null} permitted). - */ + * Sets the stroke used to fill a series of the radar and calls + * {@link #fireChangeEvent()}. + * + * @param series the series index (zero-based). + * @param stroke the stroke ({@code null} permitted). + */ public void setSeriesOutlineStroke(int series, Stroke stroke) { this.seriesOutlineStrokeList.setStroke(series, stroke); fireChangeEvent(); @@ -899,8 +894,7 @@ public Shape getLegendItemShape() { } /** - * Sets the shape used for legend items and sends a {@link PlotChangeEvent} - * to all registered listeners. + * Sets the shape used for legend items and calls {@link #fireChangeEvent()}. * * @param shape the shape ({@code null} not permitted). * @@ -924,8 +918,7 @@ public Font getLabelFont() { } /** - * Sets the series label font and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the series label font and calls {@link #fireChangeEvent()}. * * @param font the font ({@code null} not permitted). * @@ -949,8 +942,7 @@ public Paint getLabelPaint() { } /** - * Sets the series label paint and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the series label paint and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -974,8 +966,7 @@ public CategoryItemLabelGenerator getLabelGenerator() { } /** - * Sets the label generator and sends a {@link PlotChangeEvent} to all - * registered listeners. + * Sets the label generator and calls {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} not permitted). * @@ -998,13 +989,13 @@ public CategoryToolTipGenerator getToolTipGenerator() { } /** - * Sets the tool tip generator for the plot and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - * - * @see #getToolTipGenerator() - */ + * Sets the tool tip generator for the plot and calls + * {@link #fireChangeEvent()}. + * + * @param generator the generator ({@code null} permitted). + * + * @see #getToolTipGenerator() + */ public void setToolTipGenerator(CategoryToolTipGenerator generator) { this.toolTipGenerator = generator; fireChangeEvent(); @@ -1022,8 +1013,7 @@ public CategoryURLGenerator getURLGenerator() { } /** - * Sets the URL generator for the plot and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the URL generator for the plot and calls {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} permitted). * diff --git a/src/main/java/org/jfree/chart/plot/ThermometerPlot.java b/src/main/java/org/jfree/chart/plot/ThermometerPlot.java index 196411fdf..4f4eaa844 100644 --- a/src/main/java/org/jfree/chart/plot/ThermometerPlot.java +++ b/src/main/java/org/jfree/chart/plot/ThermometerPlot.java @@ -64,7 +64,6 @@ import org.jfree.chart.LegendItemCollection; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.PlotChangeEvent; import org.jfree.chart.ui.RectangleEdge; import org.jfree.chart.ui.RectangleInsets; import org.jfree.chart.util.ObjectUtils; @@ -321,7 +320,7 @@ public ValueDataset getDataset() { /** * Sets the dataset for the plot, replacing the existing dataset if there - * is one, and sends a {@link PlotChangeEvent} to all registered listeners. + * is one, and calls {@link #fireChangeEvent()}. * * @param dataset the dataset ({@code null} permitted). * @@ -361,8 +360,7 @@ public ValueAxis getRangeAxis() { } /** - * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to - * all registered listeners. + * Sets the range axis for the plot and calls {@link #fireChangeEvent()}. * * @param axis the new axis ({@code null} not permitted). * @@ -452,8 +450,7 @@ public RectangleInsets getPadding() { } /** - * Sets the padding for the thermometer and sends a {@link PlotChangeEvent} - * to all registered listeners. + * Sets the padding for the thermometer and calls {@link #fireChangeEvent()}. * * @param padding the padding ({@code null} not permitted). * @@ -478,13 +475,13 @@ public Stroke getThermometerStroke() { } /** - * Sets the stroke used to draw the thermometer outline and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param s the new stroke ({@code null} ignored). - * - * @see #getThermometerStroke() - */ + * Sets the stroke used to draw the thermometer outline and calls + * {@link #fireChangeEvent()}. + * + * @param s the new stroke ({@code null} ignored). + * + * @see #getThermometerStroke() + */ public void setThermometerStroke(Stroke s) { if (s != null) { this.thermometerStroke = s; @@ -505,13 +502,13 @@ public Paint getThermometerPaint() { } /** - * Sets the paint used to draw the thermometer outline and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the new paint ({@code null} ignored). - * - * @see #getThermometerPaint() - */ + * Sets the paint used to draw the thermometer outline and calls + * {@link #fireChangeEvent()}. + * + * @param paint the new paint ({@code null} ignored). + * + * @see #getThermometerPaint() + */ public void setThermometerPaint(Paint paint) { if (paint != null) { this.thermometerPaint = paint; @@ -568,14 +565,14 @@ public int getValueLocation() { } /** - * Sets the location at which the current value is displayed and sends a - * {@link PlotChangeEvent} to all registered listeners. - *

- * The location can be one of the constants: {@code NONE}, {@code RIGHT}, - * {@code LEFT} and {@code BULB}. - * - * @param location the location. - */ + * Sets the location at which the current value is displayed and calls + * {@link #fireChangeEvent()}. + *

+ * The location can be one of the constants: {@code NONE}, {@code RIGHT}, + * {@code LEFT} and {@code BULB}. + * + * @param location the location. + */ public void setValueLocation(int location) { if ((location >= 0) && (location < 4)) { this.valueLocation = location; @@ -600,8 +597,7 @@ public int getAxisLocation() { /** * Sets the location at which the axis is displayed relative to the - * thermometer, and sends a {@link PlotChangeEvent} to all registered - * listeners. + * thermometer, and calls {@link #fireChangeEvent()}. * * @param location the location (one of {@link #NONE}, {@link #LEFT} and * {@link #RIGHT}). @@ -656,13 +652,13 @@ public Paint getValuePaint() { } /** - * Sets the paint used to display the current value and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the new paint ({@code null} not permitted). - * - * @see #getValuePaint() - */ + * Sets the paint used to display the current value and calls + * {@link #fireChangeEvent()}. + * + * @param paint the new paint ({@code null} not permitted). + * + * @see #getValuePaint() + */ public void setValuePaint(Paint paint) { Args.nullNotPermitted(paint, "paint"); if (!this.valuePaint.equals(paint)) { @@ -674,8 +670,7 @@ public void setValuePaint(Paint paint) { // FIXME: No getValueFormat() method? /** - * Sets the formatter for the value label and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the formatter for the value label and calls {@link #fireChangeEvent()}. * * @param formatter the new formatter ({@code null} not permitted). */ @@ -697,8 +692,7 @@ public Paint getMercuryPaint() { } /** - * Sets the default mercury paint and sends a {@link PlotChangeEvent} to - * all registered listeners. + * Sets the default mercury paint and calls {@link #fireChangeEvent()}. * * @param paint the new paint ({@code null} not permitted). * @@ -801,8 +795,7 @@ public Paint getSubrangePaint(int range) { } /** - * Sets the paint to be used for a subrange and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the paint to be used for a subrange and calls {@link #fireChangeEvent()}. * * @param range the range (0, 1 or 2). * @param paint the paint to be applied ({@code null} not permitted). @@ -872,8 +865,7 @@ public int getBulbRadius() { } /** - * Sets the bulb radius (in Java2D units) and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the bulb radius (in Java2D units) and calls {@link #fireChangeEvent()}. * * @param r the new radius (in Java2D units). * @@ -906,8 +898,7 @@ public int getColumnRadius() { } /** - * Sets the column radius (in Java2D units) and sends a - * {@link PlotChangeEvent} to all registered listeners. + * Sets the column radius (in Java2D units) and calls {@link #fireChangeEvent()}. * * @param r the new radius. * @@ -942,8 +933,7 @@ public int getGap() { /** * Sets the gap (in Java2D units) between the two outlines that represent - * the thermometer, and sends a {@link PlotChangeEvent} to all registered - * listeners. + * the thermometer, and calls {@link #fireChangeEvent()}. * * @param gap the new gap. * diff --git a/src/main/java/org/jfree/chart/plot/WaferMapPlot.java b/src/main/java/org/jfree/chart/plot/WaferMapPlot.java index 32f8dcae4..f85032c92 100644 --- a/src/main/java/org/jfree/chart/plot/WaferMapPlot.java +++ b/src/main/java/org/jfree/chart/plot/WaferMapPlot.java @@ -51,7 +51,6 @@ import java.util.ResourceBundle; import org.jfree.chart.LegendItemCollection; -import org.jfree.chart.event.PlotChangeEvent; import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.event.RendererChangeListener; import org.jfree.chart.renderer.WaferMapRenderer; @@ -171,11 +170,11 @@ public WaferMapDataset getDataset() { } /** - * Sets the dataset used by the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param dataset the dataset ({@code null} permitted). - */ + * Sets the dataset used by the plot and calls + * {@link #datasetChanged(DatasetChangeEvent)} + * + * @param dataset the dataset ({@code null} permitted). + */ public void setDataset(WaferMapDataset dataset) { // if there is an existing dataset, remove the plot from the list of // change listeners... diff --git a/src/main/java/org/jfree/chart/plot/XYCrosshairState.java b/src/main/java/org/jfree/chart/plot/XYCrosshairState.java index 0a792610a..44b675246 100644 --- a/src/main/java/org/jfree/chart/plot/XYCrosshairState.java +++ b/src/main/java/org/jfree/chart/plot/XYCrosshairState.java @@ -36,11 +36,9 @@ package org.jfree.chart.plot; -import org.jfree.chart.renderer.xy.XYItemRenderer; - /** - * Crosshair state information for the {@link XYPlot} and {@link XYItemRenderer} - * classes. + * Crosshair state information for the {@link XYPlot} and + * {@link org.jfree.chart.renderer.xy.XYItemRenderer} classes. */ public class XYCrosshairState extends CrosshairState { diff --git a/src/main/java/org/jfree/chart/plot/XYPlot.java b/src/main/java/org/jfree/chart/plot/XYPlot.java index 153aa2b08..cc415500f 100644 --- a/src/main/java/org/jfree/chart/plot/XYPlot.java +++ b/src/main/java/org/jfree/chart/plot/XYPlot.java @@ -1,5355 +1,5353 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ----------- - * XYPlot.java - * ----------- - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): Craig MacFarlane; - * Mark Watson (www.markwatson.com); - * Jonathan Nash; - * Gideon Krause; - * Klaus Rheinwald; - * Xavier Poinsard; - * Richard Atkinson; - * Arnaud Lelievre; - * Nicolas Brodu; - * Eduardo Ramalho; - * Sergei Ivanov; - * Richard West, Advanced Micro Devices, Inc.; - * Ulrich Voigt - patches 1997549 and 2686040; - * Peter Kolb - patches 1934255, 2603321 and 2809117; - * Andrew Mickish - patch 1868749; - * - */ - -package org.jfree.chart.plot; - -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Line2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.ResourceBundle; -import java.util.Set; -import java.util.TreeMap; -import org.jfree.chart.JFreeChart; - -import org.jfree.chart.LegendItem; -import org.jfree.chart.LegendItemCollection; -import org.jfree.chart.annotations.Annotation; -import org.jfree.chart.annotations.XYAnnotation; -import org.jfree.chart.annotations.XYAnnotationBoundsInfo; -import org.jfree.chart.axis.Axis; -import org.jfree.chart.axis.AxisCollection; -import org.jfree.chart.axis.AxisLocation; -import org.jfree.chart.axis.AxisSpace; -import org.jfree.chart.axis.AxisState; -import org.jfree.chart.axis.TickType; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.axis.ValueTick; -import org.jfree.chart.event.AnnotationChangeEvent; -import org.jfree.chart.event.ChartChangeEventType; -import org.jfree.chart.event.PlotChangeEvent; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.event.RendererChangeListener; -import org.jfree.chart.renderer.RendererUtils; -import org.jfree.chart.renderer.xy.AbstractXYItemRenderer; -import org.jfree.chart.renderer.xy.XYItemRenderer; -import org.jfree.chart.renderer.xy.XYItemRendererState; -import org.jfree.chart.ui.Layer; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.RectangleInsets; -import org.jfree.chart.util.CloneUtils; -import org.jfree.chart.util.ObjectUtils; -import org.jfree.chart.util.PaintUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.ResourceBundleWrapper; -import org.jfree.chart.util.SerialUtils; -import org.jfree.chart.util.ShadowGenerator; -import org.jfree.data.Range; -import org.jfree.data.general.DatasetChangeEvent; -import org.jfree.data.general.DatasetUtils; -import org.jfree.data.xy.XYDataset; - -/** - * A general class for plotting data in the form of (x, y) pairs. This plot can - * use data from any class that implements the {@link XYDataset} interface. - *

- * {@code XYPlot} makes use of an {@link XYItemRenderer} to draw each point - * on the plot. By using different renderers, various chart types can be - * produced. - *

- * The {@link org.jfree.chart.ChartFactory} class contains static methods for - * creating pre-configured charts. - */ -public class XYPlot extends Plot implements ValueAxisPlot, Pannable, Zoomable, - RendererChangeListener, Cloneable, PublicCloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = 7044148245716569264L; - - /** The default grid line stroke. */ - public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, - BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, - new float[] {2.0f, 2.0f}, 0.0f); - - /** The default grid line paint. */ - public static final Paint DEFAULT_GRIDLINE_PAINT = Color.LIGHT_GRAY; - - /** The default crosshair visibility. */ - public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false; - - /** The default crosshair stroke. */ - public static final Stroke DEFAULT_CROSSHAIR_STROKE - = DEFAULT_GRIDLINE_STROKE; - - /** The default crosshair paint. */ - public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.BLUE; - - /** The resourceBundle for the localization. */ - protected static ResourceBundle localizationResources - = ResourceBundleWrapper.getBundle( - "org.jfree.chart.plot.LocalizationBundle"); - - /** The plot orientation. */ - private PlotOrientation orientation; - - /** The offset between the data area and the axes. */ - private RectangleInsets axisOffset; - - /** The domain axis / axes (used for the x-values). */ - private Map domainAxes; - - /** The domain axis locations. */ - private Map domainAxisLocations; - - /** The range axis (used for the y-values). */ - private Map rangeAxes; - - /** The range axis location. */ - private Map rangeAxisLocations; - - /** Storage for the datasets. */ - private Map datasets; - - /** Storage for the renderers. */ - private Map renderers; - - /** - * Storage for the mapping between datasets/renderers and domain axes. The - * keys in the map are Integer objects, corresponding to the dataset - * index. The values in the map are List objects containing Integer - * objects (corresponding to the axis indices). If the map contains no - * entry for a dataset, it is assumed to map to the primary domain axis - * (index = 0). - */ - private Map> datasetToDomainAxesMap; - - /** - * Storage for the mapping between datasets/renderers and range axes. The - * keys in the map are Integer objects, corresponding to the dataset - * index. The values in the map are List objects containing Integer - * objects (corresponding to the axis indices). If the map contains no - * entry for a dataset, it is assumed to map to the primary domain axis - * (index = 0). - */ - private Map> datasetToRangeAxesMap; - - /** The origin point for the quadrants (if drawn). */ - private transient Point2D quadrantOrigin = new Point2D.Double(0.0, 0.0); - - /** The paint used for each quadrant. */ - private transient Paint[] quadrantPaint - = new Paint[] {null, null, null, null}; - - /** A flag that controls whether the domain grid-lines are visible. */ - private boolean domainGridlinesVisible; - - /** The stroke used to draw the domain grid-lines. */ - private transient Stroke domainGridlineStroke; - - /** The paint used to draw the domain grid-lines. */ - private transient Paint domainGridlinePaint; - - /** A flag that controls whether the range grid-lines are visible. */ - private boolean rangeGridlinesVisible; - - /** The stroke used to draw the range grid-lines. */ - private transient Stroke rangeGridlineStroke; - - /** The paint used to draw the range grid-lines. */ - private transient Paint rangeGridlinePaint; - - /** - * A flag that controls whether the domain minor grid-lines are visible. - */ - private boolean domainMinorGridlinesVisible; - - /** - * The stroke used to draw the domain minor grid-lines. - */ - private transient Stroke domainMinorGridlineStroke; - - /** - * The paint used to draw the domain minor grid-lines. - */ - private transient Paint domainMinorGridlinePaint; - - /** - * A flag that controls whether the range minor grid-lines are visible. - */ - private boolean rangeMinorGridlinesVisible; - - /** - * The stroke used to draw the range minor grid-lines. - */ - private transient Stroke rangeMinorGridlineStroke; - - /** - * The paint used to draw the range minor grid-lines. - */ - private transient Paint rangeMinorGridlinePaint; - - /** - * A flag that controls whether or not the zero baseline against the domain - * axis is visible. - */ - private boolean domainZeroBaselineVisible; - - /** - * The stroke used for the zero baseline against the domain axis. - */ - private transient Stroke domainZeroBaselineStroke; - - /** - * The paint used for the zero baseline against the domain axis. - */ - private transient Paint domainZeroBaselinePaint; - - /** - * A flag that controls whether or not the zero baseline against the range - * axis is visible. - */ - private boolean rangeZeroBaselineVisible; - - /** The stroke used for the zero baseline against the range axis. */ - private transient Stroke rangeZeroBaselineStroke; - - /** The paint used for the zero baseline against the range axis. */ - private transient Paint rangeZeroBaselinePaint; - - /** A flag that controls whether or not a domain crosshair is drawn..*/ - private boolean domainCrosshairVisible; - - /** The domain crosshair value. */ - private double domainCrosshairValue; - - /** The pen/brush used to draw the crosshair (if any). */ - private transient Stroke domainCrosshairStroke; - - /** The color used to draw the crosshair (if any). */ - private transient Paint domainCrosshairPaint; - - /** - * A flag that controls whether or not the crosshair locks onto actual - * data points. - */ - private boolean domainCrosshairLockedOnData = true; - - /** A flag that controls whether or not a range crosshair is drawn..*/ - private boolean rangeCrosshairVisible; - - /** The range crosshair value. */ - private double rangeCrosshairValue; - - /** The pen/brush used to draw the crosshair (if any). */ - private transient Stroke rangeCrosshairStroke; - - /** The color used to draw the crosshair (if any). */ - private transient Paint rangeCrosshairPaint; - - /** - * A flag that controls whether or not the crosshair locks onto actual - * data points. - */ - private boolean rangeCrosshairLockedOnData = true; - - /** A map of lists of foreground markers (optional) for the domain axes. */ - private Map> foregroundDomainMarkers; - - /** A map of lists of background markers (optional) for the domain axes. */ - private Map> backgroundDomainMarkers; - - /** A map of lists of foreground markers (optional) for the range axes. */ - private Map> foregroundRangeMarkers; - - /** A map of lists of background markers (optional) for the range axes. */ - private Map> backgroundRangeMarkers; - - /** - * A (possibly empty) list of annotations for the plot. The list should - * be initialised in the constructor and never allowed to be - * {@code null}. - */ - private List annotations; - - /** The paint used for the domain tick bands (if any). */ - private transient Paint domainTickBandPaint; - - /** The paint used for the range tick bands (if any). */ - private transient Paint rangeTickBandPaint; - - /** The fixed domain axis space. */ - private AxisSpace fixedDomainAxisSpace; - - /** The fixed range axis space. */ - private AxisSpace fixedRangeAxisSpace; - - /** - * The order of the dataset rendering (REVERSE draws the primary dataset - * last so that it appears to be on top). - */ - private DatasetRenderingOrder datasetRenderingOrder - = DatasetRenderingOrder.REVERSE; - - /** - * The order of the series rendering (REVERSE draws the primary series - * last so that it appears to be on top). - */ - private SeriesRenderingOrder seriesRenderingOrder - = SeriesRenderingOrder.REVERSE; - - /** - * The weight for this plot (only relevant if this is a subplot in a - * combined plot). - */ - private int weight; - - /** - * An optional collection of legend items that can be returned by the - * getLegendItems() method. - */ - private LegendItemCollection fixedLegendItems; - - /** - * A flag that controls whether or not panning is enabled for the domain - * axis/axes. - */ - private boolean domainPannable; - - /** - * A flag that controls whether or not panning is enabled for the range - * axis/axes. - */ - private boolean rangePannable; - - /** - * The shadow generator ({@code null} permitted). - */ - private ShadowGenerator shadowGenerator; - - /** - * Creates a new {@code XYPlot} instance with no dataset, no axes and - * no renderer. You should specify these items before using the plot. - */ - public XYPlot() { - this(null, null, null, null); - } - - /** - * Creates a new plot with the specified dataset, axes and renderer. Any - * of the arguments can be {@code null}, but in that case you should - * take care to specify the value before using the plot (otherwise a - * {@code NullPointerException} may be thrown). - * - * @param dataset the dataset ({@code null} permitted). - * @param domainAxis the domain axis ({@code null} permitted). - * @param rangeAxis the range axis ({@code null} permitted). - * @param renderer the renderer ({@code null} permitted). - */ - public XYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, - XYItemRenderer renderer) { - super(); - this.orientation = PlotOrientation.VERTICAL; - this.weight = 1; // only relevant when this is a subplot - this.axisOffset = RectangleInsets.ZERO_INSETS; - - // allocate storage for datasets, axes and renderers (all optional) - this.domainAxes = new HashMap<>(); - this.domainAxisLocations = new HashMap<>(); - this.foregroundDomainMarkers = new HashMap<>(); - this.backgroundDomainMarkers = new HashMap<>(); - - this.rangeAxes = new HashMap<>(); - this.rangeAxisLocations = new HashMap<>(); - this.foregroundRangeMarkers = new HashMap<>(); - this.backgroundRangeMarkers = new HashMap<>(); - - this.datasets = new HashMap<>(); - this.renderers = new HashMap<>(); - - this.datasetToDomainAxesMap = new TreeMap<>(); - this.datasetToRangeAxesMap = new TreeMap<>(); - - this.annotations = new ArrayList<>(); - - this.datasets.put(0, dataset); - if (dataset != null) { - dataset.addChangeListener(this); - } - - this.renderers.put(0, renderer); - if (renderer != null) { - renderer.setPlot(this); - renderer.addChangeListener(this); - } - - this.domainAxes.put(0, domainAxis); - mapDatasetToDomainAxis(0, 0); - if (domainAxis != null) { - domainAxis.setPlot(this); - domainAxis.addChangeListener(this); - } - this.domainAxisLocations.put(0, AxisLocation.BOTTOM_OR_LEFT); - - this.rangeAxes.put(0, rangeAxis); - mapDatasetToRangeAxis(0, 0); - if (rangeAxis != null) { - rangeAxis.setPlot(this); - rangeAxis.addChangeListener(this); - } - this.rangeAxisLocations.put(0, AxisLocation.BOTTOM_OR_LEFT); - - configureDomainAxes(); - configureRangeAxes(); - - this.domainGridlinesVisible = true; - this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE; - this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT; - - this.domainMinorGridlinesVisible = false; - this.domainMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE; - this.domainMinorGridlinePaint = Color.WHITE; - - this.domainZeroBaselineVisible = false; - this.domainZeroBaselinePaint = Color.BLACK; - this.domainZeroBaselineStroke = new BasicStroke(0.5f); - - this.rangeGridlinesVisible = true; - this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE; - this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT; - - this.rangeMinorGridlinesVisible = false; - this.rangeMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE; - this.rangeMinorGridlinePaint = Color.WHITE; - - this.rangeZeroBaselineVisible = false; - this.rangeZeroBaselinePaint = Color.BLACK; - this.rangeZeroBaselineStroke = new BasicStroke(0.5f); - - this.domainCrosshairVisible = false; - this.domainCrosshairValue = 0.0; - this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; - this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; - - this.rangeCrosshairVisible = false; - this.rangeCrosshairValue = 0.0; - this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; - this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; - this.shadowGenerator = null; - } - - /** - * Returns the plot type as a string. - * - * @return A short string describing the type of plot. - */ - @Override - public String getPlotType() { - return localizationResources.getString("XY_Plot"); - } - - /** - * Returns the orientation of the plot. - * - * @return The orientation (never {@code null}). - * - * @see #setOrientation(PlotOrientation) - */ - @Override - public PlotOrientation getOrientation() { - return this.orientation; - } - - /** - * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param orientation the orientation ({@code null} not allowed). - * - * @see #getOrientation() - */ - public void setOrientation(PlotOrientation orientation) { - Args.nullNotPermitted(orientation, "orientation"); - if (orientation != this.orientation) { - this.orientation = orientation; - fireChangeEvent(); - } - } - - /** - * Returns the axis offset. - * - * @return The axis offset (never {@code null}). - * - * @see #setAxisOffset(RectangleInsets) - */ - public RectangleInsets getAxisOffset() { - return this.axisOffset; - } - - /** - * Sets the axis offsets (gap between the data area and the axes) and sends - * a {@link PlotChangeEvent} to all registered listeners. - * - * @param offset the offset ({@code null} not permitted). - * - * @see #getAxisOffset() - */ - public void setAxisOffset(RectangleInsets offset) { - Args.nullNotPermitted(offset, "offset"); - this.axisOffset = offset; - fireChangeEvent(); - } - - /** - * Returns the domain axis with index 0. If the domain axis for this plot - * is {@code null}, then the method will return the parent plot's - * domain axis (if there is a parent plot). - * - * @return The domain axis (possibly {@code null}). - * - * @see #getDomainAxis(int) - * @see #setDomainAxis(ValueAxis) - */ - public ValueAxis getDomainAxis() { - return getDomainAxis(0); - } - - /** - * Returns the domain axis with the specified index, or {@code null} if - * there is no axis with that index. - * - * @param index the axis index. - * - * @return The axis ({@code null} possible). - * - * @see #setDomainAxis(int, ValueAxis) - */ - public ValueAxis getDomainAxis(int index) { - ValueAxis result = this.domainAxes.get(index); - if (result == null) { - Plot parent = getParent(); - if (parent instanceof XYPlot) { - XYPlot xy = (XYPlot) parent; - result = xy.getDomainAxis(index); - } - } - return result; - } - - /** - * Returns a map containing the domain axes that are assigned to this plot. - * The map is unmodifiable. - * - * @return A map containing the domain axes that are assigned to the plot - * (never {@code null}). - * - * @since 1.5.4 - */ - public Map getDomainAxes() { - return Collections.unmodifiableMap(this.domainAxes); - } - - /** - * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param axis the new axis ({@code null} permitted). - * - * @see #getDomainAxis() - * @see #setDomainAxis(int, ValueAxis) - */ - public void setDomainAxis(ValueAxis axis) { - setDomainAxis(0, axis); - } - - /** - * Sets a domain axis and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param index the axis index. - * @param axis the axis ({@code null} permitted). - * - * @see #getDomainAxis(int) - * @see #setRangeAxis(int, ValueAxis) - */ - public void setDomainAxis(int index, ValueAxis axis) { - setDomainAxis(index, axis, true); - } - - /** - * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param index the axis index. - * @param axis the axis. - * @param notify notify listeners? - * - * @see #getDomainAxis(int) - */ - public void setDomainAxis(int index, ValueAxis axis, boolean notify) { - ValueAxis existing = getDomainAxis(index); - if (existing != null) { - existing.removeChangeListener(this); - } - if (axis != null) { - axis.setPlot(this); - } - this.domainAxes.put(index, axis); - if (axis != null) { - axis.configure(); - axis.addChangeListener(this); - } - if (notify) { - fireChangeEvent(); - } - } - - /** - * Sets the domain axes for this plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param axes the axes ({@code null} not permitted). - * - * @see #setRangeAxes(ValueAxis[]) - */ - public void setDomainAxes(ValueAxis[] axes) { - for (int i = 0; i < axes.length; i++) { - setDomainAxis(i, axes[i], false); - } - fireChangeEvent(); - } - - /** - * Returns the location of the primary domain axis. - * - * @return The location (never {@code null}). - * - * @see #setDomainAxisLocation(AxisLocation) - */ - public AxisLocation getDomainAxisLocation() { - return this.domainAxisLocations.get(0); - } - - /** - * Sets the location of the primary domain axis and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param location the location ({@code null} not permitted). - * - * @see #getDomainAxisLocation() - */ - public void setDomainAxisLocation(AxisLocation location) { - // delegate... - setDomainAxisLocation(0, location, true); - } - - /** - * Sets the location of the domain axis and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param location the location ({@code null} not permitted). - * @param notify notify listeners? - * - * @see #getDomainAxisLocation() - */ - public void setDomainAxisLocation(AxisLocation location, boolean notify) { - // delegate... - setDomainAxisLocation(0, location, notify); - } - - /** - * Returns the edge for the primary domain axis (taking into account the - * plot's orientation). - * - * @return The edge. - * - * @see #getDomainAxisLocation() - * @see #getOrientation() - */ - public RectangleEdge getDomainAxisEdge() { - return Plot.resolveDomainAxisLocation(getDomainAxisLocation(), - this.orientation); - } - - /** - * Returns the number of domain axes. - * - * @return The axis count. - * - * @see #getRangeAxisCount() - */ - public int getDomainAxisCount() { - return this.domainAxes.size(); - } - - /** - * Clears the domain axes from the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @see #clearRangeAxes() - */ - public void clearDomainAxes() { - for (ValueAxis axis: this.domainAxes.values()) { - if (axis != null) { - axis.removeChangeListener(this); - } - } - this.domainAxes.clear(); - fireChangeEvent(); - } - - /** - * Configures the domain axes. - */ - public void configureDomainAxes() { - for (ValueAxis axis: this.domainAxes.values()) { - if (axis != null) { - axis.configure(); - } - } - } - - /** - * Returns the location for a domain axis. If this hasn't been set - * explicitly, the method returns the location that is opposite to the - * primary domain axis location. - * - * @param index the axis index (must be >= 0). - * - * @return The location (never {@code null}). - * - * @see #setDomainAxisLocation(int, AxisLocation) - */ - public AxisLocation getDomainAxisLocation(int index) { - AxisLocation result = this.domainAxisLocations.get(index); - if (result == null) { - result = AxisLocation.getOpposite(getDomainAxisLocation()); - } - return result; - } - - /** - * Sets the location for a domain axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param index the axis index. - * @param location the location ({@code null} not permitted for index - * 0). - * - * @see #getDomainAxisLocation(int) - */ - public void setDomainAxisLocation(int index, AxisLocation location) { - // delegate... - setDomainAxisLocation(index, location, true); - } - - /** - * Sets the axis location for a domain axis and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the axis index (must be >= 0). - * @param location the location ({@code null} not permitted for - * index 0). - * @param notify notify listeners? - * - * @see #getDomainAxisLocation(int) - * @see #setRangeAxisLocation(int, AxisLocation, boolean) - */ - public void setDomainAxisLocation(int index, AxisLocation location, - boolean notify) { - if (index == 0 && location == null) { - throw new IllegalArgumentException( - "Null 'location' for index 0 not permitted."); - } - this.domainAxisLocations.put(index, location); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the edge for a domain axis. - * - * @param index the axis index. - * - * @return The edge. - * - * @see #getRangeAxisEdge(int) - */ - public RectangleEdge getDomainAxisEdge(int index) { - AxisLocation location = getDomainAxisLocation(index); - return Plot.resolveDomainAxisLocation(location, this.orientation); - } - - /** - * Returns the range axis for the plot. If the range axis for this plot is - * {@code null}, then the method will return the parent plot's range - * axis (if there is a parent plot). - * - * @return The range axis. - * - * @see #getRangeAxis(int) - * @see #setRangeAxis(ValueAxis) - */ - public ValueAxis getRangeAxis() { - return getRangeAxis(0); - } - - /** - * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param axis the axis ({@code null} permitted). - * - * @see #getRangeAxis() - * @see #setRangeAxis(int, ValueAxis) - */ - public void setRangeAxis(ValueAxis axis) { - if (axis != null) { - axis.setPlot(this); - } - // plot is likely registered as a listener with the existing axis... - ValueAxis existing = getRangeAxis(); - if (existing != null) { - existing.removeChangeListener(this); - } - this.rangeAxes.put(0, axis); - if (axis != null) { - axis.configure(); - axis.addChangeListener(this); - } - fireChangeEvent(); - } - - /** - * Returns the location of the primary range axis. - * - * @return The location (never {@code null}). - * - * @see #setRangeAxisLocation(AxisLocation) - */ - public AxisLocation getRangeAxisLocation() { - return (AxisLocation) this.rangeAxisLocations.get(0); - } - - /** - * Sets the location of the primary range axis and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param location the location ({@code null} not permitted). - * - * @see #getRangeAxisLocation() - */ - public void setRangeAxisLocation(AxisLocation location) { - // delegate... - setRangeAxisLocation(0, location, true); - } - - /** - * Sets the location of the primary range axis and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param location the location ({@code null} not permitted). - * @param notify notify listeners? - * - * @see #getRangeAxisLocation() - */ - public void setRangeAxisLocation(AxisLocation location, boolean notify) { - // delegate... - setRangeAxisLocation(0, location, notify); - } - - /** - * Returns the edge for the primary range axis. - * - * @return The range axis edge. - * - * @see #getRangeAxisLocation() - * @see #getOrientation() - */ - public RectangleEdge getRangeAxisEdge() { - return Plot.resolveRangeAxisLocation(getRangeAxisLocation(), - this.orientation); - } - - /** - * Returns the range axis with the specified index, or {@code null} if - * there is no axis with that index. - * - * @param index the axis index (must be >= 0). - * - * @return The axis ({@code null} possible). - * - * @see #setRangeAxis(int, ValueAxis) - */ - public ValueAxis getRangeAxis(int index) { - ValueAxis result = this.rangeAxes.get(index); - if (result == null) { - Plot parent = getParent(); - if (parent instanceof XYPlot) { - XYPlot xy = (XYPlot) parent; - result = xy.getRangeAxis(index); - } - } - return result; - } - - /** - * Returns a map containing the range axes that are assigned to this plot. - * The map is unmodifiable. - * - * @return A map containing the range axes that are assigned to the plot - * (never {@code null}). - * - * @since 1.5.4 - */ - public Map getRangeAxes() { - return Collections.unmodifiableMap(this.rangeAxes); - } - - /** - * Sets a range axis and sends a {@link PlotChangeEvent} to all registered - * listeners. - * - * @param index the axis index. - * @param axis the axis ({@code null} permitted). - * - * @see #getRangeAxis(int) - */ - public void setRangeAxis(int index, ValueAxis axis) { - setRangeAxis(index, axis, true); - } - - /** - * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param index the axis index. - * @param axis the axis ({@code null} permitted). - * @param notify notify listeners? - * - * @see #getRangeAxis(int) - */ - public void setRangeAxis(int index, ValueAxis axis, boolean notify) { - ValueAxis existing = getRangeAxis(index); - if (existing != null) { - existing.removeChangeListener(this); - } - if (axis != null) { - axis.setPlot(this); - } - this.rangeAxes.put(index, axis); - if (axis != null) { - axis.configure(); - axis.addChangeListener(this); - } - if (notify) { - fireChangeEvent(); - } - } - - /** - * Sets the range axes for this plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param axes the axes ({@code null} not permitted). - * - * @see #setDomainAxes(ValueAxis[]) - */ - public void setRangeAxes(ValueAxis[] axes) { - for (int i = 0; i < axes.length; i++) { - setRangeAxis(i, axes[i], false); - } - fireChangeEvent(); - } - - /** - * Returns the number of range axes. - * - * @return The axis count. - * - * @see #getDomainAxisCount() - */ - public int getRangeAxisCount() { - return this.rangeAxes.size(); - } - - /** - * Clears the range axes from the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @see #clearDomainAxes() - */ - public void clearRangeAxes() { - for (ValueAxis axis: this.rangeAxes.values()) { - if (axis != null) { - axis.removeChangeListener(this); - } - } - this.rangeAxes.clear(); - fireChangeEvent(); - } - - /** - * Configures the range axes. - * - * @see #configureDomainAxes() - */ - public void configureRangeAxes() { - for (ValueAxis axis: this.rangeAxes.values()) { - if (axis != null) { - axis.configure(); - } - } - } - - /** - * Returns the location for a range axis. If this hasn't been set - * explicitly, the method returns the location that is opposite to the - * primary range axis location. - * - * @param index the axis index (must be >= 0). - * - * @return The location (never {@code null}). - * - * @see #setRangeAxisLocation(int, AxisLocation) - */ - public AxisLocation getRangeAxisLocation(int index) { - AxisLocation result = this.rangeAxisLocations.get(index); - if (result == null) { - result = AxisLocation.getOpposite(getRangeAxisLocation()); - } - return result; - } - - /** - * Sets the location for a range axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param index the axis index. - * @param location the location ({@code null} permitted). - * - * @see #getRangeAxisLocation(int) - */ - public void setRangeAxisLocation(int index, AxisLocation location) { - // delegate... - setRangeAxisLocation(index, location, true); - } - - /** - * Sets the axis location for a domain axis and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the axis index. - * @param location the location ({@code null} not permitted for index 0). - * @param notify notify listeners? - * - * @see #getRangeAxisLocation(int) - * @see #setDomainAxisLocation(int, AxisLocation, boolean) - */ - public void setRangeAxisLocation(int index, AxisLocation location, - boolean notify) { - if (index == 0 && location == null) { - throw new IllegalArgumentException( - "Null 'location' for index 0 not permitted."); - } - this.rangeAxisLocations.put(index, location); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the edge for a range axis. - * - * @param index the axis index. - * - * @return The edge. - * - * @see #getRangeAxisLocation(int) - * @see #getOrientation() - */ - public RectangleEdge getRangeAxisEdge(int index) { - AxisLocation location = getRangeAxisLocation(index); - return Plot.resolveRangeAxisLocation(location, this.orientation); - } - - /** - * Returns the primary dataset for the plot. - * - * @return The primary dataset (possibly {@code null}). - * - * @see #getDataset(int) - * @see #setDataset(XYDataset) - */ - public XYDataset getDataset() { - return getDataset(0); - } - - /** - * Returns the dataset with the specified index, or {@code null} if there - * is no dataset with that index. - * - * @param index the dataset index (must be >= 0). - * - * @return The dataset (possibly {@code null}). - * - * @see #setDataset(int, XYDataset) - */ - public XYDataset getDataset(int index) { - return this.datasets.get(index); - } - - /** - * Returns a map containing the datasets that are assigned to this plot. - * The map is unmodifiable. - * - * @return A map containing the datasets that are assigned to the plot - * (never {@code null}). - * - * @since 1.5.4 - */ - public Map getDatasets() { - return Collections.unmodifiableMap(this.datasets); - } - - /** - * Sets the primary dataset for the plot, replacing the existing dataset if - * there is one. - * - * @param dataset the dataset ({@code null} permitted). - * - * @see #getDataset() - * @see #setDataset(int, XYDataset) - */ - public void setDataset(XYDataset dataset) { - setDataset(0, dataset); - } - - /** - * Sets a dataset for the plot and sends a change event to all registered - * listeners. - * - * @param index the dataset index (must be >= 0). - * @param dataset the dataset ({@code null} permitted). - * - * @see #getDataset(int) - */ - public void setDataset(int index, XYDataset dataset) { - XYDataset existing = getDataset(index); - if (existing != null) { - existing.removeChangeListener(this); - } - this.datasets.put(index, dataset); - if (dataset != null) { - dataset.addChangeListener(this); - } - - // send a dataset change event to self... - DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); - datasetChanged(event); - } - - /** - * Returns the number of datasets. - * - * @return The number of datasets. - */ - public int getDatasetCount() { - return (int) this.datasets.values().stream().filter(Objects::nonNull).count(); - } - - /** - * Returns the index of the specified dataset, or {@code -1} if the - * dataset does not belong to the plot. - * - * @param dataset the dataset ({@code null} not permitted). - * - * @return The index or -1. - */ - public int indexOf(XYDataset dataset) { - for (Map.Entry entry: this.datasets.entrySet()) { - if (dataset == entry.getValue()) { - return entry.getKey(); - } - } - return -1; - } - - /** - * Maps a dataset to a particular domain axis. All data will be plotted - * against axis zero by default, no mapping is required for this case. - * - * @param index the dataset index (zero-based). - * @param axisIndex the axis index. - * - * @see #mapDatasetToRangeAxis(int, int) - */ - public void mapDatasetToDomainAxis(int index, int axisIndex) { - List axisIndices = new ArrayList<>(1); - axisIndices.add(axisIndex); - mapDatasetToDomainAxes(index, axisIndices); - } - - /** - * Maps the specified dataset to the axes in the list. Note that the - * conversion of data values into Java2D space is always performed using - * the first axis in the list. - * - * @param index the dataset index (zero-based). - * @param axisIndices the axis indices ({@code null} permitted). - */ - public void mapDatasetToDomainAxes(int index, List axisIndices) { - Args.requireNonNegative(index, "index"); - checkAxisIndices(axisIndices); - this.datasetToDomainAxesMap.put(index, new ArrayList<>(axisIndices)); - // fake a dataset change event to update axes... - datasetChanged(new DatasetChangeEvent(this, getDataset(index))); - } - - /** - * Maps a dataset to a particular range axis. All data will be plotted - * against axis zero by default, no mapping is required for this case. - * - * @param index the dataset index (zero-based). - * @param axisIndex the axis index. - * - * @see #mapDatasetToDomainAxis(int, int) - */ - public void mapDatasetToRangeAxis(int index, int axisIndex) { - List axisIndices = new ArrayList<>(1); - axisIndices.add(axisIndex); - mapDatasetToRangeAxes(index, axisIndices); - } - - /** - * Maps the specified dataset to the axes in the list. Note that the - * conversion of data values into Java2D space is always performed using - * the first axis in the list. - * - * @param index the dataset index (zero-based). - * @param axisIndices the axis indices ({@code null} permitted). - */ - public void mapDatasetToRangeAxes(int index, List axisIndices) { - Args.requireNonNegative(index, "index"); - checkAxisIndices(axisIndices); - this.datasetToRangeAxesMap.put(index, new ArrayList<>(axisIndices)); - // fake a dataset change event to update axes... - datasetChanged(new DatasetChangeEvent(this, getDataset(index))); - } - - /** - * This method is used to perform argument checking on the list of - * axis indices passed to mapDatasetToDomainAxes() and - * mapDatasetToRangeAxes(). - * - * @param indices the list of indices ({@code null} permitted). - */ - private void checkAxisIndices(List indices) { - // axisIndices can be: - // 1. null; - // 2. non-empty, containing only Integer objects that are unique. - if (indices == null) { - return; // OK - } - int count = indices.size(); - if (count == 0) { - throw new IllegalArgumentException("Empty list not permitted."); - } - Set set = new HashSet<>(); - for (Integer item : indices) { - if (set.contains(item)) { - throw new IllegalArgumentException("Indices must be unique."); - } - set.add(item); - } - } - - /** - * Returns the number of renderer slots for this plot. - * - * @return The number of renderer slots. - */ - public int getRendererCount() { - return this.renderers.size(); - } - - /** - * Returns the renderer for the primary dataset. - * - * @return The item renderer (possibly {@code null}). - * - * @see #setRenderer(XYItemRenderer) - */ - public XYItemRenderer getRenderer() { - return getRenderer(0); - } - - /** - * Returns the renderer with the specified index, or {@code null}. - * - * @param index the renderer index (must be >= 0). - * - * @return The renderer (possibly {@code null}). - * - * @see #setRenderer(int, XYItemRenderer) - */ - public XYItemRenderer getRenderer(int index) { - return this.renderers.get(index); - } - - /** - * Returns a map containing the renderers that are assigned to this plot. - * The map is unmodifiable. - * - * @return A map containing the renderers that are assigned to the plot - * (never {@code null}). - * - * @since 1.5.4 - */ - public Map getRenderers() { - return Collections.unmodifiableMap(this.renderers); - } - - /** - * Sets the renderer for the primary dataset and sends a change event to - * all registered listeners. If the renderer is set to {@code null}, - * no data will be displayed. - * - * @param renderer the renderer ({@code null} permitted). - * - * @see #getRenderer() - */ - public void setRenderer(XYItemRenderer renderer) { - setRenderer(0, renderer); - } - - /** - * Sets the renderer for the dataset with the specified index and sends a - * change event to all registered listeners. Note that each dataset should - * have its own renderer, you should not use one renderer for multiple - * datasets. - * - * @param index the index (must be >= 0). - * @param renderer the renderer. - * - * @see #getRenderer(int) - */ - public void setRenderer(int index, XYItemRenderer renderer) { - setRenderer(index, renderer, true); - } - - /** - * Sets the renderer for the dataset with the specified index and, if - * requested, sends a change event to all registered listeners. Note that - * each dataset should have its own renderer, you should not use one - * renderer for multiple datasets. - * - * @param index the index (must be >= 0). - * @param renderer the renderer. - * @param notify notify listeners? - * - * @see #getRenderer(int) - */ - public void setRenderer(int index, XYItemRenderer renderer, - boolean notify) { - XYItemRenderer existing = getRenderer(index); - if (existing != null) { - existing.removeChangeListener(this); - } - this.renderers.put(index, renderer); - if (renderer != null) { - renderer.setPlot(this); - renderer.addChangeListener(this); - } - configureDomainAxes(); - configureRangeAxes(); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Sets the renderers for this plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param renderers the renderers ({@code null} not permitted). - */ - public void setRenderers(XYItemRenderer[] renderers) { - for (int i = 0; i < renderers.length; i++) { - setRenderer(i, renderers[i], false); - } - fireChangeEvent(); - } - - /** - * Returns the dataset rendering order. - * - * @return The order (never {@code null}). - * - * @see #setDatasetRenderingOrder(DatasetRenderingOrder) - */ - public DatasetRenderingOrder getDatasetRenderingOrder() { - return this.datasetRenderingOrder; - } - - /** - * Sets the rendering order and sends a {@link PlotChangeEvent} to all - * registered listeners. By default, the plot renders the primary dataset - * last (so that the primary dataset overlays the secondary datasets). - * You can reverse this if you want to. - * - * @param order the rendering order ({@code null} not permitted). - * - * @see #getDatasetRenderingOrder() - */ - public void setDatasetRenderingOrder(DatasetRenderingOrder order) { - Args.nullNotPermitted(order, "order"); - this.datasetRenderingOrder = order; - fireChangeEvent(); - } - - /** - * Returns the series rendering order. - * - * @return the order (never {@code null}). - * - * @see #setSeriesRenderingOrder(SeriesRenderingOrder) - */ - public SeriesRenderingOrder getSeriesRenderingOrder() { - return this.seriesRenderingOrder; - } - - /** - * Sets the series order and sends a {@link PlotChangeEvent} to all - * registered listeners. By default, the plot renders the primary series - * last (so that the primary series appears to be on top). - * You can reverse this if you want to. - * - * @param order the rendering order ({@code null} not permitted). - * - * @see #getSeriesRenderingOrder() - */ - public void setSeriesRenderingOrder(SeriesRenderingOrder order) { - Args.nullNotPermitted(order, "order"); - this.seriesRenderingOrder = order; - fireChangeEvent(); - } - - /** - * Returns the index of the specified renderer, or {@code -1} if the - * renderer is not assigned to this plot. - * - * @param renderer the renderer ({@code null} permitted). - * - * @return The renderer index. - */ - public int getIndexOf(XYItemRenderer renderer) { - for (Map.Entry entry - : this.renderers.entrySet()) { - if (entry.getValue() == renderer) { - return entry.getKey(); - } - } - return -1; - } - - /** - * Returns the renderer for the specified dataset (this is either the - * renderer with the same index as the dataset or, if there isn't a - * renderer with the same index, the default renderer). If the dataset - * does not belong to the plot, this method will return {@code null}. - * - * @param dataset the dataset ({@code null} permitted). - * - * @return The renderer (possibly {@code null}). - */ - public XYItemRenderer getRendererForDataset(XYDataset dataset) { - int datasetIndex = indexOf(dataset); - if (datasetIndex < 0) { - return null; - } - XYItemRenderer result = this.renderers.get(datasetIndex); - if (result == null) { - result = getRenderer(); - } - return result; - } - - /** - * Returns the weight for this plot when it is used as a subplot within a - * combined plot. - * - * @return The weight. - * - * @see #setWeight(int) - */ - public int getWeight() { - return this.weight; - } - - /** - * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param weight the weight. - * - * @see #getWeight() - */ - public void setWeight(int weight) { - this.weight = weight; - fireChangeEvent(); - } - - /** - * Returns {@code true} if the domain gridlines are visible, and - * {@code false} otherwise. - * - * @return {@code true} or {@code false}. - * - * @see #setDomainGridlinesVisible(boolean) - */ - public boolean isDomainGridlinesVisible() { - return this.domainGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not the domain grid-lines are - * visible. - *

- * If the flag value is changed, a {@link PlotChangeEvent} is sent to all - * registered listeners. - * - * @param visible the new value of the flag. - * - * @see #isDomainGridlinesVisible() - */ - public void setDomainGridlinesVisible(boolean visible) { - if (this.domainGridlinesVisible != visible) { - this.domainGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns {@code true} if the domain minor gridlines are visible, and - * {@code false} otherwise. - * - * @return {@code true} or {@code false}. - * - * @see #setDomainMinorGridlinesVisible(boolean) - */ - public boolean isDomainMinorGridlinesVisible() { - return this.domainMinorGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not the domain minor grid-lines - * are visible. - *

- * If the flag value is changed, a {@link PlotChangeEvent} is sent to all - * registered listeners. - * - * @param visible the new value of the flag. - * - * @see #isDomainMinorGridlinesVisible() - */ - public void setDomainMinorGridlinesVisible(boolean visible) { - if (this.domainMinorGridlinesVisible != visible) { - this.domainMinorGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the stroke for the grid-lines (if any) plotted against the - * domain axis. - * - * @return The stroke (never {@code null}). - * - * @see #setDomainGridlineStroke(Stroke) - */ - public Stroke getDomainGridlineStroke() { - return this.domainGridlineStroke; - } - - /** - * Sets the stroke for the grid lines plotted against the domain axis, and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getDomainGridlineStroke() - */ - public void setDomainGridlineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.domainGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the stroke for the minor grid-lines (if any) plotted against the - * domain axis. - * - * @return The stroke (never {@code null}). - * - * @see #setDomainMinorGridlineStroke(Stroke) - */ - - public Stroke getDomainMinorGridlineStroke() { - return this.domainMinorGridlineStroke; - } - - /** - * Sets the stroke for the minor grid lines plotted against the domain - * axis, and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getDomainMinorGridlineStroke() - */ - public void setDomainMinorGridlineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.domainMinorGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the grid lines (if any) plotted against the domain - * axis. - * - * @return The paint (never {@code null}). - * - * @see #setDomainGridlinePaint(Paint) - */ - public Paint getDomainGridlinePaint() { - return this.domainGridlinePaint; - } - - /** - * Sets the paint for the grid lines plotted against the domain axis, and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getDomainGridlinePaint() - */ - public void setDomainGridlinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.domainGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns the paint for the minor grid lines (if any) plotted against the - * domain axis. - * - * @return The paint (never {@code null}). - * - * @see #setDomainMinorGridlinePaint(Paint) - */ - public Paint getDomainMinorGridlinePaint() { - return this.domainMinorGridlinePaint; - } - - /** - * Sets the paint for the minor grid lines plotted against the domain axis, - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @throws IllegalArgumentException if {@code Paint} is - * {@code null}. - * - * @see #getDomainMinorGridlinePaint() - */ - public void setDomainMinorGridlinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.domainMinorGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns {@code true} if the range axis grid is visible, and - * {@code false} otherwise. - * - * @return A boolean. - * - * @see #setRangeGridlinesVisible(boolean) - */ - public boolean isRangeGridlinesVisible() { - return this.rangeGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not the range axis grid lines - * are visible. - *

- * If the flag value is changed, a {@link PlotChangeEvent} is sent to all - * registered listeners. - * - * @param visible the new value of the flag. - * - * @see #isRangeGridlinesVisible() - */ - public void setRangeGridlinesVisible(boolean visible) { - if (this.rangeGridlinesVisible != visible) { - this.rangeGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the stroke for the grid lines (if any) plotted against the - * range axis. - * - * @return The stroke (never {@code null}). - * - * @see #setRangeGridlineStroke(Stroke) - */ - public Stroke getRangeGridlineStroke() { - return this.rangeGridlineStroke; - } - - /** - * Sets the stroke for the grid lines plotted against the range axis, - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getRangeGridlineStroke() - */ - public void setRangeGridlineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.rangeGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the grid lines (if any) plotted against the range - * axis. - * - * @return The paint (never {@code null}). - * - * @see #setRangeGridlinePaint(Paint) - */ - public Paint getRangeGridlinePaint() { - return this.rangeGridlinePaint; - } - - /** - * Sets the paint for the grid lines plotted against the range axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getRangeGridlinePaint() - */ - public void setRangeGridlinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.rangeGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns {@code true} if the range axis minor grid is visible, and - * {@code false} otherwise. - * - * @return A boolean. - * - * @see #setRangeMinorGridlinesVisible(boolean) - */ - public boolean isRangeMinorGridlinesVisible() { - return this.rangeMinorGridlinesVisible; - } - - /** - * Sets the flag that controls whether or not the range axis minor grid - * lines are visible. - *

- * If the flag value is changed, a {@link PlotChangeEvent} is sent to all - * registered listeners. - * - * @param visible the new value of the flag. - * - * @see #isRangeMinorGridlinesVisible() - */ - public void setRangeMinorGridlinesVisible(boolean visible) { - if (this.rangeMinorGridlinesVisible != visible) { - this.rangeMinorGridlinesVisible = visible; - fireChangeEvent(); - } - } - - /** - * Returns the stroke for the minor grid lines (if any) plotted against the - * range axis. - * - * @return The stroke (never {@code null}). - * - * @see #setRangeMinorGridlineStroke(Stroke) - */ - public Stroke getRangeMinorGridlineStroke() { - return this.rangeMinorGridlineStroke; - } - - /** - * Sets the stroke for the minor grid lines plotted against the range axis, - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getRangeMinorGridlineStroke() - */ - public void setRangeMinorGridlineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.rangeMinorGridlineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the minor grid lines (if any) plotted against the - * range axis. - * - * @return The paint (never {@code null}). - * - * @see #setRangeMinorGridlinePaint(Paint) - */ - public Paint getRangeMinorGridlinePaint() { - return this.rangeMinorGridlinePaint; - } - - /** - * Sets the paint for the minor grid lines plotted against the range axis - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getRangeMinorGridlinePaint() - */ - public void setRangeMinorGridlinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.rangeMinorGridlinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns a flag that controls whether or not a zero baseline is - * displayed for the domain axis. - * - * @return A boolean. - * - * @see #setDomainZeroBaselineVisible(boolean) - */ - public boolean isDomainZeroBaselineVisible() { - return this.domainZeroBaselineVisible; - } - - /** - * Sets the flag that controls whether or not the zero baseline is - * displayed for the domain axis, and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param visible the flag. - * - * @see #isDomainZeroBaselineVisible() - */ - public void setDomainZeroBaselineVisible(boolean visible) { - this.domainZeroBaselineVisible = visible; - fireChangeEvent(); - } - - /** - * Returns the stroke used for the zero baseline against the domain axis. - * - * @return The stroke (never {@code null}). - * - * @see #setDomainZeroBaselineStroke(Stroke) - */ - public Stroke getDomainZeroBaselineStroke() { - return this.domainZeroBaselineStroke; - } - - /** - * Sets the stroke for the zero baseline for the domain axis, - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getRangeZeroBaselineStroke() - */ - public void setDomainZeroBaselineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.domainZeroBaselineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the zero baseline (if any) plotted against the - * domain axis. - * - * @return The paint (never {@code null}). - * - * @see #setDomainZeroBaselinePaint(Paint) - */ - public Paint getDomainZeroBaselinePaint() { - return this.domainZeroBaselinePaint; - } - - /** - * Sets the paint for the zero baseline plotted against the domain axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getDomainZeroBaselinePaint() - */ - public void setDomainZeroBaselinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.domainZeroBaselinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns a flag that controls whether or not a zero baseline is - * displayed for the range axis. - * - * @return A boolean. - * - * @see #setRangeZeroBaselineVisible(boolean) - */ - public boolean isRangeZeroBaselineVisible() { - return this.rangeZeroBaselineVisible; - } - - /** - * Sets the flag that controls whether or not the zero baseline is - * displayed for the range axis, and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param visible the flag. - * - * @see #isRangeZeroBaselineVisible() - */ - public void setRangeZeroBaselineVisible(boolean visible) { - this.rangeZeroBaselineVisible = visible; - fireChangeEvent(); - } - - /** - * Returns the stroke used for the zero baseline against the range axis. - * - * @return The stroke (never {@code null}). - * - * @see #setRangeZeroBaselineStroke(Stroke) - */ - public Stroke getRangeZeroBaselineStroke() { - return this.rangeZeroBaselineStroke; - } - - /** - * Sets the stroke for the zero baseline for the range axis, - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the stroke ({@code null} not permitted). - * - * @see #getRangeZeroBaselineStroke() - */ - public void setRangeZeroBaselineStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.rangeZeroBaselineStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the paint for the zero baseline (if any) plotted against the - * range axis. - * - * @return The paint (never {@code null}). - * - * @see #setRangeZeroBaselinePaint(Paint) - */ - public Paint getRangeZeroBaselinePaint() { - return this.rangeZeroBaselinePaint; - } - - /** - * Sets the paint for the zero baseline plotted against the range axis and - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getRangeZeroBaselinePaint() - */ - public void setRangeZeroBaselinePaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.rangeZeroBaselinePaint = paint; - fireChangeEvent(); - } - - /** - * Returns the paint used for the domain tick bands. If this is - * {@code null}, no tick bands will be drawn. - * - * @return The paint (possibly {@code null}). - * - * @see #setDomainTickBandPaint(Paint) - */ - public Paint getDomainTickBandPaint() { - return this.domainTickBandPaint; - } - - /** - * Sets the paint for the domain tick bands. - * - * @param paint the paint ({@code null} permitted). - * - * @see #getDomainTickBandPaint() - */ - public void setDomainTickBandPaint(Paint paint) { - this.domainTickBandPaint = paint; - fireChangeEvent(); - } - - /** - * Returns the paint used for the range tick bands. If this is - * {@code null}, no tick bands will be drawn. - * - * @return The paint (possibly {@code null}). - * - * @see #setRangeTickBandPaint(Paint) - */ - public Paint getRangeTickBandPaint() { - return this.rangeTickBandPaint; - } - - /** - * Sets the paint for the range tick bands. - * - * @param paint the paint ({@code null} permitted). - * - * @see #getRangeTickBandPaint() - */ - public void setRangeTickBandPaint(Paint paint) { - this.rangeTickBandPaint = paint; - fireChangeEvent(); - } - - /** - * Returns the origin for the quadrants that can be displayed on the plot. - * This defaults to (0, 0). - * - * @return The origin point (never {@code null}). - * - * @see #setQuadrantOrigin(Point2D) - */ - public Point2D getQuadrantOrigin() { - return this.quadrantOrigin; - } - - /** - * Sets the quadrant origin and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param origin the origin ({@code null} not permitted). - * - * @see #getQuadrantOrigin() - */ - public void setQuadrantOrigin(Point2D origin) { - Args.nullNotPermitted(origin, "origin"); - this.quadrantOrigin = origin; - fireChangeEvent(); - } - - /** - * Returns the paint used for the specified quadrant. - * - * @param index the quadrant index (0-3). - * - * @return The paint (possibly {@code null}). - * - * @see #setQuadrantPaint(int, Paint) - */ - public Paint getQuadrantPaint(int index) { - if (index < 0 || index > 3) { - throw new IllegalArgumentException("The index value (" + index - + ") should be in the range 0 to 3."); - } - return this.quadrantPaint[index]; - } - - /** - * Sets the paint used for the specified quadrant and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the quadrant index (0-3). - * @param paint the paint ({@code null} permitted). - * - * @see #getQuadrantPaint(int) - */ - public void setQuadrantPaint(int index, Paint paint) { - if (index < 0 || index > 3) { - throw new IllegalArgumentException("The index value (" + index - + ") should be in the range 0 to 3."); - } - this.quadrantPaint[index] = paint; - fireChangeEvent(); - } - - /** - * Adds a marker for the domain axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to the domain axis, however this is entirely up to the renderer. - * - * @param marker the marker ({@code null} not permitted). - * - * @see #addDomainMarker(Marker, Layer) - * @see #clearDomainMarkers() - */ - public void addDomainMarker(Marker marker) { - // defer argument checking... - addDomainMarker(marker, Layer.FOREGROUND); - } - - /** - * Adds a marker for the domain axis in the specified layer and sends a - * {@link PlotChangeEvent} to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to the domain axis, however this is entirely up to the renderer. - * - * @param marker the marker ({@code null} not permitted). - * @param layer the layer (foreground or background). - * - * @see #addDomainMarker(int, Marker, Layer) - */ - public void addDomainMarker(Marker marker, Layer layer) { - addDomainMarker(0, marker, layer); - } - - /** - * Clears all the (foreground and background) domain markers and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @see #addDomainMarker(int, Marker, Layer) - */ - public void clearDomainMarkers() { - if (this.backgroundDomainMarkers != null) { - Set keys = this.backgroundDomainMarkers.keySet(); - for (Integer key : keys) { - clearDomainMarkers(key); - } - this.backgroundDomainMarkers.clear(); - } - if (this.foregroundDomainMarkers != null) { - Set keys = this.foregroundDomainMarkers.keySet(); - for (Integer key : keys) { - clearDomainMarkers(key); - } - this.foregroundDomainMarkers.clear(); - } - fireChangeEvent(); - } - - /** - * Clears the (foreground and background) domain markers for a particular - * renderer and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param index the renderer index. - * - * @see #clearRangeMarkers(int) - */ - public void clearDomainMarkers(int index) { - if (this.backgroundDomainMarkers != null) { - List markers = this.backgroundDomainMarkers.get(index); - if (markers != null) { - for (Marker m : markers) { - m.removeChangeListener(this); - } - markers.clear(); - } - } - if (this.foregroundRangeMarkers != null) { - List markers = this.foregroundDomainMarkers.get(index); - if (markers != null) { - for (Marker m : markers) { - m.removeChangeListener(this); - } - markers.clear(); - } - } - fireChangeEvent(); - } - - /** - * Adds a marker for a specific dataset/renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to the domain axis (that the renderer is mapped to), however this is - * entirely up to the renderer. - * - * @param index the dataset/renderer index. - * @param marker the marker. - * @param layer the layer (foreground or background). - * - * @see #clearDomainMarkers(int) - * @see #addRangeMarker(int, Marker, Layer) - */ - public void addDomainMarker(int index, Marker marker, Layer layer) { - addDomainMarker(index, marker, layer, true); - } - - /** - * Adds a marker for a specific dataset/renderer and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to the domain axis (that the renderer is mapped to), however this is - * entirely up to the renderer. - * - * @param index the dataset/renderer index. - * @param marker the marker. - * @param layer the layer (foreground or background). - * @param notify notify listeners? - */ - public void addDomainMarker(int index, Marker marker, Layer layer, - boolean notify) { - Args.nullNotPermitted(marker, "marker"); - Args.nullNotPermitted(layer, "layer"); - List markers; - if (layer == Layer.FOREGROUND) { - markers = this.foregroundDomainMarkers.computeIfAbsent(index, k -> new ArrayList<>()); - markers.add(marker); - } - else if (layer == Layer.BACKGROUND) { - markers = this.backgroundDomainMarkers.computeIfAbsent(index, k -> new ArrayList<>()); - markers.add(marker); - } - marker.addChangeListener(this); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Removes a marker for the domain axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param marker the marker. - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeDomainMarker(Marker marker) { - return removeDomainMarker(marker, Layer.FOREGROUND); - } - - /** - * Removes a marker for the domain axis in the specified layer and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param marker the marker ({@code null} not permitted). - * @param layer the layer (foreground or background). - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeDomainMarker(Marker marker, Layer layer) { - return removeDomainMarker(0, marker, layer); - } - - /** - * Removes a marker for a specific dataset/renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the dataset/renderer index. - * @param marker the marker. - * @param layer the layer (foreground or background). - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeDomainMarker(int index, Marker marker, Layer layer) { - return removeDomainMarker(index, marker, layer, true); - } - - /** - * Removes a marker for a specific dataset/renderer and, if requested, - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param index the dataset/renderer index. - * @param marker the marker. - * @param layer the layer (foreground or background). - * @param notify notify listeners? - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeDomainMarker(int index, Marker marker, Layer layer, - boolean notify) { - List markers; - if (layer == Layer.FOREGROUND) { - markers = this.foregroundDomainMarkers.get(index); - } else { - markers = this.backgroundDomainMarkers.get(index); - } - if (markers == null) { - return false; - } - boolean removed = markers.remove(marker); - if (removed && notify) { - fireChangeEvent(); - } - return removed; - } - - /** - * Adds a marker for the range axis and sends a {@link PlotChangeEvent} to - * all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to the range axis, however this is entirely up to the renderer. - * - * @param marker the marker ({@code null} not permitted). - * - * @see #addRangeMarker(Marker, Layer) - */ - public void addRangeMarker(Marker marker) { - addRangeMarker(marker, Layer.FOREGROUND); - } - - /** - * Adds a marker for the range axis in the specified layer and sends a - * {@link PlotChangeEvent} to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to the range axis, however this is entirely up to the renderer. - * - * @param marker the marker ({@code null} not permitted). - * @param layer the layer (foreground or background). - * - * @see #addRangeMarker(int, Marker, Layer) - */ - public void addRangeMarker(Marker marker, Layer layer) { - addRangeMarker(0, marker, layer); - } - - /** - * Clears all the range markers and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @see #clearRangeMarkers() - */ - public void clearRangeMarkers() { - if (this.backgroundRangeMarkers != null) { - Set keys = this.backgroundRangeMarkers.keySet(); - for (Integer key : keys) { - clearRangeMarkers(key); - } - this.backgroundRangeMarkers.clear(); - } - if (this.foregroundRangeMarkers != null) { - Set keys = this.foregroundRangeMarkers.keySet(); - for (Integer key : keys) { - clearRangeMarkers(key); - } - this.foregroundRangeMarkers.clear(); - } - fireChangeEvent(); - } - - /** - * Adds a marker for a specific dataset/renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to the range axis, however this is entirely up to the renderer. - * - * @param index the dataset/renderer index. - * @param marker the marker. - * @param layer the layer (foreground or background). - * - * @see #clearRangeMarkers(int) - * @see #addDomainMarker(int, Marker, Layer) - */ - public void addRangeMarker(int index, Marker marker, Layer layer) { - addRangeMarker(index, marker, layer, true); - } - - /** - * Adds a marker for a specific dataset/renderer and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - *

- * Typically a marker will be drawn by the renderer as a line perpendicular - * to the range axis, however this is entirely up to the renderer. - * - * @param index the dataset/renderer index. - * @param marker the marker. - * @param layer the layer (foreground or background). - * @param notify notify listeners? - */ - public void addRangeMarker(int index, Marker marker, Layer layer, - boolean notify) { - List markers; - if (layer == Layer.FOREGROUND) { - markers = this.foregroundRangeMarkers.computeIfAbsent(index, k -> new ArrayList<>()); - markers.add(marker); - } - else if (layer == Layer.BACKGROUND) { - markers = this.backgroundRangeMarkers.computeIfAbsent(index, k -> new ArrayList<>()); - markers.add(marker); - } - marker.addChangeListener(this); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Clears the (foreground and background) range markers for a particular - * renderer. - * - * @param index the renderer index. - */ - public void clearRangeMarkers(int index) { - if (this.backgroundRangeMarkers != null) { - List markers = this.backgroundRangeMarkers.get(index); - if (markers != null) { - for (Marker m : markers) { - m.removeChangeListener(this); - } - markers.clear(); - } - } - if (this.foregroundRangeMarkers != null) { - List markers = this.foregroundRangeMarkers.get(index); - if (markers != null) { - for (Marker m : markers) { - m.removeChangeListener(this); - } - markers.clear(); - } - } - fireChangeEvent(); - } - - /** - * Removes a marker for the range axis and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param marker the marker. - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeRangeMarker(Marker marker) { - return removeRangeMarker(marker, Layer.FOREGROUND); - } - - /** - * Removes a marker for the range axis in the specified layer and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param marker the marker ({@code null} not permitted). - * @param layer the layer (foreground or background). - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeRangeMarker(Marker marker, Layer layer) { - return removeRangeMarker(0, marker, layer); - } - - /** - * Removes a marker for a specific dataset/renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the dataset/renderer index. - * @param marker the marker ({@code null} not permitted). - * @param layer the layer (foreground or background). - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeRangeMarker(int index, Marker marker, Layer layer) { - return removeRangeMarker(index, marker, layer, true); - } - - /** - * Removes a marker for a specific dataset/renderer and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the dataset/renderer index. - * @param marker the marker ({@code null} not permitted). - * @param layer the layer (foreground or background) ({@code null} not permitted). - * @param notify notify listeners? - * - * @return A boolean indicating whether or not the marker was actually - * removed. - */ - public boolean removeRangeMarker(int index, Marker marker, Layer layer, - boolean notify) { - Args.nullNotPermitted(marker, "marker"); - Args.nullNotPermitted(layer, "layer"); - List markers; - if (layer == Layer.FOREGROUND) { - markers = this.foregroundRangeMarkers.get(index); - } else { - markers = this.backgroundRangeMarkers.get(index); - } - if (markers == null) { - return false; - } - boolean removed = markers.remove(marker); - if (removed && notify) { - fireChangeEvent(); - } - return removed; - } - - /** - * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param annotation the annotation ({@code null} not permitted). - * - * @see #getAnnotations() - * @see #removeAnnotation(XYAnnotation) - */ - public void addAnnotation(XYAnnotation annotation) { - addAnnotation(annotation, true); - } - - /** - * Adds an annotation to the plot and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param annotation the annotation ({@code null} not permitted). - * @param notify notify listeners? - */ - public void addAnnotation(XYAnnotation annotation, boolean notify) { - Args.nullNotPermitted(annotation, "annotation"); - this.annotations.add(annotation); - annotation.addChangeListener(this); - if (notify) { - fireChangeEvent(); - } - } - - /** - * Removes an annotation from the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param annotation the annotation ({@code null} not permitted). - * - * @return A boolean (indicates whether or not the annotation was removed). - * - * @see #addAnnotation(XYAnnotation) - * @see #getAnnotations() - */ - public boolean removeAnnotation(XYAnnotation annotation) { - return removeAnnotation(annotation, true); - } - - /** - * Removes an annotation from the plot and sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param annotation the annotation ({@code null} not permitted). - * @param notify notify listeners? - * - * @return A boolean (indicates whether or not the annotation was removed). - */ - public boolean removeAnnotation(XYAnnotation annotation, boolean notify) { - Args.nullNotPermitted(annotation, "annotation"); - boolean removed = this.annotations.remove(annotation); - annotation.removeChangeListener(this); - if (removed && notify) { - fireChangeEvent(); - } - return removed; - } - - /** - * Returns the list of annotations. - * - * @return The list of annotations. - * - * @see #addAnnotation(XYAnnotation) - */ - public List getAnnotations() { - return new ArrayList<>(this.annotations); - } - - /** - * Clears all the annotations and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @see #addAnnotation(XYAnnotation) - */ - public void clearAnnotations() { - for (XYAnnotation annotation : this.annotations) { - annotation.removeChangeListener(this); - } - this.annotations.clear(); - fireChangeEvent(); - } - - /** - * Returns the shadow generator for the plot, if any. - * - * @return The shadow generator (possibly {@code null}). - */ - public ShadowGenerator getShadowGenerator() { - return this.shadowGenerator; - } - - /** - * Sets the shadow generator for the plot and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - */ - public void setShadowGenerator(ShadowGenerator generator) { - this.shadowGenerator = generator; - fireChangeEvent(); - } - - /** - * Calculates the space required for all the axes in the plot. - * - * @param g2 the graphics device. - * @param plotArea the plot area. - * - * @return The required space. - */ - protected AxisSpace calculateAxisSpace(Graphics2D g2, - Rectangle2D plotArea) { - AxisSpace space = new AxisSpace(); - space = calculateRangeAxisSpace(g2, plotArea, space); - Rectangle2D revPlotArea = space.shrink(plotArea, null); - space = calculateDomainAxisSpace(g2, revPlotArea, space); - return space; - } - - /** - * Calculates the space required for the domain axis/axes. - * - * @param g2 the graphics device. - * @param plotArea the plot area. - * @param space a carrier for the result ({@code null} permitted). - * - * @return The required space. - */ - protected AxisSpace calculateDomainAxisSpace(Graphics2D g2, - Rectangle2D plotArea, AxisSpace space) { - - if (space == null) { - space = new AxisSpace(); - } - - // reserve some space for the domain axis... - if (this.fixedDomainAxisSpace != null) { - if (this.orientation == PlotOrientation.HORIZONTAL) { - space.ensureAtLeast(this.fixedDomainAxisSpace.getLeft(), - RectangleEdge.LEFT); - space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(), - RectangleEdge.RIGHT); - } - else if (this.orientation == PlotOrientation.VERTICAL) { - space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(), - RectangleEdge.TOP); - space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(), - RectangleEdge.BOTTOM); - } - } - else { - // reserve space for the domain axes... - for (ValueAxis axis: this.domainAxes.values()) { - if (axis != null) { - RectangleEdge edge = getDomainAxisEdge( - findDomainAxisIndex(axis)); - space = axis.reserveSpace(g2, this, plotArea, edge, space); - } - } - } - - return space; - - } - - /** - * Calculates the space required for the range axis/axes. - * - * @param g2 the graphics device. - * @param plotArea the plot area. - * @param space a carrier for the result ({@code null} permitted). - * - * @return The required space. - */ - protected AxisSpace calculateRangeAxisSpace(Graphics2D g2, - Rectangle2D plotArea, AxisSpace space) { - - if (space == null) { - space = new AxisSpace(); - } - - // reserve some space for the range axis... - if (this.fixedRangeAxisSpace != null) { - if (this.orientation == PlotOrientation.HORIZONTAL) { - space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(), - RectangleEdge.TOP); - space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(), - RectangleEdge.BOTTOM); - } - else if (this.orientation == PlotOrientation.VERTICAL) { - space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(), - RectangleEdge.LEFT); - space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(), - RectangleEdge.RIGHT); - } - } - else { - // reserve space for the range axes... - for (ValueAxis axis: this.rangeAxes.values()) { - if (axis != null) { - RectangleEdge edge = getRangeAxisEdge( - findRangeAxisIndex(axis)); - space = axis.reserveSpace(g2, this, plotArea, edge, space); - } - } - } - return space; - - } - - /** - * Trims a rectangle to integer coordinates. - * - * @param rect the incoming rectangle. - * - * @return A rectangle with integer coordinates. - */ - private Rectangle integerise(Rectangle2D rect) { - int x0 = (int) Math.ceil(rect.getMinX()); - int y0 = (int) Math.ceil(rect.getMinY()); - int x1 = (int) Math.floor(rect.getMaxX()); - int y1 = (int) Math.floor(rect.getMaxY()); - return new Rectangle(x0, y0, (x1 - x0), (y1 - y0)); - } - - /** - * Draws the plot within the specified area on a graphics device. - * - * @param g2 the graphics device. - * @param area the plot area (in Java2D space). - * @param anchor an anchor point in Java2D space ({@code null} - * permitted). - * @param parentState the state from the parent plot, if there is one - * ({@code null} permitted). - * @param info collects chart drawing information ({@code null} - * permitted). - */ - @Override - public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, - PlotState parentState, PlotRenderingInfo info) { - - // if the plot area is too small, just return... - boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); - boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); - if (b1 || b2) { - return; - } - - // record the plot area... - if (info != null) { - info.setPlotArea(area); - } - - // adjust the drawing area for the plot insets (if any)... - RectangleInsets insets = getInsets(); - insets.trim(area); - - AxisSpace space = calculateAxisSpace(g2, area); - Rectangle2D dataArea = space.shrink(area, null); - this.axisOffset.trim(dataArea); - - dataArea = integerise(dataArea); - if (dataArea.isEmpty()) { - return; - } - createAndAddEntity((Rectangle2D) dataArea.clone(), info, null, null); - if (info != null) { - info.setDataArea(dataArea); - } - - // draw the plot background and axes... - drawBackground(g2, dataArea); - Map axisStateMap = drawAxes(g2, area, dataArea, info); - - PlotOrientation orient = getOrientation(); - - // the anchor point is typically the point where the mouse last - // clicked - the crosshairs will be driven off this point... - if (anchor != null && !dataArea.contains(anchor)) { - anchor = null; - } - CrosshairState crosshairState = new CrosshairState(); - crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY); - crosshairState.setAnchor(anchor); - - crosshairState.setAnchorX(Double.NaN); - crosshairState.setAnchorY(Double.NaN); - if (anchor != null) { - ValueAxis domainAxis = getDomainAxis(); - if (domainAxis != null) { - double x; - if (orient == PlotOrientation.VERTICAL) { - x = domainAxis.java2DToValue(anchor.getX(), dataArea, - getDomainAxisEdge()); - } - else { - x = domainAxis.java2DToValue(anchor.getY(), dataArea, - getDomainAxisEdge()); - } - crosshairState.setAnchorX(x); - } - ValueAxis rangeAxis = getRangeAxis(); - if (rangeAxis != null) { - double y; - if (orient == PlotOrientation.VERTICAL) { - y = rangeAxis.java2DToValue(anchor.getY(), dataArea, - getRangeAxisEdge()); - } - else { - y = rangeAxis.java2DToValue(anchor.getX(), dataArea, - getRangeAxisEdge()); - } - crosshairState.setAnchorY(y); - } - } - crosshairState.setCrosshairX(getDomainCrosshairValue()); - crosshairState.setCrosshairY(getRangeCrosshairValue()); - Shape originalClip = g2.getClip(); - Composite originalComposite = g2.getComposite(); - - g2.clip(dataArea); - g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - getForegroundAlpha())); - - AxisState domainAxisState = axisStateMap.get(getDomainAxis()); - if (domainAxisState == null) { - if (parentState != null) { - domainAxisState = (AxisState) parentState.getSharedAxisStates() - .get(getDomainAxis()); - } - } - - AxisState rangeAxisState = axisStateMap.get(getRangeAxis()); - if (rangeAxisState == null) { - if (parentState != null) { - rangeAxisState = (AxisState) parentState.getSharedAxisStates() - .get(getRangeAxis()); - } - } - if (domainAxisState != null) { - drawDomainTickBands(g2, dataArea, domainAxisState.getTicks()); - } - if (rangeAxisState != null) { - drawRangeTickBands(g2, dataArea, rangeAxisState.getTicks()); - } - if (domainAxisState != null) { - drawDomainGridlines(g2, dataArea, domainAxisState.getTicks()); - drawZeroDomainBaseline(g2, dataArea); - } - if (rangeAxisState != null) { - drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks()); - drawZeroRangeBaseline(g2, dataArea); - } - - Graphics2D savedG2 = g2; - BufferedImage dataImage = null; - boolean suppressShadow = Boolean.TRUE.equals(g2.getRenderingHint( - JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION)); - if (this.shadowGenerator != null && !suppressShadow) { - dataImage = new BufferedImage((int) dataArea.getWidth(), - (int)dataArea.getHeight(), BufferedImage.TYPE_INT_ARGB); - g2 = dataImage.createGraphics(); - g2.translate(-dataArea.getX(), -dataArea.getY()); - g2.setRenderingHints(savedG2.getRenderingHints()); - } - - // draw the markers that are associated with a specific dataset... - for (XYDataset dataset: this.datasets.values()) { - int datasetIndex = indexOf(dataset); - drawDomainMarkers(g2, dataArea, datasetIndex, Layer.BACKGROUND); - } - for (XYDataset dataset: this.datasets.values()) { - int datasetIndex = indexOf(dataset); - drawRangeMarkers(g2, dataArea, datasetIndex, Layer.BACKGROUND); - } - - // now draw annotations and render data items... - boolean foundData = false; - DatasetRenderingOrder order = getDatasetRenderingOrder(); - List rendererIndices = getRendererIndices(order); - List datasetIndices = getDatasetIndices(order); - - // draw background annotations - for (int i : rendererIndices) { - XYItemRenderer renderer = getRenderer(i); - if (renderer != null) { - ValueAxis domainAxis = getDomainAxisForDataset(i); - ValueAxis rangeAxis = getRangeAxisForDataset(i); - renderer.drawAnnotations(g2, dataArea, domainAxis, rangeAxis, - Layer.BACKGROUND, info); - } - } - - // render data items... - for (int datasetIndex : datasetIndices) { - XYDataset dataset = this.getDataset(datasetIndex); - foundData = render(g2, dataArea, datasetIndex, info, - crosshairState) || foundData; - } - - // draw foreground annotations - for (int i : rendererIndices) { - XYItemRenderer renderer = getRenderer(i); - if (renderer != null) { - ValueAxis domainAxis = getDomainAxisForDataset(i); - ValueAxis rangeAxis = getRangeAxisForDataset(i); - renderer.drawAnnotations(g2, dataArea, domainAxis, rangeAxis, - Layer.FOREGROUND, info); - } - } - - // draw domain crosshair if required... - int datasetIndex = crosshairState.getDatasetIndex(); - ValueAxis xAxis = getDomainAxisForDataset(datasetIndex); - RectangleEdge xAxisEdge = getDomainAxisEdge(getDomainAxisIndex(xAxis)); - if (!this.domainCrosshairLockedOnData && anchor != null) { - double xx; - if (orient == PlotOrientation.VERTICAL) { - xx = xAxis.java2DToValue(anchor.getX(), dataArea, xAxisEdge); - } - else { - xx = xAxis.java2DToValue(anchor.getY(), dataArea, xAxisEdge); - } - crosshairState.setCrosshairX(xx); - } - setDomainCrosshairValue(crosshairState.getCrosshairX(), false); - if (isDomainCrosshairVisible()) { - double x = getDomainCrosshairValue(); - Paint paint = getDomainCrosshairPaint(); - Stroke stroke = getDomainCrosshairStroke(); - drawDomainCrosshair(g2, dataArea, orient, x, xAxis, stroke, paint); - } - - // draw range crosshair if required... - ValueAxis yAxis = getRangeAxisForDataset(datasetIndex); - RectangleEdge yAxisEdge = getRangeAxisEdge(getRangeAxisIndex(yAxis)); - if (!this.rangeCrosshairLockedOnData && anchor != null) { - double yy; - if (orient == PlotOrientation.VERTICAL) { - yy = yAxis.java2DToValue(anchor.getY(), dataArea, yAxisEdge); - } else { - yy = yAxis.java2DToValue(anchor.getX(), dataArea, yAxisEdge); - } - crosshairState.setCrosshairY(yy); - } - setRangeCrosshairValue(crosshairState.getCrosshairY(), false); - if (isRangeCrosshairVisible()) { - double y = getRangeCrosshairValue(); - Paint paint = getRangeCrosshairPaint(); - Stroke stroke = getRangeCrosshairStroke(); - drawRangeCrosshair(g2, dataArea, orient, y, yAxis, stroke, paint); - } - - if (!foundData) { - drawNoDataMessage(g2, dataArea); - } - - for (int i : rendererIndices) { - drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND); - } - for (int i : rendererIndices) { - drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND); - } - - drawAnnotations(g2, dataArea, info); - if (this.shadowGenerator != null && !suppressShadow) { - BufferedImage shadowImage - = this.shadowGenerator.createDropShadow(dataImage); - g2 = savedG2; - g2.drawImage(shadowImage, (int) dataArea.getX() - + this.shadowGenerator.calculateOffsetX(), - (int) dataArea.getY() - + this.shadowGenerator.calculateOffsetY(), null); - g2.drawImage(dataImage, (int) dataArea.getX(), - (int) dataArea.getY(), null); - } - g2.setClip(originalClip); - g2.setComposite(originalComposite); - - drawOutline(g2, dataArea); - - } - - /** - * Returns the indices of the non-null datasets in the specified order. - * - * @param order the order ({@code null} not permitted). - * - * @return The list of indices. - */ - private List getDatasetIndices(DatasetRenderingOrder order) { - List result = new ArrayList<>(); - for (Entry entry : this.datasets.entrySet()) { - if (entry.getValue() != null) { - result.add(entry.getKey()); - } - } - Collections.sort(result); - if (order == DatasetRenderingOrder.REVERSE) { - Collections.reverse(result); - } - return result; - } - - private List getRendererIndices(DatasetRenderingOrder order) { - List result = new ArrayList<>(); - for (Entry entry : this.renderers.entrySet()) { - if (entry.getValue() != null) { - result.add(entry.getKey()); - } - } - Collections.sort(result); - if (order == DatasetRenderingOrder.REVERSE) { - Collections.reverse(result); - } - return result; - } - - /** - * Draws the background for the plot. - * - * @param g2 the graphics device. - * @param area the area. - */ - @Override - public void drawBackground(Graphics2D g2, Rectangle2D area) { - fillBackground(g2, area, this.orientation); - drawQuadrants(g2, area); - drawBackgroundImage(g2, area); - } - - /** - * Draws the quadrants. - * - * @param g2 the graphics device. - * @param area the area. - * - * @see #setQuadrantOrigin(Point2D) - * @see #setQuadrantPaint(int, Paint) - */ - protected void drawQuadrants(Graphics2D g2, Rectangle2D area) { - // 0 | 1 - // --+-- - // 2 | 3 - boolean somethingToDraw = false; - - ValueAxis xAxis = getDomainAxis(); - if (xAxis == null) { // we can't draw quadrants without a valid x-axis - return; - } - double x = xAxis.getRange().constrain(this.quadrantOrigin.getX()); - double xx = xAxis.valueToJava2D(x, area, getDomainAxisEdge()); - - ValueAxis yAxis = getRangeAxis(); - if (yAxis == null) { // we can't draw quadrants without a valid y-axis - return; - } - double y = yAxis.getRange().constrain(this.quadrantOrigin.getY()); - double yy = yAxis.valueToJava2D(y, area, getRangeAxisEdge()); - - double xmin = xAxis.getLowerBound(); - double xxmin = xAxis.valueToJava2D(xmin, area, getDomainAxisEdge()); - - double xmax = xAxis.getUpperBound(); - double xxmax = xAxis.valueToJava2D(xmax, area, getDomainAxisEdge()); - - double ymin = yAxis.getLowerBound(); - double yymin = yAxis.valueToJava2D(ymin, area, getRangeAxisEdge()); - - double ymax = yAxis.getUpperBound(); - double yymax = yAxis.valueToJava2D(ymax, area, getRangeAxisEdge()); - - Rectangle2D[] r = new Rectangle2D[] {null, null, null, null}; - if (this.quadrantPaint[0] != null) { - if (x > xmin && y < ymax) { - if (this.orientation == PlotOrientation.HORIZONTAL) { - r[0] = new Rectangle2D.Double(Math.min(yymax, yy), - Math.min(xxmin, xx), Math.abs(yy - yymax), - Math.abs(xx - xxmin)); - } - else { // PlotOrientation.VERTICAL - r[0] = new Rectangle2D.Double(Math.min(xxmin, xx), - Math.min(yymax, yy), Math.abs(xx - xxmin), - Math.abs(yy - yymax)); - } - somethingToDraw = true; - } - } - if (this.quadrantPaint[1] != null) { - if (x < xmax && y < ymax) { - if (this.orientation == PlotOrientation.HORIZONTAL) { - r[1] = new Rectangle2D.Double(Math.min(yymax, yy), - Math.min(xxmax, xx), Math.abs(yy - yymax), - Math.abs(xx - xxmax)); - } - else { // PlotOrientation.VERTICAL - r[1] = new Rectangle2D.Double(Math.min(xx, xxmax), - Math.min(yymax, yy), Math.abs(xx - xxmax), - Math.abs(yy - yymax)); - } - somethingToDraw = true; - } - } - if (this.quadrantPaint[2] != null) { - if (x > xmin && y > ymin) { - if (this.orientation == PlotOrientation.HORIZONTAL) { - r[2] = new Rectangle2D.Double(Math.min(yymin, yy), - Math.min(xxmin, xx), Math.abs(yy - yymin), - Math.abs(xx - xxmin)); - } - else { // PlotOrientation.VERTICAL - r[2] = new Rectangle2D.Double(Math.min(xxmin, xx), - Math.min(yymin, yy), Math.abs(xx - xxmin), - Math.abs(yy - yymin)); - } - somethingToDraw = true; - } - } - if (this.quadrantPaint[3] != null) { - if (x < xmax && y > ymin) { - if (this.orientation == PlotOrientation.HORIZONTAL) { - r[3] = new Rectangle2D.Double(Math.min(yymin, yy), - Math.min(xxmax, xx), Math.abs(yy - yymin), - Math.abs(xx - xxmax)); - } - else { // PlotOrientation.VERTICAL - r[3] = new Rectangle2D.Double(Math.min(xx, xxmax), - Math.min(yymin, yy), Math.abs(xx - xxmax), - Math.abs(yy - yymin)); - } - somethingToDraw = true; - } - } - if (somethingToDraw) { - Composite originalComposite = g2.getComposite(); - g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - getBackgroundAlpha())); - for (int i = 0; i < 4; i++) { - if (this.quadrantPaint[i] != null && r[i] != null) { - g2.setPaint(this.quadrantPaint[i]); - g2.fill(r[i]); - } - } - g2.setComposite(originalComposite); - } - } - - /** - * Draws the domain tick bands, if any. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param ticks the ticks. - * - * @see #setDomainTickBandPaint(Paint) - */ - public void drawDomainTickBands(Graphics2D g2, Rectangle2D dataArea, - List ticks) { - Paint bandPaint = getDomainTickBandPaint(); - if (bandPaint != null) { - boolean fillBand = false; - ValueAxis xAxis = getDomainAxis(); - double previous = xAxis.getLowerBound(); - for (ValueTick tick : ticks) { - double current = tick.getValue(); - if (fillBand) { - getRenderer().fillDomainGridBand(g2, this, xAxis, dataArea, - previous, current); - } - previous = current; - fillBand = !fillBand; - } - double end = xAxis.getUpperBound(); - if (fillBand) { - getRenderer().fillDomainGridBand(g2, this, xAxis, dataArea, - previous, end); - } - } - } - - /** - * Draws the range tick bands, if any. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param ticks the ticks. - * - * @see #setRangeTickBandPaint(Paint) - */ - public void drawRangeTickBands(Graphics2D g2, Rectangle2D dataArea, - List ticks) { - Paint bandPaint = getRangeTickBandPaint(); - if (bandPaint != null) { - boolean fillBand = false; - ValueAxis axis = getRangeAxis(); - double previous = axis.getLowerBound(); - for (ValueTick tick : ticks) { - double current = tick.getValue(); - if (fillBand) { - getRenderer().fillRangeGridBand(g2, this, axis, dataArea, - previous, current); - } - previous = current; - fillBand = !fillBand; - } - double end = axis.getUpperBound(); - if (fillBand) { - getRenderer().fillRangeGridBand(g2, this, axis, dataArea, - previous, end); - } - } - } - - /** - * A utility method for drawing the axes. - * - * @param g2 the graphics device ({@code null} not permitted). - * @param plotArea the plot area ({@code null} not permitted). - * @param dataArea the data area ({@code null} not permitted). - * @param plotState collects information about the plot ({@code null} - * permitted). - * - * @return A map containing the state for each axis drawn. - */ - protected Map drawAxes(Graphics2D g2, Rectangle2D plotArea, - Rectangle2D dataArea, PlotRenderingInfo plotState) { - - AxisCollection axisCollection = new AxisCollection(); - - // add domain axes to lists... - for (ValueAxis axis : this.domainAxes.values()) { - if (axis != null) { - int axisIndex = findDomainAxisIndex(axis); - axisCollection.add(axis, getDomainAxisEdge(axisIndex)); - } - } - - // add range axes to lists... - for (ValueAxis axis : this.rangeAxes.values()) { - if (axis != null) { - int axisIndex = findRangeAxisIndex(axis); - axisCollection.add(axis, getRangeAxisEdge(axisIndex)); - } - } - - Map axisStateMap = new HashMap<>(); - - // draw the top axes - double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset( - dataArea.getHeight()); - Iterator iterator = axisCollection.getAxesAtTop().iterator(); - while (iterator.hasNext()) { - ValueAxis axis = (ValueAxis) iterator.next(); - AxisState info = axis.draw(g2, cursor, plotArea, dataArea, - RectangleEdge.TOP, plotState); - cursor = info.getCursor(); - axisStateMap.put(axis, info); - } - - // draw the bottom axes - cursor = dataArea.getMaxY() - + this.axisOffset.calculateBottomOutset(dataArea.getHeight()); - iterator = axisCollection.getAxesAtBottom().iterator(); - while (iterator.hasNext()) { - ValueAxis axis = (ValueAxis) iterator.next(); - AxisState info = axis.draw(g2, cursor, plotArea, dataArea, - RectangleEdge.BOTTOM, plotState); - cursor = info.getCursor(); - axisStateMap.put(axis, info); - } - - // draw the left axes - cursor = dataArea.getMinX() - - this.axisOffset.calculateLeftOutset(dataArea.getWidth()); - iterator = axisCollection.getAxesAtLeft().iterator(); - while (iterator.hasNext()) { - ValueAxis axis = (ValueAxis) iterator.next(); - AxisState info = axis.draw(g2, cursor, plotArea, dataArea, - RectangleEdge.LEFT, plotState); - cursor = info.getCursor(); - axisStateMap.put(axis, info); - } - - // draw the right axes - cursor = dataArea.getMaxX() - + this.axisOffset.calculateRightOutset(dataArea.getWidth()); - iterator = axisCollection.getAxesAtRight().iterator(); - while (iterator.hasNext()) { - ValueAxis axis = (ValueAxis) iterator.next(); - AxisState info = axis.draw(g2, cursor, plotArea, dataArea, - RectangleEdge.RIGHT, plotState); - cursor = info.getCursor(); - axisStateMap.put(axis, info); - } - - return axisStateMap; - } - - /** - * Draws a representation of the data within the dataArea region, using the - * current renderer. - *

- * The {@code info} and {@code crosshairState} arguments may be - * {@code null}. - * - * @param g2 the graphics device. - * @param dataArea the region in which the data is to be drawn. - * @param index the dataset index. - * @param info an optional object for collection dimension information. - * @param crosshairState collects crosshair information - * ({@code null} permitted). - * - * @return A flag that indicates whether any data was actually rendered. - */ - public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, - PlotRenderingInfo info, CrosshairState crosshairState) { - - boolean foundData = false; - XYDataset dataset = getDataset(index); - if (!DatasetUtils.isEmptyOrNull(dataset)) { - foundData = true; - ValueAxis xAxis = getDomainAxisForDataset(index); - ValueAxis yAxis = getRangeAxisForDataset(index); - if (xAxis == null || yAxis == null) { - return foundData; // can't render anything without axes - } - XYItemRenderer renderer = getRenderer(index); - if (renderer == null) { - renderer = getRenderer(); - if (renderer == null) { // no default renderer available - return foundData; - } - } - - XYItemRendererState state = renderer.initialise(g2, dataArea, this, - dataset, info); - int passCount = renderer.getPassCount(); - - SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder(); - if (seriesOrder == SeriesRenderingOrder.REVERSE) { - //render series in reverse order - for (int pass = 0; pass < passCount; pass++) { - int seriesCount = dataset.getSeriesCount(); - for (int series = seriesCount - 1; series >= 0; series--) { - int firstItem = 0; - int lastItem = dataset.getItemCount(series) - 1; - if (lastItem == -1) { - continue; - } - if (state.getProcessVisibleItemsOnly()) { - int[] itemBounds = RendererUtils.findLiveItems( - dataset, series, xAxis.getLowerBound(), - xAxis.getUpperBound()); - firstItem = Math.max(itemBounds[0] - 1, 0); - lastItem = Math.min(itemBounds[1] + 1, lastItem); - } - state.startSeriesPass(dataset, series, firstItem, - lastItem, pass, passCount); - for (int item = firstItem; item <= lastItem; item++) { - renderer.drawItem(g2, state, dataArea, info, - this, xAxis, yAxis, dataset, series, item, - crosshairState, pass); - } - state.endSeriesPass(dataset, series, firstItem, - lastItem, pass, passCount); - } - } - } - else { - //render series in forward order - for (int pass = 0; pass < passCount; pass++) { - int seriesCount = dataset.getSeriesCount(); - for (int series = 0; series < seriesCount; series++) { - int firstItem = 0; - int lastItem = dataset.getItemCount(series) - 1; - if (state.getProcessVisibleItemsOnly()) { - int[] itemBounds = RendererUtils.findLiveItems( - dataset, series, xAxis.getLowerBound(), - xAxis.getUpperBound()); - firstItem = Math.max(itemBounds[0] - 1, 0); - lastItem = Math.min(itemBounds[1] + 1, lastItem); - } - state.startSeriesPass(dataset, series, firstItem, - lastItem, pass, passCount); - for (int item = firstItem; item <= lastItem; item++) { - renderer.drawItem(g2, state, dataArea, info, - this, xAxis, yAxis, dataset, series, item, - crosshairState, pass); - } - state.endSeriesPass(dataset, series, firstItem, - lastItem, pass, passCount); - } - } - } - } - return foundData; - } - - /** - * Returns the domain axis for a dataset. - * - * @param index the dataset index (must be >= 0). - * - * @return The axis. - */ - public ValueAxis getDomainAxisForDataset(int index) { - Args.requireNonNegative(index, "index"); - ValueAxis valueAxis; - List axisIndices = this.datasetToDomainAxesMap.get(index); - if (axisIndices != null) { - // the first axis in the list is used for data <--> Java2D - Integer axisIndex = axisIndices.get(0); - valueAxis = getDomainAxis(axisIndex); - } - else { - valueAxis = getDomainAxis(0); - } - return valueAxis; - } - - /** - * Returns the range axis for a dataset. - * - * @param index the dataset index (must be >= 0). - * - * @return The axis. - */ - public ValueAxis getRangeAxisForDataset(int index) { - Args.requireNonNegative(index, "index"); - ValueAxis valueAxis; - List axisIndices = this.datasetToRangeAxesMap.get(index); - if (axisIndices != null) { - // the first axis in the list is used for data <--> Java2D - Integer axisIndex = axisIndices.get(0); - valueAxis = getRangeAxis(axisIndex); - } - else { - valueAxis = getRangeAxis(0); - } - return valueAxis; - } - - /** - * Draws the gridlines for the plot, if they are visible. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param ticks the ticks. - * - * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List) - */ - protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea, - List ticks) { - - // no renderer, no gridlines... - if (getRenderer() == null) { - return; - } - - // draw the domain grid lines, if any... - if (isDomainGridlinesVisible() || isDomainMinorGridlinesVisible()) { - Stroke gridStroke = null; - Paint gridPaint = null; - Iterator iterator = ticks.iterator(); - boolean paintLine; - while (iterator.hasNext()) { - paintLine = false; - ValueTick tick = (ValueTick) iterator.next(); - if ((tick.getTickType() == TickType.MINOR) - && isDomainMinorGridlinesVisible()) { - gridStroke = getDomainMinorGridlineStroke(); - gridPaint = getDomainMinorGridlinePaint(); - paintLine = true; - } else if ((tick.getTickType() == TickType.MAJOR) - && isDomainGridlinesVisible()) { - gridStroke = getDomainGridlineStroke(); - gridPaint = getDomainGridlinePaint(); - paintLine = true; - } - XYItemRenderer r = getRenderer(); - if ((r instanceof AbstractXYItemRenderer) && paintLine) { - ((AbstractXYItemRenderer) r).drawDomainLine(g2, this, - getDomainAxis(), dataArea, tick.getValue(), - gridPaint, gridStroke); - } - } - } - } - - /** - * Draws the gridlines for the plot's primary range axis, if they are - * visible. - * - * @param g2 the graphics device. - * @param area the data area. - * @param ticks the ticks. - * - * @see #drawDomainGridlines(Graphics2D, Rectangle2D, List) - */ - protected void drawRangeGridlines(Graphics2D g2, Rectangle2D area, - List ticks) { - - // no renderer, no gridlines... - if (getRenderer() == null) { - return; - } - - // draw the range grid lines, if any... - if (isRangeGridlinesVisible() || isRangeMinorGridlinesVisible()) { - Stroke gridStroke = null; - Paint gridPaint = null; - ValueAxis axis = getRangeAxis(); - if (axis != null) { - for (ValueTick tick : ticks) { - boolean paintLine = false; - if ((tick.getTickType() == TickType.MINOR) - && isRangeMinorGridlinesVisible()) { - gridStroke = getRangeMinorGridlineStroke(); - gridPaint = getRangeMinorGridlinePaint(); - paintLine = true; - } else if ((tick.getTickType() == TickType.MAJOR) - && isRangeGridlinesVisible()) { - gridStroke = getRangeGridlineStroke(); - gridPaint = getRangeGridlinePaint(); - paintLine = true; - } - if ((tick.getValue() != 0.0 - || !isRangeZeroBaselineVisible()) && paintLine) { - getRenderer().drawRangeLine(g2, this, getRangeAxis(), - area, tick.getValue(), gridPaint, gridStroke); - } - } - } - } - } - - /** - * Draws a base line across the chart at value zero on the domain axis. - * - * @param g2 the graphics device. - * @param area the data area. - * - * @see #setDomainZeroBaselineVisible(boolean) - */ - protected void drawZeroDomainBaseline(Graphics2D g2, Rectangle2D area) { - if (isDomainZeroBaselineVisible() && getRenderer() != null) { - getRenderer().drawDomainLine(g2, this, getDomainAxis(), area, 0.0, - this.domainZeroBaselinePaint, - this.domainZeroBaselineStroke); - } - } - - /** - * Draws a base line across the chart at value zero on the range axis. - * - * @param g2 the graphics device. - * @param area the data area. - * - * @see #setRangeZeroBaselineVisible(boolean) - */ - protected void drawZeroRangeBaseline(Graphics2D g2, Rectangle2D area) { - if (isRangeZeroBaselineVisible()) { - getRenderer().drawRangeLine(g2, this, getRangeAxis(), area, 0.0, - this.rangeZeroBaselinePaint, this.rangeZeroBaselineStroke); - } - } - - /** - * Draws the annotations for the plot. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param info the chart rendering info. - */ - public void drawAnnotations(Graphics2D g2, Rectangle2D dataArea, - PlotRenderingInfo info) { - - for (XYAnnotation annotation : this.annotations) { - ValueAxis xAxis = getDomainAxis(); - ValueAxis yAxis = getRangeAxis(); - annotation.draw(g2, this, dataArea, xAxis, yAxis, 0, info); - } - - } - - /** - * Draws the domain markers (if any) for an axis and layer. This method is - * typically called from within the draw() method. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param index the dataset/renderer index. - * @param layer the layer (foreground or background). - */ - protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea, - int index, Layer layer) { - - XYItemRenderer r = getRenderer(index); - if (r == null) { - return; - } - // check that the renderer has a corresponding dataset (it doesn't - // matter if the dataset is null) - if (!this.datasets.containsKey(index)) { - return; - } - Collection markers = getDomainMarkers(index, layer); - ValueAxis axis = getDomainAxisForDataset(index); - if (markers != null && axis != null) { - for (Marker marker : markers) { - r.drawDomainMarker(g2, this, axis, marker, dataArea); - } - } - - } - - /** - * Draws the range markers (if any) for a renderer and layer. This method - * is typically called from within the draw() method. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param index the renderer index. - * @param layer the layer (foreground or background). - */ - protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea, - int index, Layer layer) { - - XYItemRenderer r = getRenderer(index); - if (r == null) { - return; - } - // check that the renderer has a corresponding dataset (it doesn't - // matter if the dataset is null) - if (!this.datasets.containsKey(index)) { - return; - } - Collection markers = getRangeMarkers(index, layer); - ValueAxis axis = getRangeAxisForDataset(index); - if (markers != null && axis != null) { - for (Marker marker : markers) { - r.drawRangeMarker(g2, this, axis, marker, dataArea); - } - } - } - - /** - * Returns the list of domain markers (read only) for the specified layer. - * - * @param layer the layer (foreground or background). - * - * @return The list of domain markers. - * - * @see #getRangeMarkers(Layer) - */ - public Collection getDomainMarkers(Layer layer) { - return getDomainMarkers(0, layer); - } - - /** - * Returns the list of range markers (read only) for the specified layer. - * - * @param layer the layer (foreground or background). - * - * @return The list of range markers. - * - * @see #getDomainMarkers(Layer) - */ - public Collection getRangeMarkers(Layer layer) { - return getRangeMarkers(0, layer); - } - - /** - * Returns a collection of domain markers for a particular renderer and - * layer. - * - * @param index the renderer index. - * @param layer the layer. - * - * @return A collection of markers (possibly {@code null}). - * - * @see #getRangeMarkers(int, Layer) - */ - public Collection getDomainMarkers(int index, Layer layer) { - Collection result = null; - if (layer == Layer.FOREGROUND) { - result = this.foregroundDomainMarkers.get(index); - } - else if (layer == Layer.BACKGROUND) { - result = this.backgroundDomainMarkers.get(index); - } - if (result != null) { - result = Collections.unmodifiableCollection(result); - } - return result; - } - - /** - * Returns a collection of range markers for a particular renderer and - * layer. - * - * @param index the renderer index. - * @param layer the layer. - * - * @return A collection of markers (possibly {@code null}). - * - * @see #getDomainMarkers(int, Layer) - */ - public Collection getRangeMarkers(int index, Layer layer) { - Collection result = null; - if (layer == Layer.FOREGROUND) { - result = this.foregroundRangeMarkers.get(index); - } - else if (layer == Layer.BACKGROUND) { - result = this.backgroundRangeMarkers.get(index); - } - if (result != null) { - result = Collections.unmodifiableCollection(result); - } - return result; - } - - /** - * Utility method for drawing a horizontal line across the data area of the - * plot. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param value the coordinate, where to draw the line. - * @param stroke the stroke to use. - * @param paint the paint to use. - */ - protected void drawHorizontalLine(Graphics2D g2, Rectangle2D dataArea, - double value, Stroke stroke, - Paint paint) { - - ValueAxis axis = getRangeAxis(); - if (getOrientation() == PlotOrientation.HORIZONTAL) { - axis = getDomainAxis(); - } - if (axis.getRange().contains(value)) { - double yy = axis.valueToJava2D(value, dataArea, RectangleEdge.LEFT); - Line2D line = new Line2D.Double(dataArea.getMinX(), yy, - dataArea.getMaxX(), yy); - g2.setStroke(stroke); - g2.setPaint(paint); - g2.draw(line); - } - - } - - /** - * Draws a domain crosshair. - * - * @param g2 the graphics target. - * @param dataArea the data area. - * @param orientation the plot orientation. - * @param value the crosshair value. - * @param axis the axis against which the value is measured. - * @param stroke the stroke used to draw the crosshair line. - * @param paint the paint used to draw the crosshair line. - */ - protected void drawDomainCrosshair(Graphics2D g2, Rectangle2D dataArea, - PlotOrientation orientation, double value, ValueAxis axis, - Stroke stroke, Paint paint) { - - if (!axis.getRange().contains(value)) { - return; - } - Line2D line; - if (orientation == PlotOrientation.VERTICAL) { - double xx = axis.valueToJava2D(value, dataArea, - RectangleEdge.BOTTOM); - line = new Line2D.Double(xx, dataArea.getMinY(), xx, - dataArea.getMaxY()); - } else { - double yy = axis.valueToJava2D(value, dataArea, - RectangleEdge.LEFT); - line = new Line2D.Double(dataArea.getMinX(), yy, - dataArea.getMaxX(), yy); - } - Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_NORMALIZE); - g2.setStroke(stroke); - g2.setPaint(paint); - g2.draw(line); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); - } - - /** - * Utility method for drawing a vertical line on the data area of the plot. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param value the coordinate, where to draw the line. - * @param stroke the stroke to use. - * @param paint the paint to use. - */ - protected void drawVerticalLine(Graphics2D g2, Rectangle2D dataArea, - double value, Stroke stroke, Paint paint) { - - ValueAxis axis = getDomainAxis(); - if (getOrientation() == PlotOrientation.HORIZONTAL) { - axis = getRangeAxis(); - } - if (axis.getRange().contains(value)) { - double xx = axis.valueToJava2D(value, dataArea, - RectangleEdge.BOTTOM); - Line2D line = new Line2D.Double(xx, dataArea.getMinY(), xx, - dataArea.getMaxY()); - g2.setStroke(stroke); - g2.setPaint(paint); - g2.draw(line); - } - - } - - /** - * Draws a range crosshair. - * - * @param g2 the graphics target. - * @param dataArea the data area. - * @param orientation the plot orientation. - * @param value the crosshair value. - * @param axis the axis against which the value is measured. - * @param stroke the stroke used to draw the crosshair line. - * @param paint the paint used to draw the crosshair line. - */ - protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea, - PlotOrientation orientation, double value, ValueAxis axis, - Stroke stroke, Paint paint) { - - if (!axis.getRange().contains(value)) { - return; - } - Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_NORMALIZE); - Line2D line; - if (orientation == PlotOrientation.HORIZONTAL) { - double xx = axis.valueToJava2D(value, dataArea, - RectangleEdge.BOTTOM); - line = new Line2D.Double(xx, dataArea.getMinY(), xx, - dataArea.getMaxY()); - } else { - double yy = axis.valueToJava2D(value, dataArea, RectangleEdge.LEFT); - line = new Line2D.Double(dataArea.getMinX(), yy, - dataArea.getMaxX(), yy); - } - g2.setStroke(stroke); - g2.setPaint(paint); - g2.draw(line); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); - } - - /** - * Handles a 'click' on the plot by updating the anchor values. - * - * @param x the x-coordinate, where the click occurred, in Java2D space. - * @param y the y-coordinate, where the click occurred, in Java2D space. - * @param info object containing information about the plot dimensions. - */ - @Override - public void handleClick(int x, int y, PlotRenderingInfo info) { - - Rectangle2D dataArea = info.getDataArea(); - if (dataArea.contains(x, y)) { - // set the anchor value for the horizontal axis... - ValueAxis xaxis = getDomainAxis(); - if (xaxis != null) { - double hvalue = xaxis.java2DToValue(x, info.getDataArea(), - getDomainAxisEdge()); - setDomainCrosshairValue(hvalue); - } - - // set the anchor value for the vertical axis... - ValueAxis yaxis = getRangeAxis(); - if (yaxis != null) { - double vvalue = yaxis.java2DToValue(y, info.getDataArea(), - getRangeAxisEdge()); - setRangeCrosshairValue(vvalue); - } - } - } - - /** - * A utility method that returns a list of datasets that are mapped to a - * particular axis. - * - * @param axisIndex the axis index ({@code null} not permitted). - * - * @return A list of datasets. - */ - private List getDatasetsMappedToDomainAxis(Integer axisIndex) { - Args.nullNotPermitted(axisIndex, "axisIndex"); - List result = new ArrayList<>(); - for (Entry entry : this.datasets.entrySet()) { - int index = entry.getKey(); - List mappedAxes = this.datasetToDomainAxesMap.get(index); - if (mappedAxes == null) { - if (axisIndex.equals(ZERO)) { - result.add(entry.getValue()); - } - } else { - if (mappedAxes.contains(axisIndex)) { - result.add(entry.getValue()); - } - } - } - return result; - } - - /** - * A utility method that returns a list of datasets that are mapped to a - * particular axis. - * - * @param axisIndex the axis index ({@code null} not permitted). - * - * @return A list of datasets. - */ - private List getDatasetsMappedToRangeAxis(Integer axisIndex) { - Args.nullNotPermitted(axisIndex, "axisIndex"); - List result = new ArrayList<>(); - for (Entry entry : this.datasets.entrySet()) { - int index = entry.getKey(); - List mappedAxes = this.datasetToRangeAxesMap.get(index); - if (mappedAxes == null) { - if (axisIndex.equals(ZERO)) { - result.add(entry.getValue()); - } - } else { - if (mappedAxes.contains(axisIndex)) { - result.add(entry.getValue()); - } - } - } - return result; - } - - /** - * Returns the index of the given domain axis. - * - * @param axis the axis. - * - * @return The axis index. - * - * @see #getRangeAxisIndex(ValueAxis) - */ - public int getDomainAxisIndex(ValueAxis axis) { - int result = findDomainAxisIndex(axis); - if (result < 0) { - // try the parent plot - Plot parent = getParent(); - if (parent instanceof XYPlot) { - XYPlot p = (XYPlot) parent; - result = p.getDomainAxisIndex(axis); - } - } - return result; - } - - private int findDomainAxisIndex(ValueAxis axis) { - for (Map.Entry entry : this.domainAxes.entrySet()) { - if (entry.getValue() == axis) { - return entry.getKey(); - } - } - return -1; - } - - /** - * Returns the index of the given range axis. - * - * @param axis the axis. - * - * @return The axis index. - * - * @see #getDomainAxisIndex(ValueAxis) - */ - public int getRangeAxisIndex(ValueAxis axis) { - int result = findRangeAxisIndex(axis); - if (result < 0) { - // try the parent plot - Plot parent = getParent(); - if (parent instanceof XYPlot) { - XYPlot p = (XYPlot) parent; - result = p.getRangeAxisIndex(axis); - } - } - return result; - } - - private int findRangeAxisIndex(ValueAxis axis) { - for (Map.Entry entry : this.rangeAxes.entrySet()) { - if (entry.getValue() == axis) { - return entry.getKey(); - } - } - return -1; - } - - /** - * Returns the range for the specified axis. - * - * @param axis the axis. - * - * @return The range. - */ - @Override - public Range getDataRange(ValueAxis axis) { - Range result = null; - List mappedDatasets = new ArrayList<>(); - List includedAnnotations = new ArrayList<>(); - boolean isDomainAxis = true; - - // is it a domain axis? - int domainIndex = getDomainAxisIndex(axis); - if (domainIndex >= 0) { - isDomainAxis = true; - mappedDatasets.addAll(getDatasetsMappedToDomainAxis(domainIndex)); - if (domainIndex == 0) { - // grab the plot's annotations - for (XYAnnotation annotation : this.annotations) { - if (annotation instanceof XYAnnotationBoundsInfo) { - includedAnnotations.add(annotation); - } - } - } - } - - // or is it a range axis? - int rangeIndex = getRangeAxisIndex(axis); - if (rangeIndex >= 0) { - isDomainAxis = false; - mappedDatasets.addAll(getDatasetsMappedToRangeAxis(rangeIndex)); - if (rangeIndex == 0) { - Iterator iterator = this.annotations.iterator(); - while (iterator.hasNext()) { - XYAnnotation annotation = (XYAnnotation) iterator.next(); - if (annotation instanceof XYAnnotationBoundsInfo) { - includedAnnotations.add(annotation); - } - } - } - } - - // iterate through the datasets that map to the axis and get the union - // of the ranges. - for (XYDataset d : mappedDatasets) { - if (d != null) { - XYItemRenderer r = getRendererForDataset(d); - if (isDomainAxis) { - if (r != null) { - result = Range.combine(result, r.findDomainBounds(d)); - } - else { - result = Range.combine(result, - DatasetUtils.findDomainBounds(d)); - } - } - else { - if (r != null) { - result = Range.combine(result, r.findRangeBounds(d)); - } - else { - result = Range.combine(result, - DatasetUtils.findRangeBounds(d)); - } - } - // FIXME: the XYItemRenderer interface doesn't specify the - // getAnnotations() method but it should - if (r instanceof AbstractXYItemRenderer) { - AbstractXYItemRenderer rr = (AbstractXYItemRenderer) r; - Collection c = rr.getAnnotations(); - Iterator i = c.iterator(); - while (i.hasNext()) { - XYAnnotation a = (XYAnnotation) i.next(); - if (a instanceof XYAnnotationBoundsInfo) { - includedAnnotations.add(a); - } - } - } - } - } - - Iterator it = includedAnnotations.iterator(); - while (it.hasNext()) { - XYAnnotationBoundsInfo xyabi = (XYAnnotationBoundsInfo) it.next(); - if (xyabi.getIncludeInDataBounds()) { - if (isDomainAxis) { - result = Range.combine(result, xyabi.getXRange()); - } - else { - result = Range.combine(result, xyabi.getYRange()); - } - } - } - return result; - } - - /** - * Receives notification of a change to an {@link Annotation} added to - * this plot. - * - * @param event information about the event (not used here). - */ - @Override - public void annotationChanged(AnnotationChangeEvent event) { - if (getParent() != null) { - getParent().annotationChanged(event); - } - else { - PlotChangeEvent e = new PlotChangeEvent(this); - notifyListeners(e); - } - } - - /** - * Receives notification of a change to the plot's dataset. - *

- * The axis ranges are updated if necessary. - * - * @param event information about the event (not used here). - */ - @Override - public void datasetChanged(DatasetChangeEvent event) { - configureDomainAxes(); - configureRangeAxes(); - if (getParent() != null) { - getParent().datasetChanged(event); - } - else { - PlotChangeEvent e = new PlotChangeEvent(this); - e.setType(ChartChangeEventType.DATASET_UPDATED); - notifyListeners(e); - } - } - - /** - * Receives notification of a renderer change event. - * - * @param event the event. - */ - @Override - public void rendererChanged(RendererChangeEvent event) { - // if the event was caused by a change to series visibility, then - // the axis ranges might need updating... - if (event.getSeriesVisibilityChanged()) { - configureDomainAxes(); - configureRangeAxes(); - } - fireChangeEvent(); - } - - /** - * Returns a flag indicating whether or not the domain crosshair is visible. - * - * @return The flag. - * - * @see #setDomainCrosshairVisible(boolean) - */ - public boolean isDomainCrosshairVisible() { - return this.domainCrosshairVisible; - } - - /** - * Sets the flag indicating whether or not the domain crosshair is visible - * and, if the flag changes, sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param flag the new value of the flag. - * - * @see #isDomainCrosshairVisible() - */ - public void setDomainCrosshairVisible(boolean flag) { - if (this.domainCrosshairVisible != flag) { - this.domainCrosshairVisible = flag; - fireChangeEvent(); - } - } - - /** - * Returns a flag indicating whether or not the crosshair should "lock-on" - * to actual data values. - * - * @return The flag. - * - * @see #setDomainCrosshairLockedOnData(boolean) - */ - public boolean isDomainCrosshairLockedOnData() { - return this.domainCrosshairLockedOnData; - } - - /** - * Sets the flag indicating whether or not the domain crosshair should - * "lock-on" to actual data values. If the flag value changes, this - * method sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param flag the flag. - * - * @see #isDomainCrosshairLockedOnData() - */ - public void setDomainCrosshairLockedOnData(boolean flag) { - if (this.domainCrosshairLockedOnData != flag) { - this.domainCrosshairLockedOnData = flag; - fireChangeEvent(); - } - } - - /** - * Returns the domain crosshair value. - * - * @return The value. - * - * @see #setDomainCrosshairValue(double) - */ - public double getDomainCrosshairValue() { - return this.domainCrosshairValue; - } - - /** - * Sets the domain crosshair value and sends a {@link PlotChangeEvent} to - * all registered listeners (provided that the domain crosshair is visible). - * - * @param value the value. - * - * @see #getDomainCrosshairValue() - */ - public void setDomainCrosshairValue(double value) { - setDomainCrosshairValue(value, true); - } - - /** - * Sets the domain crosshair value and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners (provided that the - * domain crosshair is visible). - * - * @param value the new value. - * @param notify notify listeners? - * - * @see #getDomainCrosshairValue() - */ - public void setDomainCrosshairValue(double value, boolean notify) { - this.domainCrosshairValue = value; - if (isDomainCrosshairVisible() && notify) { - fireChangeEvent(); - } - } - - /** - * Returns the {@link Stroke} used to draw the crosshair (if visible). - * - * @return The crosshair stroke (never {@code null}). - * - * @see #setDomainCrosshairStroke(Stroke) - * @see #isDomainCrosshairVisible() - * @see #getDomainCrosshairPaint() - */ - public Stroke getDomainCrosshairStroke() { - return this.domainCrosshairStroke; - } - - /** - * Sets the Stroke used to draw the crosshairs (if visible) and notifies - * registered listeners that the axis has been modified. - * - * @param stroke the new crosshair stroke ({@code null} not - * permitted). - * - * @see #getDomainCrosshairStroke() - */ - public void setDomainCrosshairStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.domainCrosshairStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the domain crosshair paint. - * - * @return The crosshair paint (never {@code null}). - * - * @see #setDomainCrosshairPaint(Paint) - * @see #isDomainCrosshairVisible() - * @see #getDomainCrosshairStroke() - */ - public Paint getDomainCrosshairPaint() { - return this.domainCrosshairPaint; - } - - /** - * Sets the paint used to draw the crosshairs (if visible) and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the new crosshair paint ({@code null} not permitted). - * - * @see #getDomainCrosshairPaint() - */ - public void setDomainCrosshairPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.domainCrosshairPaint = paint; - fireChangeEvent(); - } - - /** - * Returns a flag indicating whether or not the range crosshair is visible. - * - * @return The flag. - * - * @see #setRangeCrosshairVisible(boolean) - * @see #isDomainCrosshairVisible() - */ - public boolean isRangeCrosshairVisible() { - return this.rangeCrosshairVisible; - } - - /** - * Sets the flag indicating whether or not the range crosshair is visible. - * If the flag value changes, this method sends a {@link PlotChangeEvent} - * to all registered listeners. - * - * @param flag the new value of the flag. - * - * @see #isRangeCrosshairVisible() - */ - public void setRangeCrosshairVisible(boolean flag) { - if (this.rangeCrosshairVisible != flag) { - this.rangeCrosshairVisible = flag; - fireChangeEvent(); - } - } - - /** - * Returns a flag indicating whether or not the crosshair should "lock-on" - * to actual data values. - * - * @return The flag. - * - * @see #setRangeCrosshairLockedOnData(boolean) - */ - public boolean isRangeCrosshairLockedOnData() { - return this.rangeCrosshairLockedOnData; - } - - /** - * Sets the flag indicating whether or not the range crosshair should - * "lock-on" to actual data values. If the flag value changes, this method - * sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param flag the flag. - * - * @see #isRangeCrosshairLockedOnData() - */ - public void setRangeCrosshairLockedOnData(boolean flag) { - if (this.rangeCrosshairLockedOnData != flag) { - this.rangeCrosshairLockedOnData = flag; - fireChangeEvent(); - } - } - - /** - * Returns the range crosshair value. - * - * @return The value. - * - * @see #setRangeCrosshairValue(double) - */ - public double getRangeCrosshairValue() { - return this.rangeCrosshairValue; - } - - /** - * Sets the range crosshair value. - *

- * Registered listeners are notified that the plot has been modified, but - * only if the crosshair is visible. - * - * @param value the new value. - * - * @see #getRangeCrosshairValue() - */ - public void setRangeCrosshairValue(double value) { - setRangeCrosshairValue(value, true); - } - - /** - * Sets the range crosshair value and sends a {@link PlotChangeEvent} to - * all registered listeners, but only if the crosshair is visible. - * - * @param value the new value. - * @param notify a flag that controls whether or not listeners are - * notified. - * - * @see #getRangeCrosshairValue() - */ - public void setRangeCrosshairValue(double value, boolean notify) { - this.rangeCrosshairValue = value; - if (isRangeCrosshairVisible() && notify) { - fireChangeEvent(); - } - } - - /** - * Returns the stroke used to draw the crosshair (if visible). - * - * @return The crosshair stroke (never {@code null}). - * - * @see #setRangeCrosshairStroke(Stroke) - * @see #isRangeCrosshairVisible() - * @see #getRangeCrosshairPaint() - */ - public Stroke getRangeCrosshairStroke() { - return this.rangeCrosshairStroke; - } - - /** - * Sets the stroke used to draw the crosshairs (if visible) and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param stroke the new crosshair stroke ({@code null} not - * permitted). - * - * @see #getRangeCrosshairStroke() - */ - public void setRangeCrosshairStroke(Stroke stroke) { - Args.nullNotPermitted(stroke, "stroke"); - this.rangeCrosshairStroke = stroke; - fireChangeEvent(); - } - - /** - * Returns the range crosshair paint. - * - * @return The crosshair paint (never {@code null}). - * - * @see #setRangeCrosshairPaint(Paint) - * @see #isRangeCrosshairVisible() - * @see #getRangeCrosshairStroke() - */ - public Paint getRangeCrosshairPaint() { - return this.rangeCrosshairPaint; - } - - /** - * Sets the paint used to color the crosshairs (if visible) and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param paint the new crosshair paint ({@code null} not permitted). - * - * @see #getRangeCrosshairPaint() - */ - public void setRangeCrosshairPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.rangeCrosshairPaint = paint; - fireChangeEvent(); - } - - /** - * Returns the fixed domain axis space. - * - * @return The fixed domain axis space (possibly {@code null}). - * - * @see #setFixedDomainAxisSpace(AxisSpace) - */ - public AxisSpace getFixedDomainAxisSpace() { - return this.fixedDomainAxisSpace; - } - - /** - * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param space the space ({@code null} permitted). - * - * @see #getFixedDomainAxisSpace() - */ - public void setFixedDomainAxisSpace(AxisSpace space) { - setFixedDomainAxisSpace(space, true); - } - - /** - * Sets the fixed domain axis space and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param space the space ({@code null} permitted). - * @param notify notify listeners? - * - * @see #getFixedDomainAxisSpace() - */ - public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) { - this.fixedDomainAxisSpace = space; - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns the fixed range axis space. - * - * @return The fixed range axis space (possibly {@code null}). - * - * @see #setFixedRangeAxisSpace(AxisSpace) - */ - public AxisSpace getFixedRangeAxisSpace() { - return this.fixedRangeAxisSpace; - } - - /** - * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param space the space ({@code null} permitted). - * - * @see #getFixedRangeAxisSpace() - */ - public void setFixedRangeAxisSpace(AxisSpace space) { - setFixedRangeAxisSpace(space, true); - } - - /** - * Sets the fixed range axis space and, if requested, sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param space the space ({@code null} permitted). - * @param notify notify listeners? - * - * @see #getFixedRangeAxisSpace() - */ - public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) { - this.fixedRangeAxisSpace = space; - if (notify) { - fireChangeEvent(); - } - } - - /** - * Returns {@code true} if panning is enabled for the domain axes, - * and {@code false} otherwise. - * - * @return A boolean. - */ - @Override - public boolean isDomainPannable() { - return this.domainPannable; - } - - /** - * Sets the flag that enables or disables panning of the plot along the - * domain axes. - * - * @param pannable the new flag value. - */ - public void setDomainPannable(boolean pannable) { - this.domainPannable = pannable; - } - - /** - * Returns {@code true} if panning is enabled for the range axis/axes, - * and {@code false} otherwise. The default value is {@code false}. - * - * @return A boolean. - */ - @Override - public boolean isRangePannable() { - return this.rangePannable; - } - - /** - * Sets the flag that enables or disables panning of the plot along - * the range axis/axes. - * - * @param pannable the new flag value. - */ - public void setRangePannable(boolean pannable) { - this.rangePannable = pannable; - } - - /** - * Pans the domain axes by the specified percentage. - * - * @param percent the distance to pan (as a percentage of the axis length). - * @param info the plot info - * @param source the source point where the pan action started. - */ - @Override - public void panDomainAxes(double percent, PlotRenderingInfo info, - Point2D source) { - if (!isDomainPannable()) { - return; - } - int domainAxisCount = getDomainAxisCount(); - for (int i = 0; i < domainAxisCount; i++) { - ValueAxis axis = getDomainAxis(i); - if (axis == null) { - continue; - } - - axis.pan(axis.isInverted() ? -percent : percent); - } - } - - /** - * Pans the range axes by the specified percentage. - * - * @param percent the distance to pan (as a percentage of the axis length). - * @param info the plot info - * @param source the source point where the pan action started. - */ - @Override - public void panRangeAxes(double percent, PlotRenderingInfo info, - Point2D source) { - if (!isRangePannable()) { - return; - } - int rangeAxisCount = getRangeAxisCount(); - for (int i = 0; i < rangeAxisCount; i++) { - ValueAxis axis = getRangeAxis(i); - if (axis == null) { - continue; - } - - axis.pan(axis.isInverted() ? -percent : percent); - } - } - - /** - * Multiplies the range on the domain axis/axes by the specified factor. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point (in Java2D space). - * - * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D) - */ - @Override - public void zoomDomainAxes(double factor, PlotRenderingInfo info, - Point2D source) { - // delegate to other method - zoomDomainAxes(factor, info, source, false); - } - - /** - * Multiplies the range on the domain axis/axes by the specified factor. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point (in Java2D space). - * @param useAnchor use source point as zoom anchor? - * - * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean) - */ - @Override - public void zoomDomainAxes(double factor, PlotRenderingInfo info, - Point2D source, boolean useAnchor) { - - // perform the zoom on each domain axis - for (ValueAxis xAxis : this.domainAxes.values()) { - if (xAxis == null) { - continue; - } - if (useAnchor) { - // get the relevant source coordinate given the plot orientation - double sourceX = source.getX(); - if (this.orientation == PlotOrientation.HORIZONTAL) { - sourceX = source.getY(); - } - double anchorX = xAxis.java2DToValue(sourceX, - info.getDataArea(), getDomainAxisEdge()); - xAxis.resizeRange2(factor, anchorX); - } else { - xAxis.resizeRange(factor); - } - } - } - - /** - * Zooms in on the domain axis/axes. The new lower and upper bounds are - * specified as percentages of the current axis range, where 0 percent is - * the current lower bound and 100 percent is the current upper bound. - * - * @param lowerPercent a percentage that determines the new lower bound - * for the axis (e.g. 0.20 is twenty percent). - * @param upperPercent a percentage that determines the new upper bound - * for the axis (e.g. 0.80 is eighty percent). - * @param info the plot rendering info. - * @param source the source point (ignored). - * - * @see #zoomRangeAxes(double, double, PlotRenderingInfo, Point2D) - */ - @Override - public void zoomDomainAxes(double lowerPercent, double upperPercent, - PlotRenderingInfo info, Point2D source) { - for (ValueAxis xAxis : this.domainAxes.values()) { - if (xAxis != null) { - xAxis.zoomRange(lowerPercent, upperPercent); - } - } - } - - /** - * Multiplies the range on the range axis/axes by the specified factor. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point. - * - * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) - */ - @Override - public void zoomRangeAxes(double factor, PlotRenderingInfo info, - Point2D source) { - // delegate to other method - zoomRangeAxes(factor, info, source, false); - } - - /** - * Multiplies the range on the range axis/axes by the specified factor. - * - * @param factor the zoom factor. - * @param info the plot rendering info. - * @param source the source point. - * @param useAnchor a flag that controls whether or not the source point - * is used for the zoom anchor. - * - * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) - */ - @Override - public void zoomRangeAxes(double factor, PlotRenderingInfo info, - Point2D source, boolean useAnchor) { - - // perform the zoom on each range axis - for (ValueAxis yAxis : this.rangeAxes.values()) { - if (yAxis == null) { - continue; - } - if (useAnchor) { - // get the relevant source coordinate given the plot orientation - double sourceY = source.getY(); - if (this.orientation == PlotOrientation.HORIZONTAL) { - sourceY = source.getX(); - } - double anchorY = yAxis.java2DToValue(sourceY, - info.getDataArea(), getRangeAxisEdge()); - yAxis.resizeRange2(factor, anchorY); - } else { - yAxis.resizeRange(factor); - } - } - } - - /** - * Zooms in on the range axes. - * - * @param lowerPercent the lower bound. - * @param upperPercent the upper bound. - * @param info the plot rendering info. - * @param source the source point. - * - * @see #zoomDomainAxes(double, double, PlotRenderingInfo, Point2D) - */ - @Override - public void zoomRangeAxes(double lowerPercent, double upperPercent, - PlotRenderingInfo info, Point2D source) { - for (ValueAxis yAxis : this.rangeAxes.values()) { - if (yAxis != null) { - yAxis.zoomRange(lowerPercent, upperPercent); - } - } - } - - /** - * Returns {@code true}, indicating that the domain axis/axes for this - * plot are zoomable. - * - * @return A boolean. - * - * @see #isRangeZoomable() - */ - @Override - public boolean isDomainZoomable() { - return true; - } - - /** - * Returns {@code true}, indicating that the range axis/axes for this - * plot are zoomable. - * - * @return A boolean. - * - * @see #isDomainZoomable() - */ - @Override - public boolean isRangeZoomable() { - return true; - } - - /** - * Returns the number of series in the primary dataset for this plot. If - * the dataset is {@code null}, the method returns 0. - * - * @return The series count. - */ - public int getSeriesCount() { - int result = 0; - XYDataset dataset = getDataset(); - if (dataset != null) { - result = dataset.getSeriesCount(); - } - return result; - } - - /** - * Returns the fixed legend items, if any. - * - * @return The legend items (possibly {@code null}). - * - * @see #setFixedLegendItems(LegendItemCollection) - */ - public LegendItemCollection getFixedLegendItems() { - return this.fixedLegendItems; - } - - /** - * Sets the fixed legend items for the plot. Leave this set to - * {@code null} if you prefer the legend items to be created - * automatically. - * - * @param items the legend items ({@code null} permitted). - * - * @see #getFixedLegendItems() - */ - public void setFixedLegendItems(LegendItemCollection items) { - this.fixedLegendItems = items; - fireChangeEvent(); - } - - /** - * Returns the legend items for the plot. Each legend item is generated by - * the plot's renderer, since the renderer is responsible for the visual - * representation of the data. - * - * @return The legend items. - */ - @Override - public LegendItemCollection getLegendItems() { - if (this.fixedLegendItems != null) { - return this.fixedLegendItems; - } - LegendItemCollection result = new LegendItemCollection(); - for (XYDataset dataset : this.datasets.values()) { - if (dataset == null) { - continue; - } - int datasetIndex = indexOf(dataset); - XYItemRenderer renderer = getRenderer(datasetIndex); - if (renderer == null) { - renderer = getRenderer(0); - } - if (renderer != null) { - int seriesCount = dataset.getSeriesCount(); - for (int i = 0; i < seriesCount; i++) { - if (renderer.isSeriesVisible(i) - && renderer.isSeriesVisibleInLegend(i)) { - LegendItem item = renderer.getLegendItem( - datasetIndex, i); - if (item != null) { - result.add(item); - } - } - } - } - } - return result; - } - - /** - * Tests this plot for equality with another object. - * - * @param obj the object ({@code null} permitted). - * - * @return {@code true} or {@code false}. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof XYPlot)) { - return false; - } - XYPlot that = (XYPlot) obj; - if (this.weight != that.weight) { - return false; - } - if (this.orientation != that.orientation) { - return false; - } - if (!this.domainAxes.equals(that.domainAxes)) { - return false; - } - if (!this.domainAxisLocations.equals(that.domainAxisLocations)) { - return false; - } - if (this.rangeCrosshairLockedOnData - != that.rangeCrosshairLockedOnData) { - return false; - } - if (this.domainGridlinesVisible != that.domainGridlinesVisible) { - return false; - } - if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) { - return false; - } - if (this.domainMinorGridlinesVisible - != that.domainMinorGridlinesVisible) { - return false; - } - if (this.rangeMinorGridlinesVisible - != that.rangeMinorGridlinesVisible) { - return false; - } - if (this.domainZeroBaselineVisible != that.domainZeroBaselineVisible) { - return false; - } - if (this.rangeZeroBaselineVisible != that.rangeZeroBaselineVisible) { - return false; - } - if (this.domainCrosshairVisible != that.domainCrosshairVisible) { - return false; - } - if (this.domainCrosshairValue != that.domainCrosshairValue) { - return false; - } - if (this.domainCrosshairLockedOnData - != that.domainCrosshairLockedOnData) { - return false; - } - if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) { - return false; - } - if (this.rangeCrosshairValue != that.rangeCrosshairValue) { - return false; - } - if (!Objects.equals(this.axisOffset, that.axisOffset)) { - return false; - } - if (!Objects.equals(this.renderers, that.renderers)) { - return false; - } - if (!Objects.equals(this.rangeAxes, that.rangeAxes)) { - return false; - } - if (!this.rangeAxisLocations.equals(that.rangeAxisLocations)) { - return false; - } - if (!Objects.equals(this.datasetToDomainAxesMap, - that.datasetToDomainAxesMap)) { - return false; - } - if (!Objects.equals(this.datasetToRangeAxesMap, - that.datasetToRangeAxesMap)) { - return false; - } - if (!Objects.equals(this.domainGridlineStroke, - that.domainGridlineStroke)) { - return false; - } - if (!PaintUtils.equal(this.domainGridlinePaint, - that.domainGridlinePaint)) { - return false; - } - if (!Objects.equals(this.rangeGridlineStroke, - that.rangeGridlineStroke)) { - return false; - } - if (!PaintUtils.equal(this.rangeGridlinePaint, - that.rangeGridlinePaint)) { - return false; - } - if (!Objects.equals(this.domainMinorGridlineStroke, - that.domainMinorGridlineStroke)) { - return false; - } - if (!PaintUtils.equal(this.domainMinorGridlinePaint, - that.domainMinorGridlinePaint)) { - return false; - } - if (!Objects.equals(this.rangeMinorGridlineStroke, - that.rangeMinorGridlineStroke)) { - return false; - } - if (!PaintUtils.equal(this.rangeMinorGridlinePaint, - that.rangeMinorGridlinePaint)) { - return false; - } - if (!PaintUtils.equal(this.domainZeroBaselinePaint, - that.domainZeroBaselinePaint)) { - return false; - } - if (!Objects.equals(this.domainZeroBaselineStroke, - that.domainZeroBaselineStroke)) { - return false; - } - if (!PaintUtils.equal(this.rangeZeroBaselinePaint, - that.rangeZeroBaselinePaint)) { - return false; - } - if (!Objects.equals(this.rangeZeroBaselineStroke, - that.rangeZeroBaselineStroke)) { - return false; - } - if (!Objects.equals(this.domainCrosshairStroke, - that.domainCrosshairStroke)) { - return false; - } - if (!PaintUtils.equal(this.domainCrosshairPaint, - that.domainCrosshairPaint)) { - return false; - } - if (!Objects.equals(this.rangeCrosshairStroke, - that.rangeCrosshairStroke)) { - return false; - } - if (!PaintUtils.equal(this.rangeCrosshairPaint, - that.rangeCrosshairPaint)) { - return false; - } - if (!Objects.equals(this.foregroundDomainMarkers, - that.foregroundDomainMarkers)) { - return false; - } - if (!Objects.equals(this.backgroundDomainMarkers, - that.backgroundDomainMarkers)) { - return false; - } - if (!Objects.equals(this.foregroundRangeMarkers, - that.foregroundRangeMarkers)) { - return false; - } - if (!Objects.equals(this.backgroundRangeMarkers, - that.backgroundRangeMarkers)) { - return false; - } - if (!Objects.equals(this.foregroundDomainMarkers, - that.foregroundDomainMarkers)) { - return false; - } - if (!Objects.equals(this.backgroundDomainMarkers, - that.backgroundDomainMarkers)) { - return false; - } - if (!Objects.equals(this.foregroundRangeMarkers, - that.foregroundRangeMarkers)) { - return false; - } - if (!Objects.equals(this.backgroundRangeMarkers, - that.backgroundRangeMarkers)) { - return false; - } - if (!Objects.equals(this.annotations, that.annotations)) { - return false; - } - if (!Objects.equals(this.fixedLegendItems, - that.fixedLegendItems)) { - return false; - } - if (!PaintUtils.equal(this.domainTickBandPaint, - that.domainTickBandPaint)) { - return false; - } - if (!PaintUtils.equal(this.rangeTickBandPaint, - that.rangeTickBandPaint)) { - return false; - } - if (!this.quadrantOrigin.equals(that.quadrantOrigin)) { - return false; - } - for (int i = 0; i < 4; i++) { - if (!PaintUtils.equal(this.quadrantPaint[i], - that.quadrantPaint[i])) { - return false; - } - } - if (!Objects.equals(this.shadowGenerator, - that.shadowGenerator)) { - return false; - } - return super.equals(obj); - } - - /** - * Returns a clone of the plot. - * - * @return A clone. - * - * @throws CloneNotSupportedException this can occur if some component of - * the plot cannot be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - XYPlot clone = (XYPlot) super.clone(); - clone.domainAxes = CloneUtils.cloneMapValues(this.domainAxes); - for (ValueAxis axis : clone.domainAxes.values()) { - if (axis != null) { - axis.setPlot(clone); - axis.addChangeListener(clone); - } - } - clone.rangeAxes = CloneUtils.cloneMapValues(this.rangeAxes); - for (ValueAxis axis : clone.rangeAxes.values()) { - if (axis != null) { - axis.setPlot(clone); - axis.addChangeListener(clone); - } - } - clone.domainAxisLocations = new HashMap<>(this.domainAxisLocations); - clone.rangeAxisLocations = new HashMap<>(this.rangeAxisLocations); - - // the datasets are not cloned, but listeners need to be added... - clone.datasets = new HashMap<>(this.datasets); - for (XYDataset dataset : clone.datasets.values()) { - if (dataset != null) { - dataset.addChangeListener(clone); - } - } - - clone.datasetToDomainAxesMap = new TreeMap(); - clone.datasetToDomainAxesMap.putAll(this.datasetToDomainAxesMap); - clone.datasetToRangeAxesMap = new TreeMap(); - clone.datasetToRangeAxesMap.putAll(this.datasetToRangeAxesMap); - - clone.renderers = CloneUtils.cloneMapValues(this.renderers); - for (XYItemRenderer renderer : clone.renderers.values()) { - if (renderer != null) { - renderer.setPlot(clone); - renderer.addChangeListener(clone); - } - } - clone.foregroundDomainMarkers = (Map) ObjectUtils.clone( - this.foregroundDomainMarkers); - clone.backgroundDomainMarkers = (Map) ObjectUtils.clone( - this.backgroundDomainMarkers); - clone.foregroundRangeMarkers = (Map) ObjectUtils.clone( - this.foregroundRangeMarkers); - clone.backgroundRangeMarkers = (Map) ObjectUtils.clone( - this.backgroundRangeMarkers); - clone.annotations = (List) ObjectUtils.deepClone(this.annotations); - if (this.fixedDomainAxisSpace != null) { - clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtils.clone( - this.fixedDomainAxisSpace); - } - if (this.fixedRangeAxisSpace != null) { - clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtils.clone( - this.fixedRangeAxisSpace); - } - if (this.fixedLegendItems != null) { - clone.fixedLegendItems - = (LegendItemCollection) this.fixedLegendItems.clone(); - } - clone.quadrantOrigin = (Point2D) ObjectUtils.clone( - this.quadrantOrigin); - clone.quadrantPaint = this.quadrantPaint.clone(); - return clone; - - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writeStroke(this.domainGridlineStroke, stream); - SerialUtils.writePaint(this.domainGridlinePaint, stream); - SerialUtils.writeStroke(this.rangeGridlineStroke, stream); - SerialUtils.writePaint(this.rangeGridlinePaint, stream); - SerialUtils.writeStroke(this.domainMinorGridlineStroke, stream); - SerialUtils.writePaint(this.domainMinorGridlinePaint, stream); - SerialUtils.writeStroke(this.rangeMinorGridlineStroke, stream); - SerialUtils.writePaint(this.rangeMinorGridlinePaint, stream); - SerialUtils.writeStroke(this.rangeZeroBaselineStroke, stream); - SerialUtils.writePaint(this.rangeZeroBaselinePaint, stream); - SerialUtils.writeStroke(this.domainCrosshairStroke, stream); - SerialUtils.writePaint(this.domainCrosshairPaint, stream); - SerialUtils.writeStroke(this.rangeCrosshairStroke, stream); - SerialUtils.writePaint(this.rangeCrosshairPaint, stream); - SerialUtils.writePaint(this.domainTickBandPaint, stream); - SerialUtils.writePaint(this.rangeTickBandPaint, stream); - SerialUtils.writePoint2D(this.quadrantOrigin, stream); - for (int i = 0; i < 4; i++) { - SerialUtils.writePaint(this.quadrantPaint[i], stream); - } - SerialUtils.writeStroke(this.domainZeroBaselineStroke, stream); - SerialUtils.writePaint(this.domainZeroBaselinePaint, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - - stream.defaultReadObject(); - this.domainGridlineStroke = SerialUtils.readStroke(stream); - this.domainGridlinePaint = SerialUtils.readPaint(stream); - this.rangeGridlineStroke = SerialUtils.readStroke(stream); - this.rangeGridlinePaint = SerialUtils.readPaint(stream); - this.domainMinorGridlineStroke = SerialUtils.readStroke(stream); - this.domainMinorGridlinePaint = SerialUtils.readPaint(stream); - this.rangeMinorGridlineStroke = SerialUtils.readStroke(stream); - this.rangeMinorGridlinePaint = SerialUtils.readPaint(stream); - this.rangeZeroBaselineStroke = SerialUtils.readStroke(stream); - this.rangeZeroBaselinePaint = SerialUtils.readPaint(stream); - this.domainCrosshairStroke = SerialUtils.readStroke(stream); - this.domainCrosshairPaint = SerialUtils.readPaint(stream); - this.rangeCrosshairStroke = SerialUtils.readStroke(stream); - this.rangeCrosshairPaint = SerialUtils.readPaint(stream); - this.domainTickBandPaint = SerialUtils.readPaint(stream); - this.rangeTickBandPaint = SerialUtils.readPaint(stream); - this.quadrantOrigin = SerialUtils.readPoint2D(stream); - this.quadrantPaint = new Paint[4]; - for (int i = 0; i < 4; i++) { - this.quadrantPaint[i] = SerialUtils.readPaint(stream); - } - - this.domainZeroBaselineStroke = SerialUtils.readStroke(stream); - this.domainZeroBaselinePaint = SerialUtils.readPaint(stream); - - // register the plot as a listener with its axes, datasets, and - // renderers... - for (ValueAxis axis : this.domainAxes.values()) { - if (axis != null) { - axis.setPlot(this); - axis.addChangeListener(this); - } - } - for (ValueAxis axis : this.rangeAxes.values()) { - if (axis != null) { - axis.setPlot(this); - axis.addChangeListener(this); - } - } - for (XYDataset dataset : this.datasets.values()) { - if (dataset != null) { - dataset.addChangeListener(this); - } - } - for (XYItemRenderer renderer : this.renderers.values()) { - if (renderer != null) { - renderer.addChangeListener(this); - } - } - - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ----------- + * XYPlot.java + * ----------- + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): Craig MacFarlane; + * Mark Watson (www.markwatson.com); + * Jonathan Nash; + * Gideon Krause; + * Klaus Rheinwald; + * Xavier Poinsard; + * Richard Atkinson; + * Arnaud Lelievre; + * Nicolas Brodu; + * Eduardo Ramalho; + * Sergei Ivanov; + * Richard West, Advanced Micro Devices, Inc.; + * Ulrich Voigt - patches 1997549 and 2686040; + * Peter Kolb - patches 1934255, 2603321 and 2809117; + * Andrew Mickish - patch 1868749; + * + */ + +package org.jfree.chart.plot; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.TreeMap; +import org.jfree.chart.JFreeChart; + +import org.jfree.chart.LegendItem; +import org.jfree.chart.LegendItemCollection; +import org.jfree.chart.annotations.XYAnnotation; +import org.jfree.chart.annotations.XYAnnotationBoundsInfo; +import org.jfree.chart.axis.Axis; +import org.jfree.chart.axis.AxisCollection; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.AxisSpace; +import org.jfree.chart.axis.AxisState; +import org.jfree.chart.axis.TickType; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.axis.ValueTick; +import org.jfree.chart.event.AnnotationChangeEvent; +import org.jfree.chart.event.ChartChangeEventType; +import org.jfree.chart.event.PlotChangeEvent; +import org.jfree.chart.event.RendererChangeEvent; +import org.jfree.chart.event.RendererChangeListener; +import org.jfree.chart.renderer.RendererUtils; +import org.jfree.chart.renderer.xy.AbstractXYItemRenderer; +import org.jfree.chart.renderer.xy.XYItemRenderer; +import org.jfree.chart.renderer.xy.XYItemRendererState; +import org.jfree.chart.ui.Layer; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.chart.util.CloneUtils; +import org.jfree.chart.util.ObjectUtils; +import org.jfree.chart.util.PaintUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.ResourceBundleWrapper; +import org.jfree.chart.util.SerialUtils; +import org.jfree.chart.util.ShadowGenerator; +import org.jfree.data.Range; +import org.jfree.data.general.DatasetChangeEvent; +import org.jfree.data.general.DatasetUtils; +import org.jfree.data.xy.XYDataset; + +/** + * A general class for plotting data in the form of (x, y) pairs. This plot can + * use data from any class that implements the {@link XYDataset} interface. + *

+ * {@code XYPlot} makes use of an {@link XYItemRenderer} to draw each point + * on the plot. By using different renderers, various chart types can be + * produced. + *

+ * The {@link org.jfree.chart.ChartFactory} class contains static methods for + * creating pre-configured charts. + */ +public class XYPlot extends Plot implements ValueAxisPlot, Pannable, Zoomable, + RendererChangeListener, Cloneable, PublicCloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = 7044148245716569264L; + + /** The default grid line stroke. */ + public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, + BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, + new float[] {2.0f, 2.0f}, 0.0f); + + /** The default grid line paint. */ + public static final Paint DEFAULT_GRIDLINE_PAINT = Color.LIGHT_GRAY; + + /** The default crosshair visibility. */ + public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false; + + /** The default crosshair stroke. */ + public static final Stroke DEFAULT_CROSSHAIR_STROKE + = DEFAULT_GRIDLINE_STROKE; + + /** The default crosshair paint. */ + public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.BLUE; + + /** The resourceBundle for the localization. */ + protected static ResourceBundle localizationResources + = ResourceBundleWrapper.getBundle( + "org.jfree.chart.plot.LocalizationBundle"); + + /** The plot orientation. */ + private PlotOrientation orientation; + + /** The offset between the data area and the axes. */ + private RectangleInsets axisOffset; + + /** The domain axis / axes (used for the x-values). */ + private Map domainAxes; + + /** The domain axis locations. */ + private Map domainAxisLocations; + + /** The range axis (used for the y-values). */ + private Map rangeAxes; + + /** The range axis location. */ + private Map rangeAxisLocations; + + /** Storage for the datasets. */ + private Map datasets; + + /** Storage for the renderers. */ + private Map renderers; + + /** + * Storage for the mapping between datasets/renderers and domain axes. The + * keys in the map are Integer objects, corresponding to the dataset + * index. The values in the map are List objects containing Integer + * objects (corresponding to the axis indices). If the map contains no + * entry for a dataset, it is assumed to map to the primary domain axis + * (index = 0). + */ + private Map> datasetToDomainAxesMap; + + /** + * Storage for the mapping between datasets/renderers and range axes. The + * keys in the map are Integer objects, corresponding to the dataset + * index. The values in the map are List objects containing Integer + * objects (corresponding to the axis indices). If the map contains no + * entry for a dataset, it is assumed to map to the primary domain axis + * (index = 0). + */ + private Map> datasetToRangeAxesMap; + + /** The origin point for the quadrants (if drawn). */ + private transient Point2D quadrantOrigin = new Point2D.Double(0.0, 0.0); + + /** The paint used for each quadrant. */ + private transient Paint[] quadrantPaint + = new Paint[] {null, null, null, null}; + + /** A flag that controls whether the domain grid-lines are visible. */ + private boolean domainGridlinesVisible; + + /** The stroke used to draw the domain grid-lines. */ + private transient Stroke domainGridlineStroke; + + /** The paint used to draw the domain grid-lines. */ + private transient Paint domainGridlinePaint; + + /** A flag that controls whether the range grid-lines are visible. */ + private boolean rangeGridlinesVisible; + + /** The stroke used to draw the range grid-lines. */ + private transient Stroke rangeGridlineStroke; + + /** The paint used to draw the range grid-lines. */ + private transient Paint rangeGridlinePaint; + + /** + * A flag that controls whether the domain minor grid-lines are visible. + */ + private boolean domainMinorGridlinesVisible; + + /** + * The stroke used to draw the domain minor grid-lines. + */ + private transient Stroke domainMinorGridlineStroke; + + /** + * The paint used to draw the domain minor grid-lines. + */ + private transient Paint domainMinorGridlinePaint; + + /** + * A flag that controls whether the range minor grid-lines are visible. + */ + private boolean rangeMinorGridlinesVisible; + + /** + * The stroke used to draw the range minor grid-lines. + */ + private transient Stroke rangeMinorGridlineStroke; + + /** + * The paint used to draw the range minor grid-lines. + */ + private transient Paint rangeMinorGridlinePaint; + + /** + * A flag that controls whether or not the zero baseline against the domain + * axis is visible. + */ + private boolean domainZeroBaselineVisible; + + /** + * The stroke used for the zero baseline against the domain axis. + */ + private transient Stroke domainZeroBaselineStroke; + + /** + * The paint used for the zero baseline against the domain axis. + */ + private transient Paint domainZeroBaselinePaint; + + /** + * A flag that controls whether or not the zero baseline against the range + * axis is visible. + */ + private boolean rangeZeroBaselineVisible; + + /** The stroke used for the zero baseline against the range axis. */ + private transient Stroke rangeZeroBaselineStroke; + + /** The paint used for the zero baseline against the range axis. */ + private transient Paint rangeZeroBaselinePaint; + + /** A flag that controls whether or not a domain crosshair is drawn..*/ + private boolean domainCrosshairVisible; + + /** The domain crosshair value. */ + private double domainCrosshairValue; + + /** The pen/brush used to draw the crosshair (if any). */ + private transient Stroke domainCrosshairStroke; + + /** The color used to draw the crosshair (if any). */ + private transient Paint domainCrosshairPaint; + + /** + * A flag that controls whether or not the crosshair locks onto actual + * data points. + */ + private boolean domainCrosshairLockedOnData = true; + + /** A flag that controls whether or not a range crosshair is drawn..*/ + private boolean rangeCrosshairVisible; + + /** The range crosshair value. */ + private double rangeCrosshairValue; + + /** The pen/brush used to draw the crosshair (if any). */ + private transient Stroke rangeCrosshairStroke; + + /** The color used to draw the crosshair (if any). */ + private transient Paint rangeCrosshairPaint; + + /** + * A flag that controls whether or not the crosshair locks onto actual + * data points. + */ + private boolean rangeCrosshairLockedOnData = true; + + /** A map of lists of foreground markers (optional) for the domain axes. */ + private Map> foregroundDomainMarkers; + + /** A map of lists of background markers (optional) for the domain axes. */ + private Map> backgroundDomainMarkers; + + /** A map of lists of foreground markers (optional) for the range axes. */ + private Map> foregroundRangeMarkers; + + /** A map of lists of background markers (optional) for the range axes. */ + private Map> backgroundRangeMarkers; + + /** + * A (possibly empty) list of annotations for the plot. The list should + * be initialised in the constructor and never allowed to be + * {@code null}. + */ + private List annotations; + + /** The paint used for the domain tick bands (if any). */ + private transient Paint domainTickBandPaint; + + /** The paint used for the range tick bands (if any). */ + private transient Paint rangeTickBandPaint; + + /** The fixed domain axis space. */ + private AxisSpace fixedDomainAxisSpace; + + /** The fixed range axis space. */ + private AxisSpace fixedRangeAxisSpace; + + /** + * The order of the dataset rendering (REVERSE draws the primary dataset + * last so that it appears to be on top). + */ + private DatasetRenderingOrder datasetRenderingOrder + = DatasetRenderingOrder.REVERSE; + + /** + * The order of the series rendering (REVERSE draws the primary series + * last so that it appears to be on top). + */ + private SeriesRenderingOrder seriesRenderingOrder + = SeriesRenderingOrder.REVERSE; + + /** + * The weight for this plot (only relevant if this is a subplot in a + * combined plot). + */ + private int weight; + + /** + * An optional collection of legend items that can be returned by the + * getLegendItems() method. + */ + private LegendItemCollection fixedLegendItems; + + /** + * A flag that controls whether or not panning is enabled for the domain + * axis/axes. + */ + private boolean domainPannable; + + /** + * A flag that controls whether or not panning is enabled for the range + * axis/axes. + */ + private boolean rangePannable; + + /** + * The shadow generator ({@code null} permitted). + */ + private ShadowGenerator shadowGenerator; + + /** + * Creates a new {@code XYPlot} instance with no dataset, no axes and + * no renderer. You should specify these items before using the plot. + */ + public XYPlot() { + this(null, null, null, null); + } + + /** + * Creates a new plot with the specified dataset, axes and renderer. Any + * of the arguments can be {@code null}, but in that case you should + * take care to specify the value before using the plot (otherwise a + * {@code NullPointerException} may be thrown). + * + * @param dataset the dataset ({@code null} permitted). + * @param domainAxis the domain axis ({@code null} permitted). + * @param rangeAxis the range axis ({@code null} permitted). + * @param renderer the renderer ({@code null} permitted). + */ + public XYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, + XYItemRenderer renderer) { + super(); + this.orientation = PlotOrientation.VERTICAL; + this.weight = 1; // only relevant when this is a subplot + this.axisOffset = RectangleInsets.ZERO_INSETS; + + // allocate storage for datasets, axes and renderers (all optional) + this.domainAxes = new HashMap<>(); + this.domainAxisLocations = new HashMap<>(); + this.foregroundDomainMarkers = new HashMap<>(); + this.backgroundDomainMarkers = new HashMap<>(); + + this.rangeAxes = new HashMap<>(); + this.rangeAxisLocations = new HashMap<>(); + this.foregroundRangeMarkers = new HashMap<>(); + this.backgroundRangeMarkers = new HashMap<>(); + + this.datasets = new HashMap<>(); + this.renderers = new HashMap<>(); + + this.datasetToDomainAxesMap = new TreeMap<>(); + this.datasetToRangeAxesMap = new TreeMap<>(); + + this.annotations = new ArrayList<>(); + + this.datasets.put(0, dataset); + if (dataset != null) { + dataset.addChangeListener(this); + } + + this.renderers.put(0, renderer); + if (renderer != null) { + renderer.setPlot(this); + renderer.addChangeListener(this); + } + + this.domainAxes.put(0, domainAxis); + mapDatasetToDomainAxis(0, 0); + if (domainAxis != null) { + domainAxis.setPlot(this); + domainAxis.addChangeListener(this); + } + this.domainAxisLocations.put(0, AxisLocation.BOTTOM_OR_LEFT); + + this.rangeAxes.put(0, rangeAxis); + mapDatasetToRangeAxis(0, 0); + if (rangeAxis != null) { + rangeAxis.setPlot(this); + rangeAxis.addChangeListener(this); + } + this.rangeAxisLocations.put(0, AxisLocation.BOTTOM_OR_LEFT); + + configureDomainAxes(); + configureRangeAxes(); + + this.domainGridlinesVisible = true; + this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE; + this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT; + + this.domainMinorGridlinesVisible = false; + this.domainMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE; + this.domainMinorGridlinePaint = Color.WHITE; + + this.domainZeroBaselineVisible = false; + this.domainZeroBaselinePaint = Color.BLACK; + this.domainZeroBaselineStroke = new BasicStroke(0.5f); + + this.rangeGridlinesVisible = true; + this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE; + this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT; + + this.rangeMinorGridlinesVisible = false; + this.rangeMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE; + this.rangeMinorGridlinePaint = Color.WHITE; + + this.rangeZeroBaselineVisible = false; + this.rangeZeroBaselinePaint = Color.BLACK; + this.rangeZeroBaselineStroke = new BasicStroke(0.5f); + + this.domainCrosshairVisible = false; + this.domainCrosshairValue = 0.0; + this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; + this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; + + this.rangeCrosshairVisible = false; + this.rangeCrosshairValue = 0.0; + this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; + this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; + this.shadowGenerator = null; + } + + /** + * Returns the plot type as a string. + * + * @return A short string describing the type of plot. + */ + @Override + public String getPlotType() { + return localizationResources.getString("XY_Plot"); + } + + /** + * Returns the orientation of the plot. + * + * @return The orientation (never {@code null}). + * + * @see #setOrientation(PlotOrientation) + */ + @Override + public PlotOrientation getOrientation() { + return this.orientation; + } + + /** + * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param orientation the orientation ({@code null} not allowed). + * + * @see #getOrientation() + */ + public void setOrientation(PlotOrientation orientation) { + Args.nullNotPermitted(orientation, "orientation"); + if (orientation != this.orientation) { + this.orientation = orientation; + fireChangeEvent(); + } + } + + /** + * Returns the axis offset. + * + * @return The axis offset (never {@code null}). + * + * @see #setAxisOffset(RectangleInsets) + */ + public RectangleInsets getAxisOffset() { + return this.axisOffset; + } + + /** + * Sets the axis offsets (gap between the data area and the axes) and sends + * a {@link PlotChangeEvent} to all registered listeners. + * + * @param offset the offset ({@code null} not permitted). + * + * @see #getAxisOffset() + */ + public void setAxisOffset(RectangleInsets offset) { + Args.nullNotPermitted(offset, "offset"); + this.axisOffset = offset; + fireChangeEvent(); + } + + /** + * Returns the domain axis with index 0. If the domain axis for this plot + * is {@code null}, then the method will return the parent plot's + * domain axis (if there is a parent plot). + * + * @return The domain axis (possibly {@code null}). + * + * @see #getDomainAxis(int) + * @see #setDomainAxis(ValueAxis) + */ + public ValueAxis getDomainAxis() { + return getDomainAxis(0); + } + + /** + * Returns the domain axis with the specified index, or {@code null} if + * there is no axis with that index. + * + * @param index the axis index. + * + * @return The axis ({@code null} possible). + * + * @see #setDomainAxis(int, ValueAxis) + */ + public ValueAxis getDomainAxis(int index) { + ValueAxis result = this.domainAxes.get(index); + if (result == null) { + Plot parent = getParent(); + if (parent instanceof XYPlot) { + XYPlot xy = (XYPlot) parent; + result = xy.getDomainAxis(index); + } + } + return result; + } + + /** + * Returns a map containing the domain axes that are assigned to this plot. + * The map is unmodifiable. + * + * @return A map containing the domain axes that are assigned to the plot + * (never {@code null}). + * + * @since 1.5.4 + */ + public Map getDomainAxes() { + return Collections.unmodifiableMap(this.domainAxes); + } + + /** + * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param axis the new axis ({@code null} permitted). + * + * @see #getDomainAxis() + * @see #setDomainAxis(int, ValueAxis) + */ + public void setDomainAxis(ValueAxis axis) { + setDomainAxis(0, axis); + } + + /** + * Sets a domain axis and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param index the axis index. + * @param axis the axis ({@code null} permitted). + * + * @see #getDomainAxis(int) + * @see #setRangeAxis(int, ValueAxis) + */ + public void setDomainAxis(int index, ValueAxis axis) { + setDomainAxis(index, axis, true); + } + + /** + * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param index the axis index. + * @param axis the axis. + * @param notify notify listeners? + * + * @see #getDomainAxis(int) + */ + public void setDomainAxis(int index, ValueAxis axis, boolean notify) { + ValueAxis existing = getDomainAxis(index); + if (existing != null) { + existing.removeChangeListener(this); + } + if (axis != null) { + axis.setPlot(this); + } + this.domainAxes.put(index, axis); + if (axis != null) { + axis.configure(); + axis.addChangeListener(this); + } + if (notify) { + fireChangeEvent(); + } + } + + /** + * Sets the domain axes for this plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param axes the axes ({@code null} not permitted). + * + * @see #setRangeAxes(ValueAxis[]) + */ + public void setDomainAxes(ValueAxis[] axes) { + for (int i = 0; i < axes.length; i++) { + setDomainAxis(i, axes[i], false); + } + fireChangeEvent(); + } + + /** + * Returns the location of the primary domain axis. + * + * @return The location (never {@code null}). + * + * @see #setDomainAxisLocation(AxisLocation) + */ + public AxisLocation getDomainAxisLocation() { + return this.domainAxisLocations.get(0); + } + + /** + * Sets the location of the primary domain axis and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param location the location ({@code null} not permitted). + * + * @see #getDomainAxisLocation() + */ + public void setDomainAxisLocation(AxisLocation location) { + // delegate... + setDomainAxisLocation(0, location, true); + } + + /** + * Sets the location of the domain axis and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param location the location ({@code null} not permitted). + * @param notify notify listeners? + * + * @see #getDomainAxisLocation() + */ + public void setDomainAxisLocation(AxisLocation location, boolean notify) { + // delegate... + setDomainAxisLocation(0, location, notify); + } + + /** + * Returns the edge for the primary domain axis (taking into account the + * plot's orientation). + * + * @return The edge. + * + * @see #getDomainAxisLocation() + * @see #getOrientation() + */ + public RectangleEdge getDomainAxisEdge() { + return Plot.resolveDomainAxisLocation(getDomainAxisLocation(), + this.orientation); + } + + /** + * Returns the number of domain axes. + * + * @return The axis count. + * + * @see #getRangeAxisCount() + */ + public int getDomainAxisCount() { + return this.domainAxes.size(); + } + + /** + * Clears the domain axes from the plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @see #clearRangeAxes() + */ + public void clearDomainAxes() { + for (ValueAxis axis: this.domainAxes.values()) { + if (axis != null) { + axis.removeChangeListener(this); + } + } + this.domainAxes.clear(); + fireChangeEvent(); + } + + /** + * Configures the domain axes. + */ + public void configureDomainAxes() { + for (ValueAxis axis: this.domainAxes.values()) { + if (axis != null) { + axis.configure(); + } + } + } + + /** + * Returns the location for a domain axis. If this hasn't been set + * explicitly, the method returns the location that is opposite to the + * primary domain axis location. + * + * @param index the axis index (must be >= 0). + * + * @return The location (never {@code null}). + * + * @see #setDomainAxisLocation(int, AxisLocation) + */ + public AxisLocation getDomainAxisLocation(int index) { + AxisLocation result = this.domainAxisLocations.get(index); + if (result == null) { + result = AxisLocation.getOpposite(getDomainAxisLocation()); + } + return result; + } + + /** + * Sets the location for a domain axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param index the axis index. + * @param location the location ({@code null} not permitted for index + * 0). + * + * @see #getDomainAxisLocation(int) + */ + public void setDomainAxisLocation(int index, AxisLocation location) { + // delegate... + setDomainAxisLocation(index, location, true); + } + + /** + * Sets the axis location for a domain axis and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the axis index (must be >= 0). + * @param location the location ({@code null} not permitted for + * index 0). + * @param notify notify listeners? + * + * @see #getDomainAxisLocation(int) + * @see #setRangeAxisLocation(int, AxisLocation, boolean) + */ + public void setDomainAxisLocation(int index, AxisLocation location, + boolean notify) { + if (index == 0 && location == null) { + throw new IllegalArgumentException( + "Null 'location' for index 0 not permitted."); + } + this.domainAxisLocations.put(index, location); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the edge for a domain axis. + * + * @param index the axis index. + * + * @return The edge. + * + * @see #getRangeAxisEdge(int) + */ + public RectangleEdge getDomainAxisEdge(int index) { + AxisLocation location = getDomainAxisLocation(index); + return Plot.resolveDomainAxisLocation(location, this.orientation); + } + + /** + * Returns the range axis for the plot. If the range axis for this plot is + * {@code null}, then the method will return the parent plot's range + * axis (if there is a parent plot). + * + * @return The range axis. + * + * @see #getRangeAxis(int) + * @see #setRangeAxis(ValueAxis) + */ + public ValueAxis getRangeAxis() { + return getRangeAxis(0); + } + + /** + * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param axis the axis ({@code null} permitted). + * + * @see #getRangeAxis() + * @see #setRangeAxis(int, ValueAxis) + */ + public void setRangeAxis(ValueAxis axis) { + if (axis != null) { + axis.setPlot(this); + } + // plot is likely registered as a listener with the existing axis... + ValueAxis existing = getRangeAxis(); + if (existing != null) { + existing.removeChangeListener(this); + } + this.rangeAxes.put(0, axis); + if (axis != null) { + axis.configure(); + axis.addChangeListener(this); + } + fireChangeEvent(); + } + + /** + * Returns the location of the primary range axis. + * + * @return The location (never {@code null}). + * + * @see #setRangeAxisLocation(AxisLocation) + */ + public AxisLocation getRangeAxisLocation() { + return (AxisLocation) this.rangeAxisLocations.get(0); + } + + /** + * Sets the location of the primary range axis and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param location the location ({@code null} not permitted). + * + * @see #getRangeAxisLocation() + */ + public void setRangeAxisLocation(AxisLocation location) { + // delegate... + setRangeAxisLocation(0, location, true); + } + + /** + * Sets the location of the primary range axis and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param location the location ({@code null} not permitted). + * @param notify notify listeners? + * + * @see #getRangeAxisLocation() + */ + public void setRangeAxisLocation(AxisLocation location, boolean notify) { + // delegate... + setRangeAxisLocation(0, location, notify); + } + + /** + * Returns the edge for the primary range axis. + * + * @return The range axis edge. + * + * @see #getRangeAxisLocation() + * @see #getOrientation() + */ + public RectangleEdge getRangeAxisEdge() { + return Plot.resolveRangeAxisLocation(getRangeAxisLocation(), + this.orientation); + } + + /** + * Returns the range axis with the specified index, or {@code null} if + * there is no axis with that index. + * + * @param index the axis index (must be >= 0). + * + * @return The axis ({@code null} possible). + * + * @see #setRangeAxis(int, ValueAxis) + */ + public ValueAxis getRangeAxis(int index) { + ValueAxis result = this.rangeAxes.get(index); + if (result == null) { + Plot parent = getParent(); + if (parent instanceof XYPlot) { + XYPlot xy = (XYPlot) parent; + result = xy.getRangeAxis(index); + } + } + return result; + } + + /** + * Returns a map containing the range axes that are assigned to this plot. + * The map is unmodifiable. + * + * @return A map containing the range axes that are assigned to the plot + * (never {@code null}). + * + * @since 1.5.4 + */ + public Map getRangeAxes() { + return Collections.unmodifiableMap(this.rangeAxes); + } + + /** + * Sets a range axis and sends a {@link PlotChangeEvent} to all registered + * listeners. + * + * @param index the axis index. + * @param axis the axis ({@code null} permitted). + * + * @see #getRangeAxis(int) + */ + public void setRangeAxis(int index, ValueAxis axis) { + setRangeAxis(index, axis, true); + } + + /** + * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param index the axis index. + * @param axis the axis ({@code null} permitted). + * @param notify notify listeners? + * + * @see #getRangeAxis(int) + */ + public void setRangeAxis(int index, ValueAxis axis, boolean notify) { + ValueAxis existing = getRangeAxis(index); + if (existing != null) { + existing.removeChangeListener(this); + } + if (axis != null) { + axis.setPlot(this); + } + this.rangeAxes.put(index, axis); + if (axis != null) { + axis.configure(); + axis.addChangeListener(this); + } + if (notify) { + fireChangeEvent(); + } + } + + /** + * Sets the range axes for this plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param axes the axes ({@code null} not permitted). + * + * @see #setDomainAxes(ValueAxis[]) + */ + public void setRangeAxes(ValueAxis[] axes) { + for (int i = 0; i < axes.length; i++) { + setRangeAxis(i, axes[i], false); + } + fireChangeEvent(); + } + + /** + * Returns the number of range axes. + * + * @return The axis count. + * + * @see #getDomainAxisCount() + */ + public int getRangeAxisCount() { + return this.rangeAxes.size(); + } + + /** + * Clears the range axes from the plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @see #clearDomainAxes() + */ + public void clearRangeAxes() { + for (ValueAxis axis: this.rangeAxes.values()) { + if (axis != null) { + axis.removeChangeListener(this); + } + } + this.rangeAxes.clear(); + fireChangeEvent(); + } + + /** + * Configures the range axes. + * + * @see #configureDomainAxes() + */ + public void configureRangeAxes() { + for (ValueAxis axis: this.rangeAxes.values()) { + if (axis != null) { + axis.configure(); + } + } + } + + /** + * Returns the location for a range axis. If this hasn't been set + * explicitly, the method returns the location that is opposite to the + * primary range axis location. + * + * @param index the axis index (must be >= 0). + * + * @return The location (never {@code null}). + * + * @see #setRangeAxisLocation(int, AxisLocation) + */ + public AxisLocation getRangeAxisLocation(int index) { + AxisLocation result = this.rangeAxisLocations.get(index); + if (result == null) { + result = AxisLocation.getOpposite(getRangeAxisLocation()); + } + return result; + } + + /** + * Sets the location for a range axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param index the axis index. + * @param location the location ({@code null} permitted). + * + * @see #getRangeAxisLocation(int) + */ + public void setRangeAxisLocation(int index, AxisLocation location) { + // delegate... + setRangeAxisLocation(index, location, true); + } + + /** + * Sets the axis location for a domain axis and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the axis index. + * @param location the location ({@code null} not permitted for index 0). + * @param notify notify listeners? + * + * @see #getRangeAxisLocation(int) + * @see #setDomainAxisLocation(int, AxisLocation, boolean) + */ + public void setRangeAxisLocation(int index, AxisLocation location, + boolean notify) { + if (index == 0 && location == null) { + throw new IllegalArgumentException( + "Null 'location' for index 0 not permitted."); + } + this.rangeAxisLocations.put(index, location); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the edge for a range axis. + * + * @param index the axis index. + * + * @return The edge. + * + * @see #getRangeAxisLocation(int) + * @see #getOrientation() + */ + public RectangleEdge getRangeAxisEdge(int index) { + AxisLocation location = getRangeAxisLocation(index); + return Plot.resolveRangeAxisLocation(location, this.orientation); + } + + /** + * Returns the primary dataset for the plot. + * + * @return The primary dataset (possibly {@code null}). + * + * @see #getDataset(int) + * @see #setDataset(XYDataset) + */ + public XYDataset getDataset() { + return getDataset(0); + } + + /** + * Returns the dataset with the specified index, or {@code null} if there + * is no dataset with that index. + * + * @param index the dataset index (must be >= 0). + * + * @return The dataset (possibly {@code null}). + * + * @see #setDataset(int, XYDataset) + */ + public XYDataset getDataset(int index) { + return this.datasets.get(index); + } + + /** + * Returns a map containing the datasets that are assigned to this plot. + * The map is unmodifiable. + * + * @return A map containing the datasets that are assigned to the plot + * (never {@code null}). + * + * @since 1.5.4 + */ + public Map getDatasets() { + return Collections.unmodifiableMap(this.datasets); + } + + /** + * Sets the primary dataset for the plot, replacing the existing dataset if + * there is one. + * + * @param dataset the dataset ({@code null} permitted). + * + * @see #getDataset() + * @see #setDataset(int, XYDataset) + */ + public void setDataset(XYDataset dataset) { + setDataset(0, dataset); + } + + /** + * Sets a dataset for the plot and sends a change event to all registered + * listeners. + * + * @param index the dataset index (must be >= 0). + * @param dataset the dataset ({@code null} permitted). + * + * @see #getDataset(int) + */ + public void setDataset(int index, XYDataset dataset) { + XYDataset existing = getDataset(index); + if (existing != null) { + existing.removeChangeListener(this); + } + this.datasets.put(index, dataset); + if (dataset != null) { + dataset.addChangeListener(this); + } + + // send a dataset change event to self... + DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); + datasetChanged(event); + } + + /** + * Returns the number of datasets. + * + * @return The number of datasets. + */ + public int getDatasetCount() { + return (int) this.datasets.values().stream().filter(Objects::nonNull).count(); + } + + /** + * Returns the index of the specified dataset, or {@code -1} if the + * dataset does not belong to the plot. + * + * @param dataset the dataset ({@code null} not permitted). + * + * @return The index or -1. + */ + public int indexOf(XYDataset dataset) { + for (Map.Entry entry: this.datasets.entrySet()) { + if (dataset == entry.getValue()) { + return entry.getKey(); + } + } + return -1; + } + + /** + * Maps a dataset to a particular domain axis. All data will be plotted + * against axis zero by default, no mapping is required for this case. + * + * @param index the dataset index (zero-based). + * @param axisIndex the axis index. + * + * @see #mapDatasetToRangeAxis(int, int) + */ + public void mapDatasetToDomainAxis(int index, int axisIndex) { + List axisIndices = new ArrayList<>(1); + axisIndices.add(axisIndex); + mapDatasetToDomainAxes(index, axisIndices); + } + + /** + * Maps the specified dataset to the axes in the list. Note that the + * conversion of data values into Java2D space is always performed using + * the first axis in the list. + * + * @param index the dataset index (zero-based). + * @param axisIndices the axis indices ({@code null} permitted). + */ + public void mapDatasetToDomainAxes(int index, List axisIndices) { + Args.requireNonNegative(index, "index"); + checkAxisIndices(axisIndices); + this.datasetToDomainAxesMap.put(index, new ArrayList<>(axisIndices)); + // fake a dataset change event to update axes... + datasetChanged(new DatasetChangeEvent(this, getDataset(index))); + } + + /** + * Maps a dataset to a particular range axis. All data will be plotted + * against axis zero by default, no mapping is required for this case. + * + * @param index the dataset index (zero-based). + * @param axisIndex the axis index. + * + * @see #mapDatasetToDomainAxis(int, int) + */ + public void mapDatasetToRangeAxis(int index, int axisIndex) { + List axisIndices = new ArrayList<>(1); + axisIndices.add(axisIndex); + mapDatasetToRangeAxes(index, axisIndices); + } + + /** + * Maps the specified dataset to the axes in the list. Note that the + * conversion of data values into Java2D space is always performed using + * the first axis in the list. + * + * @param index the dataset index (zero-based). + * @param axisIndices the axis indices ({@code null} permitted). + */ + public void mapDatasetToRangeAxes(int index, List axisIndices) { + Args.requireNonNegative(index, "index"); + checkAxisIndices(axisIndices); + this.datasetToRangeAxesMap.put(index, new ArrayList<>(axisIndices)); + // fake a dataset change event to update axes... + datasetChanged(new DatasetChangeEvent(this, getDataset(index))); + } + + /** + * This method is used to perform argument checking on the list of + * axis indices passed to mapDatasetToDomainAxes() and + * mapDatasetToRangeAxes(). + * + * @param indices the list of indices ({@code null} permitted). + */ + private void checkAxisIndices(List indices) { + // axisIndices can be: + // 1. null; + // 2. non-empty, containing only Integer objects that are unique. + if (indices == null) { + return; // OK + } + int count = indices.size(); + if (count == 0) { + throw new IllegalArgumentException("Empty list not permitted."); + } + Set set = new HashSet<>(); + for (Integer item : indices) { + if (set.contains(item)) { + throw new IllegalArgumentException("Indices must be unique."); + } + set.add(item); + } + } + + /** + * Returns the number of renderer slots for this plot. + * + * @return The number of renderer slots. + */ + public int getRendererCount() { + return this.renderers.size(); + } + + /** + * Returns the renderer for the primary dataset. + * + * @return The item renderer (possibly {@code null}). + * + * @see #setRenderer(XYItemRenderer) + */ + public XYItemRenderer getRenderer() { + return getRenderer(0); + } + + /** + * Returns the renderer with the specified index, or {@code null}. + * + * @param index the renderer index (must be >= 0). + * + * @return The renderer (possibly {@code null}). + * + * @see #setRenderer(int, XYItemRenderer) + */ + public XYItemRenderer getRenderer(int index) { + return this.renderers.get(index); + } + + /** + * Returns a map containing the renderers that are assigned to this plot. + * The map is unmodifiable. + * + * @return A map containing the renderers that are assigned to the plot + * (never {@code null}). + * + * @since 1.5.4 + */ + public Map getRenderers() { + return Collections.unmodifiableMap(this.renderers); + } + + /** + * Sets the renderer for the primary dataset and sends a change event to + * all registered listeners. If the renderer is set to {@code null}, + * no data will be displayed. + * + * @param renderer the renderer ({@code null} permitted). + * + * @see #getRenderer() + */ + public void setRenderer(XYItemRenderer renderer) { + setRenderer(0, renderer); + } + + /** + * Sets the renderer for the dataset with the specified index and sends a + * change event to all registered listeners. Note that each dataset should + * have its own renderer, you should not use one renderer for multiple + * datasets. + * + * @param index the index (must be >= 0). + * @param renderer the renderer. + * + * @see #getRenderer(int) + */ + public void setRenderer(int index, XYItemRenderer renderer) { + setRenderer(index, renderer, true); + } + + /** + * Sets the renderer for the dataset with the specified index and, if + * requested, sends a change event to all registered listeners. Note that + * each dataset should have its own renderer, you should not use one + * renderer for multiple datasets. + * + * @param index the index (must be >= 0). + * @param renderer the renderer. + * @param notify notify listeners? + * + * @see #getRenderer(int) + */ + public void setRenderer(int index, XYItemRenderer renderer, + boolean notify) { + XYItemRenderer existing = getRenderer(index); + if (existing != null) { + existing.removeChangeListener(this); + } + this.renderers.put(index, renderer); + if (renderer != null) { + renderer.setPlot(this); + renderer.addChangeListener(this); + } + configureDomainAxes(); + configureRangeAxes(); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Sets the renderers for this plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param renderers the renderers ({@code null} not permitted). + */ + public void setRenderers(XYItemRenderer[] renderers) { + for (int i = 0; i < renderers.length; i++) { + setRenderer(i, renderers[i], false); + } + fireChangeEvent(); + } + + /** + * Returns the dataset rendering order. + * + * @return The order (never {@code null}). + * + * @see #setDatasetRenderingOrder(DatasetRenderingOrder) + */ + public DatasetRenderingOrder getDatasetRenderingOrder() { + return this.datasetRenderingOrder; + } + + /** + * Sets the rendering order and sends a {@link PlotChangeEvent} to all + * registered listeners. By default, the plot renders the primary dataset + * last (so that the primary dataset overlays the secondary datasets). + * You can reverse this if you want to. + * + * @param order the rendering order ({@code null} not permitted). + * + * @see #getDatasetRenderingOrder() + */ + public void setDatasetRenderingOrder(DatasetRenderingOrder order) { + Args.nullNotPermitted(order, "order"); + this.datasetRenderingOrder = order; + fireChangeEvent(); + } + + /** + * Returns the series rendering order. + * + * @return the order (never {@code null}). + * + * @see #setSeriesRenderingOrder(SeriesRenderingOrder) + */ + public SeriesRenderingOrder getSeriesRenderingOrder() { + return this.seriesRenderingOrder; + } + + /** + * Sets the series order and sends a {@link PlotChangeEvent} to all + * registered listeners. By default, the plot renders the primary series + * last (so that the primary series appears to be on top). + * You can reverse this if you want to. + * + * @param order the rendering order ({@code null} not permitted). + * + * @see #getSeriesRenderingOrder() + */ + public void setSeriesRenderingOrder(SeriesRenderingOrder order) { + Args.nullNotPermitted(order, "order"); + this.seriesRenderingOrder = order; + fireChangeEvent(); + } + + /** + * Returns the index of the specified renderer, or {@code -1} if the + * renderer is not assigned to this plot. + * + * @param renderer the renderer ({@code null} permitted). + * + * @return The renderer index. + */ + public int getIndexOf(XYItemRenderer renderer) { + for (Map.Entry entry + : this.renderers.entrySet()) { + if (entry.getValue() == renderer) { + return entry.getKey(); + } + } + return -1; + } + + /** + * Returns the renderer for the specified dataset (this is either the + * renderer with the same index as the dataset or, if there isn't a + * renderer with the same index, the default renderer). If the dataset + * does not belong to the plot, this method will return {@code null}. + * + * @param dataset the dataset ({@code null} permitted). + * + * @return The renderer (possibly {@code null}). + */ + public XYItemRenderer getRendererForDataset(XYDataset dataset) { + int datasetIndex = indexOf(dataset); + if (datasetIndex < 0) { + return null; + } + XYItemRenderer result = this.renderers.get(datasetIndex); + if (result == null) { + result = getRenderer(); + } + return result; + } + + /** + * Returns the weight for this plot when it is used as a subplot within a + * combined plot. + * + * @return The weight. + * + * @see #setWeight(int) + */ + public int getWeight() { + return this.weight; + } + + /** + * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param weight the weight. + * + * @see #getWeight() + */ + public void setWeight(int weight) { + this.weight = weight; + fireChangeEvent(); + } + + /** + * Returns {@code true} if the domain gridlines are visible, and + * {@code false} otherwise. + * + * @return {@code true} or {@code false}. + * + * @see #setDomainGridlinesVisible(boolean) + */ + public boolean isDomainGridlinesVisible() { + return this.domainGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not the domain grid-lines are + * visible. + *

+ * If the flag value is changed, a {@link PlotChangeEvent} is sent to all + * registered listeners. + * + * @param visible the new value of the flag. + * + * @see #isDomainGridlinesVisible() + */ + public void setDomainGridlinesVisible(boolean visible) { + if (this.domainGridlinesVisible != visible) { + this.domainGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns {@code true} if the domain minor gridlines are visible, and + * {@code false} otherwise. + * + * @return {@code true} or {@code false}. + * + * @see #setDomainMinorGridlinesVisible(boolean) + */ + public boolean isDomainMinorGridlinesVisible() { + return this.domainMinorGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not the domain minor grid-lines + * are visible. + *

+ * If the flag value is changed, a {@link PlotChangeEvent} is sent to all + * registered listeners. + * + * @param visible the new value of the flag. + * + * @see #isDomainMinorGridlinesVisible() + */ + public void setDomainMinorGridlinesVisible(boolean visible) { + if (this.domainMinorGridlinesVisible != visible) { + this.domainMinorGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the stroke for the grid-lines (if any) plotted against the + * domain axis. + * + * @return The stroke (never {@code null}). + * + * @see #setDomainGridlineStroke(Stroke) + */ + public Stroke getDomainGridlineStroke() { + return this.domainGridlineStroke; + } + + /** + * Sets the stroke for the grid lines plotted against the domain axis, and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getDomainGridlineStroke() + */ + public void setDomainGridlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.domainGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the stroke for the minor grid-lines (if any) plotted against the + * domain axis. + * + * @return The stroke (never {@code null}). + * + * @see #setDomainMinorGridlineStroke(Stroke) + */ + + public Stroke getDomainMinorGridlineStroke() { + return this.domainMinorGridlineStroke; + } + + /** + * Sets the stroke for the minor grid lines plotted against the domain + * axis, and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getDomainMinorGridlineStroke() + */ + public void setDomainMinorGridlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.domainMinorGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the grid lines (if any) plotted against the domain + * axis. + * + * @return The paint (never {@code null}). + * + * @see #setDomainGridlinePaint(Paint) + */ + public Paint getDomainGridlinePaint() { + return this.domainGridlinePaint; + } + + /** + * Sets the paint for the grid lines plotted against the domain axis, and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getDomainGridlinePaint() + */ + public void setDomainGridlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.domainGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns the paint for the minor grid lines (if any) plotted against the + * domain axis. + * + * @return The paint (never {@code null}). + * + * @see #setDomainMinorGridlinePaint(Paint) + */ + public Paint getDomainMinorGridlinePaint() { + return this.domainMinorGridlinePaint; + } + + /** + * Sets the paint for the minor grid lines plotted against the domain axis, + * and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @throws IllegalArgumentException if {@code Paint} is + * {@code null}. + * + * @see #getDomainMinorGridlinePaint() + */ + public void setDomainMinorGridlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.domainMinorGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns {@code true} if the range axis grid is visible, and + * {@code false} otherwise. + * + * @return A boolean. + * + * @see #setRangeGridlinesVisible(boolean) + */ + public boolean isRangeGridlinesVisible() { + return this.rangeGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not the range axis grid lines + * are visible. + *

+ * If the flag value is changed, a {@link PlotChangeEvent} is sent to all + * registered listeners. + * + * @param visible the new value of the flag. + * + * @see #isRangeGridlinesVisible() + */ + public void setRangeGridlinesVisible(boolean visible) { + if (this.rangeGridlinesVisible != visible) { + this.rangeGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the stroke for the grid lines (if any) plotted against the + * range axis. + * + * @return The stroke (never {@code null}). + * + * @see #setRangeGridlineStroke(Stroke) + */ + public Stroke getRangeGridlineStroke() { + return this.rangeGridlineStroke; + } + + /** + * Sets the stroke for the grid lines plotted against the range axis, + * and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getRangeGridlineStroke() + */ + public void setRangeGridlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.rangeGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the grid lines (if any) plotted against the range + * axis. + * + * @return The paint (never {@code null}). + * + * @see #setRangeGridlinePaint(Paint) + */ + public Paint getRangeGridlinePaint() { + return this.rangeGridlinePaint; + } + + /** + * Sets the paint for the grid lines plotted against the range axis and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getRangeGridlinePaint() + */ + public void setRangeGridlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.rangeGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns {@code true} if the range axis minor grid is visible, and + * {@code false} otherwise. + * + * @return A boolean. + * + * @see #setRangeMinorGridlinesVisible(boolean) + */ + public boolean isRangeMinorGridlinesVisible() { + return this.rangeMinorGridlinesVisible; + } + + /** + * Sets the flag that controls whether or not the range axis minor grid + * lines are visible. + *

+ * If the flag value is changed, a {@link PlotChangeEvent} is sent to all + * registered listeners. + * + * @param visible the new value of the flag. + * + * @see #isRangeMinorGridlinesVisible() + */ + public void setRangeMinorGridlinesVisible(boolean visible) { + if (this.rangeMinorGridlinesVisible != visible) { + this.rangeMinorGridlinesVisible = visible; + fireChangeEvent(); + } + } + + /** + * Returns the stroke for the minor grid lines (if any) plotted against the + * range axis. + * + * @return The stroke (never {@code null}). + * + * @see #setRangeMinorGridlineStroke(Stroke) + */ + public Stroke getRangeMinorGridlineStroke() { + return this.rangeMinorGridlineStroke; + } + + /** + * Sets the stroke for the minor grid lines plotted against the range axis, + * and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getRangeMinorGridlineStroke() + */ + public void setRangeMinorGridlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.rangeMinorGridlineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the minor grid lines (if any) plotted against the + * range axis. + * + * @return The paint (never {@code null}). + * + * @see #setRangeMinorGridlinePaint(Paint) + */ + public Paint getRangeMinorGridlinePaint() { + return this.rangeMinorGridlinePaint; + } + + /** + * Sets the paint for the minor grid lines plotted against the range axis + * and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getRangeMinorGridlinePaint() + */ + public void setRangeMinorGridlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.rangeMinorGridlinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns a flag that controls whether or not a zero baseline is + * displayed for the domain axis. + * + * @return A boolean. + * + * @see #setDomainZeroBaselineVisible(boolean) + */ + public boolean isDomainZeroBaselineVisible() { + return this.domainZeroBaselineVisible; + } + + /** + * Sets the flag that controls whether or not the zero baseline is + * displayed for the domain axis, and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param visible the flag. + * + * @see #isDomainZeroBaselineVisible() + */ + public void setDomainZeroBaselineVisible(boolean visible) { + this.domainZeroBaselineVisible = visible; + fireChangeEvent(); + } + + /** + * Returns the stroke used for the zero baseline against the domain axis. + * + * @return The stroke (never {@code null}). + * + * @see #setDomainZeroBaselineStroke(Stroke) + */ + public Stroke getDomainZeroBaselineStroke() { + return this.domainZeroBaselineStroke; + } + + /** + * Sets the stroke for the zero baseline for the domain axis, + * and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getRangeZeroBaselineStroke() + */ + public void setDomainZeroBaselineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.domainZeroBaselineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the zero baseline (if any) plotted against the + * domain axis. + * + * @return The paint (never {@code null}). + * + * @see #setDomainZeroBaselinePaint(Paint) + */ + public Paint getDomainZeroBaselinePaint() { + return this.domainZeroBaselinePaint; + } + + /** + * Sets the paint for the zero baseline plotted against the domain axis and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getDomainZeroBaselinePaint() + */ + public void setDomainZeroBaselinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.domainZeroBaselinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns a flag that controls whether or not a zero baseline is + * displayed for the range axis. + * + * @return A boolean. + * + * @see #setRangeZeroBaselineVisible(boolean) + */ + public boolean isRangeZeroBaselineVisible() { + return this.rangeZeroBaselineVisible; + } + + /** + * Sets the flag that controls whether or not the zero baseline is + * displayed for the range axis, and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param visible the flag. + * + * @see #isRangeZeroBaselineVisible() + */ + public void setRangeZeroBaselineVisible(boolean visible) { + this.rangeZeroBaselineVisible = visible; + fireChangeEvent(); + } + + /** + * Returns the stroke used for the zero baseline against the range axis. + * + * @return The stroke (never {@code null}). + * + * @see #setRangeZeroBaselineStroke(Stroke) + */ + public Stroke getRangeZeroBaselineStroke() { + return this.rangeZeroBaselineStroke; + } + + /** + * Sets the stroke for the zero baseline for the range axis, + * and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the stroke ({@code null} not permitted). + * + * @see #getRangeZeroBaselineStroke() + */ + public void setRangeZeroBaselineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.rangeZeroBaselineStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the paint for the zero baseline (if any) plotted against the + * range axis. + * + * @return The paint (never {@code null}). + * + * @see #setRangeZeroBaselinePaint(Paint) + */ + public Paint getRangeZeroBaselinePaint() { + return this.rangeZeroBaselinePaint; + } + + /** + * Sets the paint for the zero baseline plotted against the range axis and + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getRangeZeroBaselinePaint() + */ + public void setRangeZeroBaselinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.rangeZeroBaselinePaint = paint; + fireChangeEvent(); + } + + /** + * Returns the paint used for the domain tick bands. If this is + * {@code null}, no tick bands will be drawn. + * + * @return The paint (possibly {@code null}). + * + * @see #setDomainTickBandPaint(Paint) + */ + public Paint getDomainTickBandPaint() { + return this.domainTickBandPaint; + } + + /** + * Sets the paint for the domain tick bands. + * + * @param paint the paint ({@code null} permitted). + * + * @see #getDomainTickBandPaint() + */ + public void setDomainTickBandPaint(Paint paint) { + this.domainTickBandPaint = paint; + fireChangeEvent(); + } + + /** + * Returns the paint used for the range tick bands. If this is + * {@code null}, no tick bands will be drawn. + * + * @return The paint (possibly {@code null}). + * + * @see #setRangeTickBandPaint(Paint) + */ + public Paint getRangeTickBandPaint() { + return this.rangeTickBandPaint; + } + + /** + * Sets the paint for the range tick bands. + * + * @param paint the paint ({@code null} permitted). + * + * @see #getRangeTickBandPaint() + */ + public void setRangeTickBandPaint(Paint paint) { + this.rangeTickBandPaint = paint; + fireChangeEvent(); + } + + /** + * Returns the origin for the quadrants that can be displayed on the plot. + * This defaults to (0, 0). + * + * @return The origin point (never {@code null}). + * + * @see #setQuadrantOrigin(Point2D) + */ + public Point2D getQuadrantOrigin() { + return this.quadrantOrigin; + } + + /** + * Sets the quadrant origin and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param origin the origin ({@code null} not permitted). + * + * @see #getQuadrantOrigin() + */ + public void setQuadrantOrigin(Point2D origin) { + Args.nullNotPermitted(origin, "origin"); + this.quadrantOrigin = origin; + fireChangeEvent(); + } + + /** + * Returns the paint used for the specified quadrant. + * + * @param index the quadrant index (0-3). + * + * @return The paint (possibly {@code null}). + * + * @see #setQuadrantPaint(int, Paint) + */ + public Paint getQuadrantPaint(int index) { + if (index < 0 || index > 3) { + throw new IllegalArgumentException("The index value (" + index + + ") should be in the range 0 to 3."); + } + return this.quadrantPaint[index]; + } + + /** + * Sets the paint used for the specified quadrant and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the quadrant index (0-3). + * @param paint the paint ({@code null} permitted). + * + * @see #getQuadrantPaint(int) + */ + public void setQuadrantPaint(int index, Paint paint) { + if (index < 0 || index > 3) { + throw new IllegalArgumentException("The index value (" + index + + ") should be in the range 0 to 3."); + } + this.quadrantPaint[index] = paint; + fireChangeEvent(); + } + + /** + * Adds a marker for the domain axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to the domain axis, however this is entirely up to the renderer. + * + * @param marker the marker ({@code null} not permitted). + * + * @see #addDomainMarker(Marker, Layer) + * @see #clearDomainMarkers() + */ + public void addDomainMarker(Marker marker) { + // defer argument checking... + addDomainMarker(marker, Layer.FOREGROUND); + } + + /** + * Adds a marker for the domain axis in the specified layer and sends a + * {@link PlotChangeEvent} to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to the domain axis, however this is entirely up to the renderer. + * + * @param marker the marker ({@code null} not permitted). + * @param layer the layer (foreground or background). + * + * @see #addDomainMarker(int, Marker, Layer) + */ + public void addDomainMarker(Marker marker, Layer layer) { + addDomainMarker(0, marker, layer); + } + + /** + * Clears all the (foreground and background) domain markers and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @see #addDomainMarker(int, Marker, Layer) + */ + public void clearDomainMarkers() { + if (this.backgroundDomainMarkers != null) { + Set keys = this.backgroundDomainMarkers.keySet(); + for (Integer key : keys) { + clearDomainMarkers(key); + } + this.backgroundDomainMarkers.clear(); + } + if (this.foregroundDomainMarkers != null) { + Set keys = this.foregroundDomainMarkers.keySet(); + for (Integer key : keys) { + clearDomainMarkers(key); + } + this.foregroundDomainMarkers.clear(); + } + fireChangeEvent(); + } + + /** + * Clears the (foreground and background) domain markers for a particular + * renderer and sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param index the renderer index. + * + * @see #clearRangeMarkers(int) + */ + public void clearDomainMarkers(int index) { + if (this.backgroundDomainMarkers != null) { + List markers = this.backgroundDomainMarkers.get(index); + if (markers != null) { + for (Marker m : markers) { + m.removeChangeListener(this); + } + markers.clear(); + } + } + if (this.foregroundRangeMarkers != null) { + List markers = this.foregroundDomainMarkers.get(index); + if (markers != null) { + for (Marker m : markers) { + m.removeChangeListener(this); + } + markers.clear(); + } + } + fireChangeEvent(); + } + + /** + * Adds a marker for a specific dataset/renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to the domain axis (that the renderer is mapped to), however this is + * entirely up to the renderer. + * + * @param index the dataset/renderer index. + * @param marker the marker. + * @param layer the layer (foreground or background). + * + * @see #clearDomainMarkers(int) + * @see #addRangeMarker(int, Marker, Layer) + */ + public void addDomainMarker(int index, Marker marker, Layer layer) { + addDomainMarker(index, marker, layer, true); + } + + /** + * Adds a marker for a specific dataset/renderer and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to the domain axis (that the renderer is mapped to), however this is + * entirely up to the renderer. + * + * @param index the dataset/renderer index. + * @param marker the marker. + * @param layer the layer (foreground or background). + * @param notify notify listeners? + */ + public void addDomainMarker(int index, Marker marker, Layer layer, + boolean notify) { + Args.nullNotPermitted(marker, "marker"); + Args.nullNotPermitted(layer, "layer"); + List markers; + if (layer == Layer.FOREGROUND) { + markers = this.foregroundDomainMarkers.computeIfAbsent(index, k -> new ArrayList<>()); + markers.add(marker); + } + else if (layer == Layer.BACKGROUND) { + markers = this.backgroundDomainMarkers.computeIfAbsent(index, k -> new ArrayList<>()); + markers.add(marker); + } + marker.addChangeListener(this); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Removes a marker for the domain axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param marker the marker. + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeDomainMarker(Marker marker) { + return removeDomainMarker(marker, Layer.FOREGROUND); + } + + /** + * Removes a marker for the domain axis in the specified layer and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param marker the marker ({@code null} not permitted). + * @param layer the layer (foreground or background). + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeDomainMarker(Marker marker, Layer layer) { + return removeDomainMarker(0, marker, layer); + } + + /** + * Removes a marker for a specific dataset/renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the dataset/renderer index. + * @param marker the marker. + * @param layer the layer (foreground or background). + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeDomainMarker(int index, Marker marker, Layer layer) { + return removeDomainMarker(index, marker, layer, true); + } + + /** + * Removes a marker for a specific dataset/renderer and, if requested, + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param index the dataset/renderer index. + * @param marker the marker. + * @param layer the layer (foreground or background). + * @param notify notify listeners? + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeDomainMarker(int index, Marker marker, Layer layer, + boolean notify) { + List markers; + if (layer == Layer.FOREGROUND) { + markers = this.foregroundDomainMarkers.get(index); + } else { + markers = this.backgroundDomainMarkers.get(index); + } + if (markers == null) { + return false; + } + boolean removed = markers.remove(marker); + if (removed && notify) { + fireChangeEvent(); + } + return removed; + } + + /** + * Adds a marker for the range axis and sends a {@link PlotChangeEvent} to + * all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to the range axis, however this is entirely up to the renderer. + * + * @param marker the marker ({@code null} not permitted). + * + * @see #addRangeMarker(Marker, Layer) + */ + public void addRangeMarker(Marker marker) { + addRangeMarker(marker, Layer.FOREGROUND); + } + + /** + * Adds a marker for the range axis in the specified layer and sends a + * {@link PlotChangeEvent} to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to the range axis, however this is entirely up to the renderer. + * + * @param marker the marker ({@code null} not permitted). + * @param layer the layer (foreground or background). + * + * @see #addRangeMarker(int, Marker, Layer) + */ + public void addRangeMarker(Marker marker, Layer layer) { + addRangeMarker(0, marker, layer); + } + + /** + * Clears all the range markers and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @see #clearRangeMarkers() + */ + public void clearRangeMarkers() { + if (this.backgroundRangeMarkers != null) { + Set keys = this.backgroundRangeMarkers.keySet(); + for (Integer key : keys) { + clearRangeMarkers(key); + } + this.backgroundRangeMarkers.clear(); + } + if (this.foregroundRangeMarkers != null) { + Set keys = this.foregroundRangeMarkers.keySet(); + for (Integer key : keys) { + clearRangeMarkers(key); + } + this.foregroundRangeMarkers.clear(); + } + fireChangeEvent(); + } + + /** + * Adds a marker for a specific dataset/renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to the range axis, however this is entirely up to the renderer. + * + * @param index the dataset/renderer index. + * @param marker the marker. + * @param layer the layer (foreground or background). + * + * @see #clearRangeMarkers(int) + * @see #addDomainMarker(int, Marker, Layer) + */ + public void addRangeMarker(int index, Marker marker, Layer layer) { + addRangeMarker(index, marker, layer, true); + } + + /** + * Adds a marker for a specific dataset/renderer and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + *

+ * Typically a marker will be drawn by the renderer as a line perpendicular + * to the range axis, however this is entirely up to the renderer. + * + * @param index the dataset/renderer index. + * @param marker the marker. + * @param layer the layer (foreground or background). + * @param notify notify listeners? + */ + public void addRangeMarker(int index, Marker marker, Layer layer, + boolean notify) { + List markers; + if (layer == Layer.FOREGROUND) { + markers = this.foregroundRangeMarkers.computeIfAbsent(index, k -> new ArrayList<>()); + markers.add(marker); + } + else if (layer == Layer.BACKGROUND) { + markers = this.backgroundRangeMarkers.computeIfAbsent(index, k -> new ArrayList<>()); + markers.add(marker); + } + marker.addChangeListener(this); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Clears the (foreground and background) range markers for a particular + * renderer. + * + * @param index the renderer index. + */ + public void clearRangeMarkers(int index) { + if (this.backgroundRangeMarkers != null) { + List markers = this.backgroundRangeMarkers.get(index); + if (markers != null) { + for (Marker m : markers) { + m.removeChangeListener(this); + } + markers.clear(); + } + } + if (this.foregroundRangeMarkers != null) { + List markers = this.foregroundRangeMarkers.get(index); + if (markers != null) { + for (Marker m : markers) { + m.removeChangeListener(this); + } + markers.clear(); + } + } + fireChangeEvent(); + } + + /** + * Removes a marker for the range axis and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param marker the marker. + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeRangeMarker(Marker marker) { + return removeRangeMarker(marker, Layer.FOREGROUND); + } + + /** + * Removes a marker for the range axis in the specified layer and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param marker the marker ({@code null} not permitted). + * @param layer the layer (foreground or background). + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeRangeMarker(Marker marker, Layer layer) { + return removeRangeMarker(0, marker, layer); + } + + /** + * Removes a marker for a specific dataset/renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the dataset/renderer index. + * @param marker the marker ({@code null} not permitted). + * @param layer the layer (foreground or background). + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeRangeMarker(int index, Marker marker, Layer layer) { + return removeRangeMarker(index, marker, layer, true); + } + + /** + * Removes a marker for a specific dataset/renderer and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param index the dataset/renderer index. + * @param marker the marker ({@code null} not permitted). + * @param layer the layer (foreground or background) ({@code null} not permitted). + * @param notify notify listeners? + * + * @return A boolean indicating whether or not the marker was actually + * removed. + */ + public boolean removeRangeMarker(int index, Marker marker, Layer layer, + boolean notify) { + Args.nullNotPermitted(marker, "marker"); + Args.nullNotPermitted(layer, "layer"); + List markers; + if (layer == Layer.FOREGROUND) { + markers = this.foregroundRangeMarkers.get(index); + } else { + markers = this.backgroundRangeMarkers.get(index); + } + if (markers == null) { + return false; + } + boolean removed = markers.remove(marker); + if (removed && notify) { + fireChangeEvent(); + } + return removed; + } + + /** + * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param annotation the annotation ({@code null} not permitted). + * + * @see #getAnnotations() + * @see #removeAnnotation(XYAnnotation) + */ + public void addAnnotation(XYAnnotation annotation) { + addAnnotation(annotation, true); + } + + /** + * Adds an annotation to the plot and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param annotation the annotation ({@code null} not permitted). + * @param notify notify listeners? + */ + public void addAnnotation(XYAnnotation annotation, boolean notify) { + Args.nullNotPermitted(annotation, "annotation"); + this.annotations.add(annotation); + annotation.addChangeListener(this); + if (notify) { + fireChangeEvent(); + } + } + + /** + * Removes an annotation from the plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param annotation the annotation ({@code null} not permitted). + * + * @return A boolean (indicates whether or not the annotation was removed). + * + * @see #addAnnotation(XYAnnotation) + * @see #getAnnotations() + */ + public boolean removeAnnotation(XYAnnotation annotation) { + return removeAnnotation(annotation, true); + } + + /** + * Removes an annotation from the plot and sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param annotation the annotation ({@code null} not permitted). + * @param notify notify listeners? + * + * @return A boolean (indicates whether or not the annotation was removed). + */ + public boolean removeAnnotation(XYAnnotation annotation, boolean notify) { + Args.nullNotPermitted(annotation, "annotation"); + boolean removed = this.annotations.remove(annotation); + annotation.removeChangeListener(this); + if (removed && notify) { + fireChangeEvent(); + } + return removed; + } + + /** + * Returns the list of annotations. + * + * @return The list of annotations. + * + * @see #addAnnotation(XYAnnotation) + */ + public List getAnnotations() { + return new ArrayList<>(this.annotations); + } + + /** + * Clears all the annotations and sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @see #addAnnotation(XYAnnotation) + */ + public void clearAnnotations() { + for (XYAnnotation annotation : this.annotations) { + annotation.removeChangeListener(this); + } + this.annotations.clear(); + fireChangeEvent(); + } + + /** + * Returns the shadow generator for the plot, if any. + * + * @return The shadow generator (possibly {@code null}). + */ + public ShadowGenerator getShadowGenerator() { + return this.shadowGenerator; + } + + /** + * Sets the shadow generator for the plot and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param generator the generator ({@code null} permitted). + */ + public void setShadowGenerator(ShadowGenerator generator) { + this.shadowGenerator = generator; + fireChangeEvent(); + } + + /** + * Calculates the space required for all the axes in the plot. + * + * @param g2 the graphics device. + * @param plotArea the plot area. + * + * @return The required space. + */ + protected AxisSpace calculateAxisSpace(Graphics2D g2, + Rectangle2D plotArea) { + AxisSpace space = new AxisSpace(); + space = calculateRangeAxisSpace(g2, plotArea, space); + Rectangle2D revPlotArea = space.shrink(plotArea, null); + space = calculateDomainAxisSpace(g2, revPlotArea, space); + return space; + } + + /** + * Calculates the space required for the domain axis/axes. + * + * @param g2 the graphics device. + * @param plotArea the plot area. + * @param space a carrier for the result ({@code null} permitted). + * + * @return The required space. + */ + protected AxisSpace calculateDomainAxisSpace(Graphics2D g2, + Rectangle2D plotArea, AxisSpace space) { + + if (space == null) { + space = new AxisSpace(); + } + + // reserve some space for the domain axis... + if (this.fixedDomainAxisSpace != null) { + if (this.orientation == PlotOrientation.HORIZONTAL) { + space.ensureAtLeast(this.fixedDomainAxisSpace.getLeft(), + RectangleEdge.LEFT); + space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(), + RectangleEdge.RIGHT); + } + else if (this.orientation == PlotOrientation.VERTICAL) { + space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(), + RectangleEdge.TOP); + space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(), + RectangleEdge.BOTTOM); + } + } + else { + // reserve space for the domain axes... + for (ValueAxis axis: this.domainAxes.values()) { + if (axis != null) { + RectangleEdge edge = getDomainAxisEdge( + findDomainAxisIndex(axis)); + space = axis.reserveSpace(g2, this, plotArea, edge, space); + } + } + } + + return space; + + } + + /** + * Calculates the space required for the range axis/axes. + * + * @param g2 the graphics device. + * @param plotArea the plot area. + * @param space a carrier for the result ({@code null} permitted). + * + * @return The required space. + */ + protected AxisSpace calculateRangeAxisSpace(Graphics2D g2, + Rectangle2D plotArea, AxisSpace space) { + + if (space == null) { + space = new AxisSpace(); + } + + // reserve some space for the range axis... + if (this.fixedRangeAxisSpace != null) { + if (this.orientation == PlotOrientation.HORIZONTAL) { + space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(), + RectangleEdge.TOP); + space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(), + RectangleEdge.BOTTOM); + } + else if (this.orientation == PlotOrientation.VERTICAL) { + space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(), + RectangleEdge.LEFT); + space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(), + RectangleEdge.RIGHT); + } + } + else { + // reserve space for the range axes... + for (ValueAxis axis: this.rangeAxes.values()) { + if (axis != null) { + RectangleEdge edge = getRangeAxisEdge( + findRangeAxisIndex(axis)); + space = axis.reserveSpace(g2, this, plotArea, edge, space); + } + } + } + return space; + + } + + /** + * Trims a rectangle to integer coordinates. + * + * @param rect the incoming rectangle. + * + * @return A rectangle with integer coordinates. + */ + private Rectangle integerise(Rectangle2D rect) { + int x0 = (int) Math.ceil(rect.getMinX()); + int y0 = (int) Math.ceil(rect.getMinY()); + int x1 = (int) Math.floor(rect.getMaxX()); + int y1 = (int) Math.floor(rect.getMaxY()); + return new Rectangle(x0, y0, (x1 - x0), (y1 - y0)); + } + + /** + * Draws the plot within the specified area on a graphics device. + * + * @param g2 the graphics device. + * @param area the plot area (in Java2D space). + * @param anchor an anchor point in Java2D space ({@code null} + * permitted). + * @param parentState the state from the parent plot, if there is one + * ({@code null} permitted). + * @param info collects chart drawing information ({@code null} + * permitted). + */ + @Override + public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, + PlotState parentState, PlotRenderingInfo info) { + + // if the plot area is too small, just return... + boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); + boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); + if (b1 || b2) { + return; + } + + // record the plot area... + if (info != null) { + info.setPlotArea(area); + } + + // adjust the drawing area for the plot insets (if any)... + RectangleInsets insets = getInsets(); + insets.trim(area); + + AxisSpace space = calculateAxisSpace(g2, area); + Rectangle2D dataArea = space.shrink(area, null); + this.axisOffset.trim(dataArea); + + dataArea = integerise(dataArea); + if (dataArea.isEmpty()) { + return; + } + createAndAddEntity((Rectangle2D) dataArea.clone(), info, null, null); + if (info != null) { + info.setDataArea(dataArea); + } + + // draw the plot background and axes... + drawBackground(g2, dataArea); + Map axisStateMap = drawAxes(g2, area, dataArea, info); + + PlotOrientation orient = getOrientation(); + + // the anchor point is typically the point where the mouse last + // clicked - the crosshairs will be driven off this point... + if (anchor != null && !dataArea.contains(anchor)) { + anchor = null; + } + CrosshairState crosshairState = new CrosshairState(); + crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY); + crosshairState.setAnchor(anchor); + + crosshairState.setAnchorX(Double.NaN); + crosshairState.setAnchorY(Double.NaN); + if (anchor != null) { + ValueAxis domainAxis = getDomainAxis(); + if (domainAxis != null) { + double x; + if (orient == PlotOrientation.VERTICAL) { + x = domainAxis.java2DToValue(anchor.getX(), dataArea, + getDomainAxisEdge()); + } + else { + x = domainAxis.java2DToValue(anchor.getY(), dataArea, + getDomainAxisEdge()); + } + crosshairState.setAnchorX(x); + } + ValueAxis rangeAxis = getRangeAxis(); + if (rangeAxis != null) { + double y; + if (orient == PlotOrientation.VERTICAL) { + y = rangeAxis.java2DToValue(anchor.getY(), dataArea, + getRangeAxisEdge()); + } + else { + y = rangeAxis.java2DToValue(anchor.getX(), dataArea, + getRangeAxisEdge()); + } + crosshairState.setAnchorY(y); + } + } + crosshairState.setCrosshairX(getDomainCrosshairValue()); + crosshairState.setCrosshairY(getRangeCrosshairValue()); + Shape originalClip = g2.getClip(); + Composite originalComposite = g2.getComposite(); + + g2.clip(dataArea); + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + getForegroundAlpha())); + + AxisState domainAxisState = axisStateMap.get(getDomainAxis()); + if (domainAxisState == null) { + if (parentState != null) { + domainAxisState = (AxisState) parentState.getSharedAxisStates() + .get(getDomainAxis()); + } + } + + AxisState rangeAxisState = axisStateMap.get(getRangeAxis()); + if (rangeAxisState == null) { + if (parentState != null) { + rangeAxisState = (AxisState) parentState.getSharedAxisStates() + .get(getRangeAxis()); + } + } + if (domainAxisState != null) { + drawDomainTickBands(g2, dataArea, domainAxisState.getTicks()); + } + if (rangeAxisState != null) { + drawRangeTickBands(g2, dataArea, rangeAxisState.getTicks()); + } + if (domainAxisState != null) { + drawDomainGridlines(g2, dataArea, domainAxisState.getTicks()); + drawZeroDomainBaseline(g2, dataArea); + } + if (rangeAxisState != null) { + drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks()); + drawZeroRangeBaseline(g2, dataArea); + } + + Graphics2D savedG2 = g2; + BufferedImage dataImage = null; + boolean suppressShadow = Boolean.TRUE.equals(g2.getRenderingHint( + JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION)); + if (this.shadowGenerator != null && !suppressShadow) { + dataImage = new BufferedImage((int) dataArea.getWidth(), + (int)dataArea.getHeight(), BufferedImage.TYPE_INT_ARGB); + g2 = dataImage.createGraphics(); + g2.translate(-dataArea.getX(), -dataArea.getY()); + g2.setRenderingHints(savedG2.getRenderingHints()); + } + + // draw the markers that are associated with a specific dataset... + for (XYDataset dataset: this.datasets.values()) { + int datasetIndex = indexOf(dataset); + drawDomainMarkers(g2, dataArea, datasetIndex, Layer.BACKGROUND); + } + for (XYDataset dataset: this.datasets.values()) { + int datasetIndex = indexOf(dataset); + drawRangeMarkers(g2, dataArea, datasetIndex, Layer.BACKGROUND); + } + + // now draw annotations and render data items... + boolean foundData = false; + DatasetRenderingOrder order = getDatasetRenderingOrder(); + List rendererIndices = getRendererIndices(order); + List datasetIndices = getDatasetIndices(order); + + // draw background annotations + for (int i : rendererIndices) { + XYItemRenderer renderer = getRenderer(i); + if (renderer != null) { + ValueAxis domainAxis = getDomainAxisForDataset(i); + ValueAxis rangeAxis = getRangeAxisForDataset(i); + renderer.drawAnnotations(g2, dataArea, domainAxis, rangeAxis, + Layer.BACKGROUND, info); + } + } + + // render data items... + for (int datasetIndex : datasetIndices) { + XYDataset dataset = this.getDataset(datasetIndex); + foundData = render(g2, dataArea, datasetIndex, info, + crosshairState) || foundData; + } + + // draw foreground annotations + for (int i : rendererIndices) { + XYItemRenderer renderer = getRenderer(i); + if (renderer != null) { + ValueAxis domainAxis = getDomainAxisForDataset(i); + ValueAxis rangeAxis = getRangeAxisForDataset(i); + renderer.drawAnnotations(g2, dataArea, domainAxis, rangeAxis, + Layer.FOREGROUND, info); + } + } + + // draw domain crosshair if required... + int datasetIndex = crosshairState.getDatasetIndex(); + ValueAxis xAxis = getDomainAxisForDataset(datasetIndex); + RectangleEdge xAxisEdge = getDomainAxisEdge(getDomainAxisIndex(xAxis)); + if (!this.domainCrosshairLockedOnData && anchor != null) { + double xx; + if (orient == PlotOrientation.VERTICAL) { + xx = xAxis.java2DToValue(anchor.getX(), dataArea, xAxisEdge); + } + else { + xx = xAxis.java2DToValue(anchor.getY(), dataArea, xAxisEdge); + } + crosshairState.setCrosshairX(xx); + } + setDomainCrosshairValue(crosshairState.getCrosshairX(), false); + if (isDomainCrosshairVisible()) { + double x = getDomainCrosshairValue(); + Paint paint = getDomainCrosshairPaint(); + Stroke stroke = getDomainCrosshairStroke(); + drawDomainCrosshair(g2, dataArea, orient, x, xAxis, stroke, paint); + } + + // draw range crosshair if required... + ValueAxis yAxis = getRangeAxisForDataset(datasetIndex); + RectangleEdge yAxisEdge = getRangeAxisEdge(getRangeAxisIndex(yAxis)); + if (!this.rangeCrosshairLockedOnData && anchor != null) { + double yy; + if (orient == PlotOrientation.VERTICAL) { + yy = yAxis.java2DToValue(anchor.getY(), dataArea, yAxisEdge); + } else { + yy = yAxis.java2DToValue(anchor.getX(), dataArea, yAxisEdge); + } + crosshairState.setCrosshairY(yy); + } + setRangeCrosshairValue(crosshairState.getCrosshairY(), false); + if (isRangeCrosshairVisible()) { + double y = getRangeCrosshairValue(); + Paint paint = getRangeCrosshairPaint(); + Stroke stroke = getRangeCrosshairStroke(); + drawRangeCrosshair(g2, dataArea, orient, y, yAxis, stroke, paint); + } + + if (!foundData) { + drawNoDataMessage(g2, dataArea); + } + + for (int i : rendererIndices) { + drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND); + } + for (int i : rendererIndices) { + drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND); + } + + drawAnnotations(g2, dataArea, info); + if (this.shadowGenerator != null && !suppressShadow) { + BufferedImage shadowImage + = this.shadowGenerator.createDropShadow(dataImage); + g2 = savedG2; + g2.drawImage(shadowImage, (int) dataArea.getX() + + this.shadowGenerator.calculateOffsetX(), + (int) dataArea.getY() + + this.shadowGenerator.calculateOffsetY(), null); + g2.drawImage(dataImage, (int) dataArea.getX(), + (int) dataArea.getY(), null); + } + g2.setClip(originalClip); + g2.setComposite(originalComposite); + + drawOutline(g2, dataArea); + + } + + /** + * Returns the indices of the non-null datasets in the specified order. + * + * @param order the order ({@code null} not permitted). + * + * @return The list of indices. + */ + private List getDatasetIndices(DatasetRenderingOrder order) { + List result = new ArrayList<>(); + for (Entry entry : this.datasets.entrySet()) { + if (entry.getValue() != null) { + result.add(entry.getKey()); + } + } + Collections.sort(result); + if (order == DatasetRenderingOrder.REVERSE) { + Collections.reverse(result); + } + return result; + } + + private List getRendererIndices(DatasetRenderingOrder order) { + List result = new ArrayList<>(); + for (Entry entry : this.renderers.entrySet()) { + if (entry.getValue() != null) { + result.add(entry.getKey()); + } + } + Collections.sort(result); + if (order == DatasetRenderingOrder.REVERSE) { + Collections.reverse(result); + } + return result; + } + + /** + * Draws the background for the plot. + * + * @param g2 the graphics device. + * @param area the area. + */ + @Override + public void drawBackground(Graphics2D g2, Rectangle2D area) { + fillBackground(g2, area, this.orientation); + drawQuadrants(g2, area); + drawBackgroundImage(g2, area); + } + + /** + * Draws the quadrants. + * + * @param g2 the graphics device. + * @param area the area. + * + * @see #setQuadrantOrigin(Point2D) + * @see #setQuadrantPaint(int, Paint) + */ + protected void drawQuadrants(Graphics2D g2, Rectangle2D area) { + // 0 | 1 + // --+-- + // 2 | 3 + boolean somethingToDraw = false; + + ValueAxis xAxis = getDomainAxis(); + if (xAxis == null) { // we can't draw quadrants without a valid x-axis + return; + } + double x = xAxis.getRange().constrain(this.quadrantOrigin.getX()); + double xx = xAxis.valueToJava2D(x, area, getDomainAxisEdge()); + + ValueAxis yAxis = getRangeAxis(); + if (yAxis == null) { // we can't draw quadrants without a valid y-axis + return; + } + double y = yAxis.getRange().constrain(this.quadrantOrigin.getY()); + double yy = yAxis.valueToJava2D(y, area, getRangeAxisEdge()); + + double xmin = xAxis.getLowerBound(); + double xxmin = xAxis.valueToJava2D(xmin, area, getDomainAxisEdge()); + + double xmax = xAxis.getUpperBound(); + double xxmax = xAxis.valueToJava2D(xmax, area, getDomainAxisEdge()); + + double ymin = yAxis.getLowerBound(); + double yymin = yAxis.valueToJava2D(ymin, area, getRangeAxisEdge()); + + double ymax = yAxis.getUpperBound(); + double yymax = yAxis.valueToJava2D(ymax, area, getRangeAxisEdge()); + + Rectangle2D[] r = new Rectangle2D[] {null, null, null, null}; + if (this.quadrantPaint[0] != null) { + if (x > xmin && y < ymax) { + if (this.orientation == PlotOrientation.HORIZONTAL) { + r[0] = new Rectangle2D.Double(Math.min(yymax, yy), + Math.min(xxmin, xx), Math.abs(yy - yymax), + Math.abs(xx - xxmin)); + } + else { // PlotOrientation.VERTICAL + r[0] = new Rectangle2D.Double(Math.min(xxmin, xx), + Math.min(yymax, yy), Math.abs(xx - xxmin), + Math.abs(yy - yymax)); + } + somethingToDraw = true; + } + } + if (this.quadrantPaint[1] != null) { + if (x < xmax && y < ymax) { + if (this.orientation == PlotOrientation.HORIZONTAL) { + r[1] = new Rectangle2D.Double(Math.min(yymax, yy), + Math.min(xxmax, xx), Math.abs(yy - yymax), + Math.abs(xx - xxmax)); + } + else { // PlotOrientation.VERTICAL + r[1] = new Rectangle2D.Double(Math.min(xx, xxmax), + Math.min(yymax, yy), Math.abs(xx - xxmax), + Math.abs(yy - yymax)); + } + somethingToDraw = true; + } + } + if (this.quadrantPaint[2] != null) { + if (x > xmin && y > ymin) { + if (this.orientation == PlotOrientation.HORIZONTAL) { + r[2] = new Rectangle2D.Double(Math.min(yymin, yy), + Math.min(xxmin, xx), Math.abs(yy - yymin), + Math.abs(xx - xxmin)); + } + else { // PlotOrientation.VERTICAL + r[2] = new Rectangle2D.Double(Math.min(xxmin, xx), + Math.min(yymin, yy), Math.abs(xx - xxmin), + Math.abs(yy - yymin)); + } + somethingToDraw = true; + } + } + if (this.quadrantPaint[3] != null) { + if (x < xmax && y > ymin) { + if (this.orientation == PlotOrientation.HORIZONTAL) { + r[3] = new Rectangle2D.Double(Math.min(yymin, yy), + Math.min(xxmax, xx), Math.abs(yy - yymin), + Math.abs(xx - xxmax)); + } + else { // PlotOrientation.VERTICAL + r[3] = new Rectangle2D.Double(Math.min(xx, xxmax), + Math.min(yymin, yy), Math.abs(xx - xxmax), + Math.abs(yy - yymin)); + } + somethingToDraw = true; + } + } + if (somethingToDraw) { + Composite originalComposite = g2.getComposite(); + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + getBackgroundAlpha())); + for (int i = 0; i < 4; i++) { + if (this.quadrantPaint[i] != null && r[i] != null) { + g2.setPaint(this.quadrantPaint[i]); + g2.fill(r[i]); + } + } + g2.setComposite(originalComposite); + } + } + + /** + * Draws the domain tick bands, if any. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param ticks the ticks. + * + * @see #setDomainTickBandPaint(Paint) + */ + public void drawDomainTickBands(Graphics2D g2, Rectangle2D dataArea, + List ticks) { + Paint bandPaint = getDomainTickBandPaint(); + if (bandPaint != null) { + boolean fillBand = false; + ValueAxis xAxis = getDomainAxis(); + double previous = xAxis.getLowerBound(); + for (ValueTick tick : ticks) { + double current = tick.getValue(); + if (fillBand) { + getRenderer().fillDomainGridBand(g2, this, xAxis, dataArea, + previous, current); + } + previous = current; + fillBand = !fillBand; + } + double end = xAxis.getUpperBound(); + if (fillBand) { + getRenderer().fillDomainGridBand(g2, this, xAxis, dataArea, + previous, end); + } + } + } + + /** + * Draws the range tick bands, if any. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param ticks the ticks. + * + * @see #setRangeTickBandPaint(Paint) + */ + public void drawRangeTickBands(Graphics2D g2, Rectangle2D dataArea, + List ticks) { + Paint bandPaint = getRangeTickBandPaint(); + if (bandPaint != null) { + boolean fillBand = false; + ValueAxis axis = getRangeAxis(); + double previous = axis.getLowerBound(); + for (ValueTick tick : ticks) { + double current = tick.getValue(); + if (fillBand) { + getRenderer().fillRangeGridBand(g2, this, axis, dataArea, + previous, current); + } + previous = current; + fillBand = !fillBand; + } + double end = axis.getUpperBound(); + if (fillBand) { + getRenderer().fillRangeGridBand(g2, this, axis, dataArea, + previous, end); + } + } + } + + /** + * A utility method for drawing the axes. + * + * @param g2 the graphics device ({@code null} not permitted). + * @param plotArea the plot area ({@code null} not permitted). + * @param dataArea the data area ({@code null} not permitted). + * @param plotState collects information about the plot ({@code null} + * permitted). + * + * @return A map containing the state for each axis drawn. + */ + protected Map drawAxes(Graphics2D g2, Rectangle2D plotArea, + Rectangle2D dataArea, PlotRenderingInfo plotState) { + + AxisCollection axisCollection = new AxisCollection(); + + // add domain axes to lists... + for (ValueAxis axis : this.domainAxes.values()) { + if (axis != null) { + int axisIndex = findDomainAxisIndex(axis); + axisCollection.add(axis, getDomainAxisEdge(axisIndex)); + } + } + + // add range axes to lists... + for (ValueAxis axis : this.rangeAxes.values()) { + if (axis != null) { + int axisIndex = findRangeAxisIndex(axis); + axisCollection.add(axis, getRangeAxisEdge(axisIndex)); + } + } + + Map axisStateMap = new HashMap<>(); + + // draw the top axes + double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset( + dataArea.getHeight()); + Iterator iterator = axisCollection.getAxesAtTop().iterator(); + while (iterator.hasNext()) { + ValueAxis axis = (ValueAxis) iterator.next(); + AxisState info = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.TOP, plotState); + cursor = info.getCursor(); + axisStateMap.put(axis, info); + } + + // draw the bottom axes + cursor = dataArea.getMaxY() + + this.axisOffset.calculateBottomOutset(dataArea.getHeight()); + iterator = axisCollection.getAxesAtBottom().iterator(); + while (iterator.hasNext()) { + ValueAxis axis = (ValueAxis) iterator.next(); + AxisState info = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.BOTTOM, plotState); + cursor = info.getCursor(); + axisStateMap.put(axis, info); + } + + // draw the left axes + cursor = dataArea.getMinX() + - this.axisOffset.calculateLeftOutset(dataArea.getWidth()); + iterator = axisCollection.getAxesAtLeft().iterator(); + while (iterator.hasNext()) { + ValueAxis axis = (ValueAxis) iterator.next(); + AxisState info = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.LEFT, plotState); + cursor = info.getCursor(); + axisStateMap.put(axis, info); + } + + // draw the right axes + cursor = dataArea.getMaxX() + + this.axisOffset.calculateRightOutset(dataArea.getWidth()); + iterator = axisCollection.getAxesAtRight().iterator(); + while (iterator.hasNext()) { + ValueAxis axis = (ValueAxis) iterator.next(); + AxisState info = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.RIGHT, plotState); + cursor = info.getCursor(); + axisStateMap.put(axis, info); + } + + return axisStateMap; + } + + /** + * Draws a representation of the data within the dataArea region, using the + * current renderer. + *

+ * The {@code info} and {@code crosshairState} arguments may be + * {@code null}. + * + * @param g2 the graphics device. + * @param dataArea the region in which the data is to be drawn. + * @param index the dataset index. + * @param info an optional object for collection dimension information. + * @param crosshairState collects crosshair information + * ({@code null} permitted). + * + * @return A flag that indicates whether any data was actually rendered. + */ + public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, + PlotRenderingInfo info, CrosshairState crosshairState) { + + boolean foundData = false; + XYDataset dataset = getDataset(index); + if (!DatasetUtils.isEmptyOrNull(dataset)) { + foundData = true; + ValueAxis xAxis = getDomainAxisForDataset(index); + ValueAxis yAxis = getRangeAxisForDataset(index); + if (xAxis == null || yAxis == null) { + return foundData; // can't render anything without axes + } + XYItemRenderer renderer = getRenderer(index); + if (renderer == null) { + renderer = getRenderer(); + if (renderer == null) { // no default renderer available + return foundData; + } + } + + XYItemRendererState state = renderer.initialise(g2, dataArea, this, + dataset, info); + int passCount = renderer.getPassCount(); + + SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder(); + if (seriesOrder == SeriesRenderingOrder.REVERSE) { + //render series in reverse order + for (int pass = 0; pass < passCount; pass++) { + int seriesCount = dataset.getSeriesCount(); + for (int series = seriesCount - 1; series >= 0; series--) { + int firstItem = 0; + int lastItem = dataset.getItemCount(series) - 1; + if (lastItem == -1) { + continue; + } + if (state.getProcessVisibleItemsOnly()) { + int[] itemBounds = RendererUtils.findLiveItems( + dataset, series, xAxis.getLowerBound(), + xAxis.getUpperBound()); + firstItem = Math.max(itemBounds[0] - 1, 0); + lastItem = Math.min(itemBounds[1] + 1, lastItem); + } + state.startSeriesPass(dataset, series, firstItem, + lastItem, pass, passCount); + for (int item = firstItem; item <= lastItem; item++) { + renderer.drawItem(g2, state, dataArea, info, + this, xAxis, yAxis, dataset, series, item, + crosshairState, pass); + } + state.endSeriesPass(dataset, series, firstItem, + lastItem, pass, passCount); + } + } + } + else { + //render series in forward order + for (int pass = 0; pass < passCount; pass++) { + int seriesCount = dataset.getSeriesCount(); + for (int series = 0; series < seriesCount; series++) { + int firstItem = 0; + int lastItem = dataset.getItemCount(series) - 1; + if (state.getProcessVisibleItemsOnly()) { + int[] itemBounds = RendererUtils.findLiveItems( + dataset, series, xAxis.getLowerBound(), + xAxis.getUpperBound()); + firstItem = Math.max(itemBounds[0] - 1, 0); + lastItem = Math.min(itemBounds[1] + 1, lastItem); + } + state.startSeriesPass(dataset, series, firstItem, + lastItem, pass, passCount); + for (int item = firstItem; item <= lastItem; item++) { + renderer.drawItem(g2, state, dataArea, info, + this, xAxis, yAxis, dataset, series, item, + crosshairState, pass); + } + state.endSeriesPass(dataset, series, firstItem, + lastItem, pass, passCount); + } + } + } + } + return foundData; + } + + /** + * Returns the domain axis for a dataset. + * + * @param index the dataset index (must be >= 0). + * + * @return The axis. + */ + public ValueAxis getDomainAxisForDataset(int index) { + Args.requireNonNegative(index, "index"); + ValueAxis valueAxis; + List axisIndices = this.datasetToDomainAxesMap.get(index); + if (axisIndices != null) { + // the first axis in the list is used for data <--> Java2D + Integer axisIndex = axisIndices.get(0); + valueAxis = getDomainAxis(axisIndex); + } + else { + valueAxis = getDomainAxis(0); + } + return valueAxis; + } + + /** + * Returns the range axis for a dataset. + * + * @param index the dataset index (must be >= 0). + * + * @return The axis. + */ + public ValueAxis getRangeAxisForDataset(int index) { + Args.requireNonNegative(index, "index"); + ValueAxis valueAxis; + List axisIndices = this.datasetToRangeAxesMap.get(index); + if (axisIndices != null) { + // the first axis in the list is used for data <--> Java2D + Integer axisIndex = axisIndices.get(0); + valueAxis = getRangeAxis(axisIndex); + } + else { + valueAxis = getRangeAxis(0); + } + return valueAxis; + } + + /** + * Draws the gridlines for the plot, if they are visible. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param ticks the ticks. + * + * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List) + */ + protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea, + List ticks) { + + // no renderer, no gridlines... + if (getRenderer() == null) { + return; + } + + // draw the domain grid lines, if any... + if (isDomainGridlinesVisible() || isDomainMinorGridlinesVisible()) { + Stroke gridStroke = null; + Paint gridPaint = null; + Iterator iterator = ticks.iterator(); + boolean paintLine; + while (iterator.hasNext()) { + paintLine = false; + ValueTick tick = (ValueTick) iterator.next(); + if ((tick.getTickType() == TickType.MINOR) + && isDomainMinorGridlinesVisible()) { + gridStroke = getDomainMinorGridlineStroke(); + gridPaint = getDomainMinorGridlinePaint(); + paintLine = true; + } else if ((tick.getTickType() == TickType.MAJOR) + && isDomainGridlinesVisible()) { + gridStroke = getDomainGridlineStroke(); + gridPaint = getDomainGridlinePaint(); + paintLine = true; + } + XYItemRenderer r = getRenderer(); + if ((r instanceof AbstractXYItemRenderer) && paintLine) { + ((AbstractXYItemRenderer) r).drawDomainLine(g2, this, + getDomainAxis(), dataArea, tick.getValue(), + gridPaint, gridStroke); + } + } + } + } + + /** + * Draws the gridlines for the plot's primary range axis, if they are + * visible. + * + * @param g2 the graphics device. + * @param area the data area. + * @param ticks the ticks. + * + * @see #drawDomainGridlines(Graphics2D, Rectangle2D, List) + */ + protected void drawRangeGridlines(Graphics2D g2, Rectangle2D area, + List ticks) { + + // no renderer, no gridlines... + if (getRenderer() == null) { + return; + } + + // draw the range grid lines, if any... + if (isRangeGridlinesVisible() || isRangeMinorGridlinesVisible()) { + Stroke gridStroke = null; + Paint gridPaint = null; + ValueAxis axis = getRangeAxis(); + if (axis != null) { + for (ValueTick tick : ticks) { + boolean paintLine = false; + if ((tick.getTickType() == TickType.MINOR) + && isRangeMinorGridlinesVisible()) { + gridStroke = getRangeMinorGridlineStroke(); + gridPaint = getRangeMinorGridlinePaint(); + paintLine = true; + } else if ((tick.getTickType() == TickType.MAJOR) + && isRangeGridlinesVisible()) { + gridStroke = getRangeGridlineStroke(); + gridPaint = getRangeGridlinePaint(); + paintLine = true; + } + if ((tick.getValue() != 0.0 + || !isRangeZeroBaselineVisible()) && paintLine) { + getRenderer().drawRangeLine(g2, this, getRangeAxis(), + area, tick.getValue(), gridPaint, gridStroke); + } + } + } + } + } + + /** + * Draws a base line across the chart at value zero on the domain axis. + * + * @param g2 the graphics device. + * @param area the data area. + * + * @see #setDomainZeroBaselineVisible(boolean) + */ + protected void drawZeroDomainBaseline(Graphics2D g2, Rectangle2D area) { + if (isDomainZeroBaselineVisible() && getRenderer() != null) { + getRenderer().drawDomainLine(g2, this, getDomainAxis(), area, 0.0, + this.domainZeroBaselinePaint, + this.domainZeroBaselineStroke); + } + } + + /** + * Draws a base line across the chart at value zero on the range axis. + * + * @param g2 the graphics device. + * @param area the data area. + * + * @see #setRangeZeroBaselineVisible(boolean) + */ + protected void drawZeroRangeBaseline(Graphics2D g2, Rectangle2D area) { + if (isRangeZeroBaselineVisible()) { + getRenderer().drawRangeLine(g2, this, getRangeAxis(), area, 0.0, + this.rangeZeroBaselinePaint, this.rangeZeroBaselineStroke); + } + } + + /** + * Draws the annotations for the plot. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param info the chart rendering info. + */ + public void drawAnnotations(Graphics2D g2, Rectangle2D dataArea, + PlotRenderingInfo info) { + + for (XYAnnotation annotation : this.annotations) { + ValueAxis xAxis = getDomainAxis(); + ValueAxis yAxis = getRangeAxis(); + annotation.draw(g2, this, dataArea, xAxis, yAxis, 0, info); + } + + } + + /** + * Draws the domain markers (if any) for an axis and layer. This method is + * typically called from within the draw() method. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param index the dataset/renderer index. + * @param layer the layer (foreground or background). + */ + protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea, + int index, Layer layer) { + + XYItemRenderer r = getRenderer(index); + if (r == null) { + return; + } + // check that the renderer has a corresponding dataset (it doesn't + // matter if the dataset is null) + if (!this.datasets.containsKey(index)) { + return; + } + Collection markers = getDomainMarkers(index, layer); + ValueAxis axis = getDomainAxisForDataset(index); + if (markers != null && axis != null) { + for (Marker marker : markers) { + r.drawDomainMarker(g2, this, axis, marker, dataArea); + } + } + + } + + /** + * Draws the range markers (if any) for a renderer and layer. This method + * is typically called from within the draw() method. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param index the renderer index. + * @param layer the layer (foreground or background). + */ + protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea, + int index, Layer layer) { + + XYItemRenderer r = getRenderer(index); + if (r == null) { + return; + } + // check that the renderer has a corresponding dataset (it doesn't + // matter if the dataset is null) + if (!this.datasets.containsKey(index)) { + return; + } + Collection markers = getRangeMarkers(index, layer); + ValueAxis axis = getRangeAxisForDataset(index); + if (markers != null && axis != null) { + for (Marker marker : markers) { + r.drawRangeMarker(g2, this, axis, marker, dataArea); + } + } + } + + /** + * Returns the list of domain markers (read only) for the specified layer. + * + * @param layer the layer (foreground or background). + * + * @return The list of domain markers. + * + * @see #getRangeMarkers(Layer) + */ + public Collection getDomainMarkers(Layer layer) { + return getDomainMarkers(0, layer); + } + + /** + * Returns the list of range markers (read only) for the specified layer. + * + * @param layer the layer (foreground or background). + * + * @return The list of range markers. + * + * @see #getDomainMarkers(Layer) + */ + public Collection getRangeMarkers(Layer layer) { + return getRangeMarkers(0, layer); + } + + /** + * Returns a collection of domain markers for a particular renderer and + * layer. + * + * @param index the renderer index. + * @param layer the layer. + * + * @return A collection of markers (possibly {@code null}). + * + * @see #getRangeMarkers(int, Layer) + */ + public Collection getDomainMarkers(int index, Layer layer) { + Collection result = null; + if (layer == Layer.FOREGROUND) { + result = this.foregroundDomainMarkers.get(index); + } + else if (layer == Layer.BACKGROUND) { + result = this.backgroundDomainMarkers.get(index); + } + if (result != null) { + result = Collections.unmodifiableCollection(result); + } + return result; + } + + /** + * Returns a collection of range markers for a particular renderer and + * layer. + * + * @param index the renderer index. + * @param layer the layer. + * + * @return A collection of markers (possibly {@code null}). + * + * @see #getDomainMarkers(int, Layer) + */ + public Collection getRangeMarkers(int index, Layer layer) { + Collection result = null; + if (layer == Layer.FOREGROUND) { + result = this.foregroundRangeMarkers.get(index); + } + else if (layer == Layer.BACKGROUND) { + result = this.backgroundRangeMarkers.get(index); + } + if (result != null) { + result = Collections.unmodifiableCollection(result); + } + return result; + } + + /** + * Utility method for drawing a horizontal line across the data area of the + * plot. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param value the coordinate, where to draw the line. + * @param stroke the stroke to use. + * @param paint the paint to use. + */ + protected void drawHorizontalLine(Graphics2D g2, Rectangle2D dataArea, + double value, Stroke stroke, + Paint paint) { + + ValueAxis axis = getRangeAxis(); + if (getOrientation() == PlotOrientation.HORIZONTAL) { + axis = getDomainAxis(); + } + if (axis.getRange().contains(value)) { + double yy = axis.valueToJava2D(value, dataArea, RectangleEdge.LEFT); + Line2D line = new Line2D.Double(dataArea.getMinX(), yy, + dataArea.getMaxX(), yy); + g2.setStroke(stroke); + g2.setPaint(paint); + g2.draw(line); + } + + } + + /** + * Draws a domain crosshair. + * + * @param g2 the graphics target. + * @param dataArea the data area. + * @param orientation the plot orientation. + * @param value the crosshair value. + * @param axis the axis against which the value is measured. + * @param stroke the stroke used to draw the crosshair line. + * @param paint the paint used to draw the crosshair line. + */ + protected void drawDomainCrosshair(Graphics2D g2, Rectangle2D dataArea, + PlotOrientation orientation, double value, ValueAxis axis, + Stroke stroke, Paint paint) { + + if (!axis.getRange().contains(value)) { + return; + } + Line2D line; + if (orientation == PlotOrientation.VERTICAL) { + double xx = axis.valueToJava2D(value, dataArea, + RectangleEdge.BOTTOM); + line = new Line2D.Double(xx, dataArea.getMinY(), xx, + dataArea.getMaxY()); + } else { + double yy = axis.valueToJava2D(value, dataArea, + RectangleEdge.LEFT); + line = new Line2D.Double(dataArea.getMinX(), yy, + dataArea.getMaxX(), yy); + } + Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_NORMALIZE); + g2.setStroke(stroke); + g2.setPaint(paint); + g2.draw(line); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); + } + + /** + * Utility method for drawing a vertical line on the data area of the plot. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param value the coordinate, where to draw the line. + * @param stroke the stroke to use. + * @param paint the paint to use. + */ + protected void drawVerticalLine(Graphics2D g2, Rectangle2D dataArea, + double value, Stroke stroke, Paint paint) { + + ValueAxis axis = getDomainAxis(); + if (getOrientation() == PlotOrientation.HORIZONTAL) { + axis = getRangeAxis(); + } + if (axis.getRange().contains(value)) { + double xx = axis.valueToJava2D(value, dataArea, + RectangleEdge.BOTTOM); + Line2D line = new Line2D.Double(xx, dataArea.getMinY(), xx, + dataArea.getMaxY()); + g2.setStroke(stroke); + g2.setPaint(paint); + g2.draw(line); + } + + } + + /** + * Draws a range crosshair. + * + * @param g2 the graphics target. + * @param dataArea the data area. + * @param orientation the plot orientation. + * @param value the crosshair value. + * @param axis the axis against which the value is measured. + * @param stroke the stroke used to draw the crosshair line. + * @param paint the paint used to draw the crosshair line. + */ + protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea, + PlotOrientation orientation, double value, ValueAxis axis, + Stroke stroke, Paint paint) { + + if (!axis.getRange().contains(value)) { + return; + } + Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_NORMALIZE); + Line2D line; + if (orientation == PlotOrientation.HORIZONTAL) { + double xx = axis.valueToJava2D(value, dataArea, + RectangleEdge.BOTTOM); + line = new Line2D.Double(xx, dataArea.getMinY(), xx, + dataArea.getMaxY()); + } else { + double yy = axis.valueToJava2D(value, dataArea, RectangleEdge.LEFT); + line = new Line2D.Double(dataArea.getMinX(), yy, + dataArea.getMaxX(), yy); + } + g2.setStroke(stroke); + g2.setPaint(paint); + g2.draw(line); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); + } + + /** + * Handles a 'click' on the plot by updating the anchor values. + * + * @param x the x-coordinate, where the click occurred, in Java2D space. + * @param y the y-coordinate, where the click occurred, in Java2D space. + * @param info object containing information about the plot dimensions. + */ + @Override + public void handleClick(int x, int y, PlotRenderingInfo info) { + + Rectangle2D dataArea = info.getDataArea(); + if (dataArea.contains(x, y)) { + // set the anchor value for the horizontal axis... + ValueAxis xaxis = getDomainAxis(); + if (xaxis != null) { + double hvalue = xaxis.java2DToValue(x, info.getDataArea(), + getDomainAxisEdge()); + setDomainCrosshairValue(hvalue); + } + + // set the anchor value for the vertical axis... + ValueAxis yaxis = getRangeAxis(); + if (yaxis != null) { + double vvalue = yaxis.java2DToValue(y, info.getDataArea(), + getRangeAxisEdge()); + setRangeCrosshairValue(vvalue); + } + } + } + + /** + * A utility method that returns a list of datasets that are mapped to a + * particular axis. + * + * @param axisIndex the axis index ({@code null} not permitted). + * + * @return A list of datasets. + */ + private List getDatasetsMappedToDomainAxis(Integer axisIndex) { + Args.nullNotPermitted(axisIndex, "axisIndex"); + List result = new ArrayList<>(); + for (Entry entry : this.datasets.entrySet()) { + int index = entry.getKey(); + List mappedAxes = this.datasetToDomainAxesMap.get(index); + if (mappedAxes == null) { + if (axisIndex.equals(ZERO)) { + result.add(entry.getValue()); + } + } else { + if (mappedAxes.contains(axisIndex)) { + result.add(entry.getValue()); + } + } + } + return result; + } + + /** + * A utility method that returns a list of datasets that are mapped to a + * particular axis. + * + * @param axisIndex the axis index ({@code null} not permitted). + * + * @return A list of datasets. + */ + private List getDatasetsMappedToRangeAxis(Integer axisIndex) { + Args.nullNotPermitted(axisIndex, "axisIndex"); + List result = new ArrayList<>(); + for (Entry entry : this.datasets.entrySet()) { + int index = entry.getKey(); + List mappedAxes = this.datasetToRangeAxesMap.get(index); + if (mappedAxes == null) { + if (axisIndex.equals(ZERO)) { + result.add(entry.getValue()); + } + } else { + if (mappedAxes.contains(axisIndex)) { + result.add(entry.getValue()); + } + } + } + return result; + } + + /** + * Returns the index of the given domain axis. + * + * @param axis the axis. + * + * @return The axis index. + * + * @see #getRangeAxisIndex(ValueAxis) + */ + public int getDomainAxisIndex(ValueAxis axis) { + int result = findDomainAxisIndex(axis); + if (result < 0) { + // try the parent plot + Plot parent = getParent(); + if (parent instanceof XYPlot) { + XYPlot p = (XYPlot) parent; + result = p.getDomainAxisIndex(axis); + } + } + return result; + } + + private int findDomainAxisIndex(ValueAxis axis) { + for (Map.Entry entry : this.domainAxes.entrySet()) { + if (entry.getValue() == axis) { + return entry.getKey(); + } + } + return -1; + } + + /** + * Returns the index of the given range axis. + * + * @param axis the axis. + * + * @return The axis index. + * + * @see #getDomainAxisIndex(ValueAxis) + */ + public int getRangeAxisIndex(ValueAxis axis) { + int result = findRangeAxisIndex(axis); + if (result < 0) { + // try the parent plot + Plot parent = getParent(); + if (parent instanceof XYPlot) { + XYPlot p = (XYPlot) parent; + result = p.getRangeAxisIndex(axis); + } + } + return result; + } + + private int findRangeAxisIndex(ValueAxis axis) { + for (Map.Entry entry : this.rangeAxes.entrySet()) { + if (entry.getValue() == axis) { + return entry.getKey(); + } + } + return -1; + } + + /** + * Returns the range for the specified axis. + * + * @param axis the axis. + * + * @return The range. + */ + @Override + public Range getDataRange(ValueAxis axis) { + Range result = null; + List mappedDatasets = new ArrayList<>(); + List includedAnnotations = new ArrayList<>(); + boolean isDomainAxis = true; + + // is it a domain axis? + int domainIndex = getDomainAxisIndex(axis); + if (domainIndex >= 0) { + isDomainAxis = true; + mappedDatasets.addAll(getDatasetsMappedToDomainAxis(domainIndex)); + if (domainIndex == 0) { + // grab the plot's annotations + for (XYAnnotation annotation : this.annotations) { + if (annotation instanceof XYAnnotationBoundsInfo) { + includedAnnotations.add(annotation); + } + } + } + } + + // or is it a range axis? + int rangeIndex = getRangeAxisIndex(axis); + if (rangeIndex >= 0) { + isDomainAxis = false; + mappedDatasets.addAll(getDatasetsMappedToRangeAxis(rangeIndex)); + if (rangeIndex == 0) { + Iterator iterator = this.annotations.iterator(); + while (iterator.hasNext()) { + XYAnnotation annotation = (XYAnnotation) iterator.next(); + if (annotation instanceof XYAnnotationBoundsInfo) { + includedAnnotations.add(annotation); + } + } + } + } + + // iterate through the datasets that map to the axis and get the union + // of the ranges. + for (XYDataset d : mappedDatasets) { + if (d != null) { + XYItemRenderer r = getRendererForDataset(d); + if (isDomainAxis) { + if (r != null) { + result = Range.combine(result, r.findDomainBounds(d)); + } + else { + result = Range.combine(result, + DatasetUtils.findDomainBounds(d)); + } + } + else { + if (r != null) { + result = Range.combine(result, r.findRangeBounds(d)); + } + else { + result = Range.combine(result, + DatasetUtils.findRangeBounds(d)); + } + } + // FIXME: the XYItemRenderer interface doesn't specify the + // getAnnotations() method but it should + if (r instanceof AbstractXYItemRenderer) { + AbstractXYItemRenderer rr = (AbstractXYItemRenderer) r; + Collection c = rr.getAnnotations(); + Iterator i = c.iterator(); + while (i.hasNext()) { + XYAnnotation a = (XYAnnotation) i.next(); + if (a instanceof XYAnnotationBoundsInfo) { + includedAnnotations.add(a); + } + } + } + } + } + + Iterator it = includedAnnotations.iterator(); + while (it.hasNext()) { + XYAnnotationBoundsInfo xyabi = (XYAnnotationBoundsInfo) it.next(); + if (xyabi.getIncludeInDataBounds()) { + if (isDomainAxis) { + result = Range.combine(result, xyabi.getXRange()); + } + else { + result = Range.combine(result, xyabi.getYRange()); + } + } + } + return result; + } + + /** + * Receives notification of a change to an annotation on this plot. + * + * @param event information about the event (not used here). + */ + @Override + public void annotationChanged(AnnotationChangeEvent event) { + if (getParent() != null) { + getParent().annotationChanged(event); + } + else { + PlotChangeEvent e = new PlotChangeEvent(this); + notifyListeners(e); + } + } + + /** + * Receives notification of a change to the plot's dataset. + *

+ * The axis ranges are updated if necessary. + * + * @param event information about the event (not used here). + */ + @Override + public void datasetChanged(DatasetChangeEvent event) { + configureDomainAxes(); + configureRangeAxes(); + if (getParent() != null) { + getParent().datasetChanged(event); + } + else { + PlotChangeEvent e = new PlotChangeEvent(this); + e.setType(ChartChangeEventType.DATASET_UPDATED); + notifyListeners(e); + } + } + + /** + * Receives notification of a renderer change event. + * + * @param event the event. + */ + @Override + public void rendererChanged(RendererChangeEvent event) { + // if the event was caused by a change to series visibility, then + // the axis ranges might need updating... + if (event.getSeriesVisibilityChanged()) { + configureDomainAxes(); + configureRangeAxes(); + } + fireChangeEvent(); + } + + /** + * Returns a flag indicating whether or not the domain crosshair is visible. + * + * @return The flag. + * + * @see #setDomainCrosshairVisible(boolean) + */ + public boolean isDomainCrosshairVisible() { + return this.domainCrosshairVisible; + } + + /** + * Sets the flag indicating whether or not the domain crosshair is visible + * and, if the flag changes, sends a {@link PlotChangeEvent} to all + * registered listeners. + * + * @param flag the new value of the flag. + * + * @see #isDomainCrosshairVisible() + */ + public void setDomainCrosshairVisible(boolean flag) { + if (this.domainCrosshairVisible != flag) { + this.domainCrosshairVisible = flag; + fireChangeEvent(); + } + } + + /** + * Returns a flag indicating whether or not the crosshair should "lock-on" + * to actual data values. + * + * @return The flag. + * + * @see #setDomainCrosshairLockedOnData(boolean) + */ + public boolean isDomainCrosshairLockedOnData() { + return this.domainCrosshairLockedOnData; + } + + /** + * Sets the flag indicating whether or not the domain crosshair should + * "lock-on" to actual data values. If the flag value changes, this + * method sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param flag the flag. + * + * @see #isDomainCrosshairLockedOnData() + */ + public void setDomainCrosshairLockedOnData(boolean flag) { + if (this.domainCrosshairLockedOnData != flag) { + this.domainCrosshairLockedOnData = flag; + fireChangeEvent(); + } + } + + /** + * Returns the domain crosshair value. + * + * @return The value. + * + * @see #setDomainCrosshairValue(double) + */ + public double getDomainCrosshairValue() { + return this.domainCrosshairValue; + } + + /** + * Sets the domain crosshair value and sends a {@link PlotChangeEvent} to + * all registered listeners (provided that the domain crosshair is visible). + * + * @param value the value. + * + * @see #getDomainCrosshairValue() + */ + public void setDomainCrosshairValue(double value) { + setDomainCrosshairValue(value, true); + } + + /** + * Sets the domain crosshair value and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners (provided that the + * domain crosshair is visible). + * + * @param value the new value. + * @param notify notify listeners? + * + * @see #getDomainCrosshairValue() + */ + public void setDomainCrosshairValue(double value, boolean notify) { + this.domainCrosshairValue = value; + if (isDomainCrosshairVisible() && notify) { + fireChangeEvent(); + } + } + + /** + * Returns the {@link Stroke} used to draw the crosshair (if visible). + * + * @return The crosshair stroke (never {@code null}). + * + * @see #setDomainCrosshairStroke(Stroke) + * @see #isDomainCrosshairVisible() + * @see #getDomainCrosshairPaint() + */ + public Stroke getDomainCrosshairStroke() { + return this.domainCrosshairStroke; + } + + /** + * Sets the Stroke used to draw the crosshairs (if visible) and notifies + * registered listeners that the axis has been modified. + * + * @param stroke the new crosshair stroke ({@code null} not + * permitted). + * + * @see #getDomainCrosshairStroke() + */ + public void setDomainCrosshairStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.domainCrosshairStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the domain crosshair paint. + * + * @return The crosshair paint (never {@code null}). + * + * @see #setDomainCrosshairPaint(Paint) + * @see #isDomainCrosshairVisible() + * @see #getDomainCrosshairStroke() + */ + public Paint getDomainCrosshairPaint() { + return this.domainCrosshairPaint; + } + + /** + * Sets the paint used to draw the crosshairs (if visible) and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the new crosshair paint ({@code null} not permitted). + * + * @see #getDomainCrosshairPaint() + */ + public void setDomainCrosshairPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.domainCrosshairPaint = paint; + fireChangeEvent(); + } + + /** + * Returns a flag indicating whether or not the range crosshair is visible. + * + * @return The flag. + * + * @see #setRangeCrosshairVisible(boolean) + * @see #isDomainCrosshairVisible() + */ + public boolean isRangeCrosshairVisible() { + return this.rangeCrosshairVisible; + } + + /** + * Sets the flag indicating whether or not the range crosshair is visible. + * If the flag value changes, this method sends a {@link PlotChangeEvent} + * to all registered listeners. + * + * @param flag the new value of the flag. + * + * @see #isRangeCrosshairVisible() + */ + public void setRangeCrosshairVisible(boolean flag) { + if (this.rangeCrosshairVisible != flag) { + this.rangeCrosshairVisible = flag; + fireChangeEvent(); + } + } + + /** + * Returns a flag indicating whether or not the crosshair should "lock-on" + * to actual data values. + * + * @return The flag. + * + * @see #setRangeCrosshairLockedOnData(boolean) + */ + public boolean isRangeCrosshairLockedOnData() { + return this.rangeCrosshairLockedOnData; + } + + /** + * Sets the flag indicating whether or not the range crosshair should + * "lock-on" to actual data values. If the flag value changes, this method + * sends a {@link PlotChangeEvent} to all registered listeners. + * + * @param flag the flag. + * + * @see #isRangeCrosshairLockedOnData() + */ + public void setRangeCrosshairLockedOnData(boolean flag) { + if (this.rangeCrosshairLockedOnData != flag) { + this.rangeCrosshairLockedOnData = flag; + fireChangeEvent(); + } + } + + /** + * Returns the range crosshair value. + * + * @return The value. + * + * @see #setRangeCrosshairValue(double) + */ + public double getRangeCrosshairValue() { + return this.rangeCrosshairValue; + } + + /** + * Sets the range crosshair value. + *

+ * Registered listeners are notified that the plot has been modified, but + * only if the crosshair is visible. + * + * @param value the new value. + * + * @see #getRangeCrosshairValue() + */ + public void setRangeCrosshairValue(double value) { + setRangeCrosshairValue(value, true); + } + + /** + * Sets the range crosshair value and sends a {@link PlotChangeEvent} to + * all registered listeners, but only if the crosshair is visible. + * + * @param value the new value. + * @param notify a flag that controls whether or not listeners are + * notified. + * + * @see #getRangeCrosshairValue() + */ + public void setRangeCrosshairValue(double value, boolean notify) { + this.rangeCrosshairValue = value; + if (isRangeCrosshairVisible() && notify) { + fireChangeEvent(); + } + } + + /** + * Returns the stroke used to draw the crosshair (if visible). + * + * @return The crosshair stroke (never {@code null}). + * + * @see #setRangeCrosshairStroke(Stroke) + * @see #isRangeCrosshairVisible() + * @see #getRangeCrosshairPaint() + */ + public Stroke getRangeCrosshairStroke() { + return this.rangeCrosshairStroke; + } + + /** + * Sets the stroke used to draw the crosshairs (if visible) and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param stroke the new crosshair stroke ({@code null} not + * permitted). + * + * @see #getRangeCrosshairStroke() + */ + public void setRangeCrosshairStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.rangeCrosshairStroke = stroke; + fireChangeEvent(); + } + + /** + * Returns the range crosshair paint. + * + * @return The crosshair paint (never {@code null}). + * + * @see #setRangeCrosshairPaint(Paint) + * @see #isRangeCrosshairVisible() + * @see #getRangeCrosshairStroke() + */ + public Paint getRangeCrosshairPaint() { + return this.rangeCrosshairPaint; + } + + /** + * Sets the paint used to color the crosshairs (if visible) and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param paint the new crosshair paint ({@code null} not permitted). + * + * @see #getRangeCrosshairPaint() + */ + public void setRangeCrosshairPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.rangeCrosshairPaint = paint; + fireChangeEvent(); + } + + /** + * Returns the fixed domain axis space. + * + * @return The fixed domain axis space (possibly {@code null}). + * + * @see #setFixedDomainAxisSpace(AxisSpace) + */ + public AxisSpace getFixedDomainAxisSpace() { + return this.fixedDomainAxisSpace; + } + + /** + * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param space the space ({@code null} permitted). + * + * @see #getFixedDomainAxisSpace() + */ + public void setFixedDomainAxisSpace(AxisSpace space) { + setFixedDomainAxisSpace(space, true); + } + + /** + * Sets the fixed domain axis space and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param space the space ({@code null} permitted). + * @param notify notify listeners? + * + * @see #getFixedDomainAxisSpace() + */ + public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) { + this.fixedDomainAxisSpace = space; + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns the fixed range axis space. + * + * @return The fixed range axis space (possibly {@code null}). + * + * @see #setFixedRangeAxisSpace(AxisSpace) + */ + public AxisSpace getFixedRangeAxisSpace() { + return this.fixedRangeAxisSpace; + } + + /** + * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to + * all registered listeners. + * + * @param space the space ({@code null} permitted). + * + * @see #getFixedRangeAxisSpace() + */ + public void setFixedRangeAxisSpace(AxisSpace space) { + setFixedRangeAxisSpace(space, true); + } + + /** + * Sets the fixed range axis space and, if requested, sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * @param space the space ({@code null} permitted). + * @param notify notify listeners? + * + * @see #getFixedRangeAxisSpace() + */ + public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) { + this.fixedRangeAxisSpace = space; + if (notify) { + fireChangeEvent(); + } + } + + /** + * Returns {@code true} if panning is enabled for the domain axes, + * and {@code false} otherwise. + * + * @return A boolean. + */ + @Override + public boolean isDomainPannable() { + return this.domainPannable; + } + + /** + * Sets the flag that enables or disables panning of the plot along the + * domain axes. + * + * @param pannable the new flag value. + */ + public void setDomainPannable(boolean pannable) { + this.domainPannable = pannable; + } + + /** + * Returns {@code true} if panning is enabled for the range axis/axes, + * and {@code false} otherwise. The default value is {@code false}. + * + * @return A boolean. + */ + @Override + public boolean isRangePannable() { + return this.rangePannable; + } + + /** + * Sets the flag that enables or disables panning of the plot along + * the range axis/axes. + * + * @param pannable the new flag value. + */ + public void setRangePannable(boolean pannable) { + this.rangePannable = pannable; + } + + /** + * Pans the domain axes by the specified percentage. + * + * @param percent the distance to pan (as a percentage of the axis length). + * @param info the plot info + * @param source the source point where the pan action started. + */ + @Override + public void panDomainAxes(double percent, PlotRenderingInfo info, + Point2D source) { + if (!isDomainPannable()) { + return; + } + int domainAxisCount = getDomainAxisCount(); + for (int i = 0; i < domainAxisCount; i++) { + ValueAxis axis = getDomainAxis(i); + if (axis == null) { + continue; + } + + axis.pan(axis.isInverted() ? -percent : percent); + } + } + + /** + * Pans the range axes by the specified percentage. + * + * @param percent the distance to pan (as a percentage of the axis length). + * @param info the plot info + * @param source the source point where the pan action started. + */ + @Override + public void panRangeAxes(double percent, PlotRenderingInfo info, + Point2D source) { + if (!isRangePannable()) { + return; + } + int rangeAxisCount = getRangeAxisCount(); + for (int i = 0; i < rangeAxisCount; i++) { + ValueAxis axis = getRangeAxis(i); + if (axis == null) { + continue; + } + + axis.pan(axis.isInverted() ? -percent : percent); + } + } + + /** + * Multiplies the range on the domain axis/axes by the specified factor. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point (in Java2D space). + * + * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D) + */ + @Override + public void zoomDomainAxes(double factor, PlotRenderingInfo info, + Point2D source) { + // delegate to other method + zoomDomainAxes(factor, info, source, false); + } + + /** + * Multiplies the range on the domain axis/axes by the specified factor. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point (in Java2D space). + * @param useAnchor use source point as zoom anchor? + * + * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean) + */ + @Override + public void zoomDomainAxes(double factor, PlotRenderingInfo info, + Point2D source, boolean useAnchor) { + + // perform the zoom on each domain axis + for (ValueAxis xAxis : this.domainAxes.values()) { + if (xAxis == null) { + continue; + } + if (useAnchor) { + // get the relevant source coordinate given the plot orientation + double sourceX = source.getX(); + if (this.orientation == PlotOrientation.HORIZONTAL) { + sourceX = source.getY(); + } + double anchorX = xAxis.java2DToValue(sourceX, + info.getDataArea(), getDomainAxisEdge()); + xAxis.resizeRange2(factor, anchorX); + } else { + xAxis.resizeRange(factor); + } + } + } + + /** + * Zooms in on the domain axis/axes. The new lower and upper bounds are + * specified as percentages of the current axis range, where 0 percent is + * the current lower bound and 100 percent is the current upper bound. + * + * @param lowerPercent a percentage that determines the new lower bound + * for the axis (e.g. 0.20 is twenty percent). + * @param upperPercent a percentage that determines the new upper bound + * for the axis (e.g. 0.80 is eighty percent). + * @param info the plot rendering info. + * @param source the source point (ignored). + * + * @see #zoomRangeAxes(double, double, PlotRenderingInfo, Point2D) + */ + @Override + public void zoomDomainAxes(double lowerPercent, double upperPercent, + PlotRenderingInfo info, Point2D source) { + for (ValueAxis xAxis : this.domainAxes.values()) { + if (xAxis != null) { + xAxis.zoomRange(lowerPercent, upperPercent); + } + } + } + + /** + * Multiplies the range on the range axis/axes by the specified factor. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point. + * + * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) + */ + @Override + public void zoomRangeAxes(double factor, PlotRenderingInfo info, + Point2D source) { + // delegate to other method + zoomRangeAxes(factor, info, source, false); + } + + /** + * Multiplies the range on the range axis/axes by the specified factor. + * + * @param factor the zoom factor. + * @param info the plot rendering info. + * @param source the source point. + * @param useAnchor a flag that controls whether or not the source point + * is used for the zoom anchor. + * + * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) + */ + @Override + public void zoomRangeAxes(double factor, PlotRenderingInfo info, + Point2D source, boolean useAnchor) { + + // perform the zoom on each range axis + for (ValueAxis yAxis : this.rangeAxes.values()) { + if (yAxis == null) { + continue; + } + if (useAnchor) { + // get the relevant source coordinate given the plot orientation + double sourceY = source.getY(); + if (this.orientation == PlotOrientation.HORIZONTAL) { + sourceY = source.getX(); + } + double anchorY = yAxis.java2DToValue(sourceY, + info.getDataArea(), getRangeAxisEdge()); + yAxis.resizeRange2(factor, anchorY); + } else { + yAxis.resizeRange(factor); + } + } + } + + /** + * Zooms in on the range axes. + * + * @param lowerPercent the lower bound. + * @param upperPercent the upper bound. + * @param info the plot rendering info. + * @param source the source point. + * + * @see #zoomDomainAxes(double, double, PlotRenderingInfo, Point2D) + */ + @Override + public void zoomRangeAxes(double lowerPercent, double upperPercent, + PlotRenderingInfo info, Point2D source) { + for (ValueAxis yAxis : this.rangeAxes.values()) { + if (yAxis != null) { + yAxis.zoomRange(lowerPercent, upperPercent); + } + } + } + + /** + * Returns {@code true}, indicating that the domain axis/axes for this + * plot are zoomable. + * + * @return A boolean. + * + * @see #isRangeZoomable() + */ + @Override + public boolean isDomainZoomable() { + return true; + } + + /** + * Returns {@code true}, indicating that the range axis/axes for this + * plot are zoomable. + * + * @return A boolean. + * + * @see #isDomainZoomable() + */ + @Override + public boolean isRangeZoomable() { + return true; + } + + /** + * Returns the number of series in the primary dataset for this plot. If + * the dataset is {@code null}, the method returns 0. + * + * @return The series count. + */ + public int getSeriesCount() { + int result = 0; + XYDataset dataset = getDataset(); + if (dataset != null) { + result = dataset.getSeriesCount(); + } + return result; + } + + /** + * Returns the fixed legend items, if any. + * + * @return The legend items (possibly {@code null}). + * + * @see #setFixedLegendItems(LegendItemCollection) + */ + public LegendItemCollection getFixedLegendItems() { + return this.fixedLegendItems; + } + + /** + * Sets the fixed legend items for the plot. Leave this set to + * {@code null} if you prefer the legend items to be created + * automatically. + * + * @param items the legend items ({@code null} permitted). + * + * @see #getFixedLegendItems() + */ + public void setFixedLegendItems(LegendItemCollection items) { + this.fixedLegendItems = items; + fireChangeEvent(); + } + + /** + * Returns the legend items for the plot. Each legend item is generated by + * the plot's renderer, since the renderer is responsible for the visual + * representation of the data. + * + * @return The legend items. + */ + @Override + public LegendItemCollection getLegendItems() { + if (this.fixedLegendItems != null) { + return this.fixedLegendItems; + } + LegendItemCollection result = new LegendItemCollection(); + for (XYDataset dataset : this.datasets.values()) { + if (dataset == null) { + continue; + } + int datasetIndex = indexOf(dataset); + XYItemRenderer renderer = getRenderer(datasetIndex); + if (renderer == null) { + renderer = getRenderer(0); + } + if (renderer != null) { + int seriesCount = dataset.getSeriesCount(); + for (int i = 0; i < seriesCount; i++) { + if (renderer.isSeriesVisible(i) + && renderer.isSeriesVisibleInLegend(i)) { + LegendItem item = renderer.getLegendItem( + datasetIndex, i); + if (item != null) { + result.add(item); + } + } + } + } + } + return result; + } + + /** + * Tests this plot for equality with another object. + * + * @param obj the object ({@code null} permitted). + * + * @return {@code true} or {@code false}. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof XYPlot)) { + return false; + } + XYPlot that = (XYPlot) obj; + if (this.weight != that.weight) { + return false; + } + if (this.orientation != that.orientation) { + return false; + } + if (!this.domainAxes.equals(that.domainAxes)) { + return false; + } + if (!this.domainAxisLocations.equals(that.domainAxisLocations)) { + return false; + } + if (this.rangeCrosshairLockedOnData + != that.rangeCrosshairLockedOnData) { + return false; + } + if (this.domainGridlinesVisible != that.domainGridlinesVisible) { + return false; + } + if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) { + return false; + } + if (this.domainMinorGridlinesVisible + != that.domainMinorGridlinesVisible) { + return false; + } + if (this.rangeMinorGridlinesVisible + != that.rangeMinorGridlinesVisible) { + return false; + } + if (this.domainZeroBaselineVisible != that.domainZeroBaselineVisible) { + return false; + } + if (this.rangeZeroBaselineVisible != that.rangeZeroBaselineVisible) { + return false; + } + if (this.domainCrosshairVisible != that.domainCrosshairVisible) { + return false; + } + if (this.domainCrosshairValue != that.domainCrosshairValue) { + return false; + } + if (this.domainCrosshairLockedOnData + != that.domainCrosshairLockedOnData) { + return false; + } + if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) { + return false; + } + if (this.rangeCrosshairValue != that.rangeCrosshairValue) { + return false; + } + if (!Objects.equals(this.axisOffset, that.axisOffset)) { + return false; + } + if (!Objects.equals(this.renderers, that.renderers)) { + return false; + } + if (!Objects.equals(this.rangeAxes, that.rangeAxes)) { + return false; + } + if (!this.rangeAxisLocations.equals(that.rangeAxisLocations)) { + return false; + } + if (!Objects.equals(this.datasetToDomainAxesMap, + that.datasetToDomainAxesMap)) { + return false; + } + if (!Objects.equals(this.datasetToRangeAxesMap, + that.datasetToRangeAxesMap)) { + return false; + } + if (!Objects.equals(this.domainGridlineStroke, + that.domainGridlineStroke)) { + return false; + } + if (!PaintUtils.equal(this.domainGridlinePaint, + that.domainGridlinePaint)) { + return false; + } + if (!Objects.equals(this.rangeGridlineStroke, + that.rangeGridlineStroke)) { + return false; + } + if (!PaintUtils.equal(this.rangeGridlinePaint, + that.rangeGridlinePaint)) { + return false; + } + if (!Objects.equals(this.domainMinorGridlineStroke, + that.domainMinorGridlineStroke)) { + return false; + } + if (!PaintUtils.equal(this.domainMinorGridlinePaint, + that.domainMinorGridlinePaint)) { + return false; + } + if (!Objects.equals(this.rangeMinorGridlineStroke, + that.rangeMinorGridlineStroke)) { + return false; + } + if (!PaintUtils.equal(this.rangeMinorGridlinePaint, + that.rangeMinorGridlinePaint)) { + return false; + } + if (!PaintUtils.equal(this.domainZeroBaselinePaint, + that.domainZeroBaselinePaint)) { + return false; + } + if (!Objects.equals(this.domainZeroBaselineStroke, + that.domainZeroBaselineStroke)) { + return false; + } + if (!PaintUtils.equal(this.rangeZeroBaselinePaint, + that.rangeZeroBaselinePaint)) { + return false; + } + if (!Objects.equals(this.rangeZeroBaselineStroke, + that.rangeZeroBaselineStroke)) { + return false; + } + if (!Objects.equals(this.domainCrosshairStroke, + that.domainCrosshairStroke)) { + return false; + } + if (!PaintUtils.equal(this.domainCrosshairPaint, + that.domainCrosshairPaint)) { + return false; + } + if (!Objects.equals(this.rangeCrosshairStroke, + that.rangeCrosshairStroke)) { + return false; + } + if (!PaintUtils.equal(this.rangeCrosshairPaint, + that.rangeCrosshairPaint)) { + return false; + } + if (!Objects.equals(this.foregroundDomainMarkers, + that.foregroundDomainMarkers)) { + return false; + } + if (!Objects.equals(this.backgroundDomainMarkers, + that.backgroundDomainMarkers)) { + return false; + } + if (!Objects.equals(this.foregroundRangeMarkers, + that.foregroundRangeMarkers)) { + return false; + } + if (!Objects.equals(this.backgroundRangeMarkers, + that.backgroundRangeMarkers)) { + return false; + } + if (!Objects.equals(this.foregroundDomainMarkers, + that.foregroundDomainMarkers)) { + return false; + } + if (!Objects.equals(this.backgroundDomainMarkers, + that.backgroundDomainMarkers)) { + return false; + } + if (!Objects.equals(this.foregroundRangeMarkers, + that.foregroundRangeMarkers)) { + return false; + } + if (!Objects.equals(this.backgroundRangeMarkers, + that.backgroundRangeMarkers)) { + return false; + } + if (!Objects.equals(this.annotations, that.annotations)) { + return false; + } + if (!Objects.equals(this.fixedLegendItems, + that.fixedLegendItems)) { + return false; + } + if (!PaintUtils.equal(this.domainTickBandPaint, + that.domainTickBandPaint)) { + return false; + } + if (!PaintUtils.equal(this.rangeTickBandPaint, + that.rangeTickBandPaint)) { + return false; + } + if (!this.quadrantOrigin.equals(that.quadrantOrigin)) { + return false; + } + for (int i = 0; i < 4; i++) { + if (!PaintUtils.equal(this.quadrantPaint[i], + that.quadrantPaint[i])) { + return false; + } + } + if (!Objects.equals(this.shadowGenerator, + that.shadowGenerator)) { + return false; + } + return super.equals(obj); + } + + /** + * Returns a clone of the plot. + * + * @return A clone. + * + * @throws CloneNotSupportedException this can occur if some component of + * the plot cannot be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + XYPlot clone = (XYPlot) super.clone(); + clone.domainAxes = CloneUtils.cloneMapValues(this.domainAxes); + for (ValueAxis axis : clone.domainAxes.values()) { + if (axis != null) { + axis.setPlot(clone); + axis.addChangeListener(clone); + } + } + clone.rangeAxes = CloneUtils.cloneMapValues(this.rangeAxes); + for (ValueAxis axis : clone.rangeAxes.values()) { + if (axis != null) { + axis.setPlot(clone); + axis.addChangeListener(clone); + } + } + clone.domainAxisLocations = new HashMap<>(this.domainAxisLocations); + clone.rangeAxisLocations = new HashMap<>(this.rangeAxisLocations); + + // the datasets are not cloned, but listeners need to be added... + clone.datasets = new HashMap<>(this.datasets); + for (XYDataset dataset : clone.datasets.values()) { + if (dataset != null) { + dataset.addChangeListener(clone); + } + } + + clone.datasetToDomainAxesMap = new TreeMap(); + clone.datasetToDomainAxesMap.putAll(this.datasetToDomainAxesMap); + clone.datasetToRangeAxesMap = new TreeMap(); + clone.datasetToRangeAxesMap.putAll(this.datasetToRangeAxesMap); + + clone.renderers = CloneUtils.cloneMapValues(this.renderers); + for (XYItemRenderer renderer : clone.renderers.values()) { + if (renderer != null) { + renderer.setPlot(clone); + renderer.addChangeListener(clone); + } + } + clone.foregroundDomainMarkers = (Map) ObjectUtils.clone( + this.foregroundDomainMarkers); + clone.backgroundDomainMarkers = (Map) ObjectUtils.clone( + this.backgroundDomainMarkers); + clone.foregroundRangeMarkers = (Map) ObjectUtils.clone( + this.foregroundRangeMarkers); + clone.backgroundRangeMarkers = (Map) ObjectUtils.clone( + this.backgroundRangeMarkers); + clone.annotations = (List) ObjectUtils.deepClone(this.annotations); + if (this.fixedDomainAxisSpace != null) { + clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtils.clone( + this.fixedDomainAxisSpace); + } + if (this.fixedRangeAxisSpace != null) { + clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtils.clone( + this.fixedRangeAxisSpace); + } + if (this.fixedLegendItems != null) { + clone.fixedLegendItems + = (LegendItemCollection) this.fixedLegendItems.clone(); + } + clone.quadrantOrigin = (Point2D) ObjectUtils.clone( + this.quadrantOrigin); + clone.quadrantPaint = this.quadrantPaint.clone(); + return clone; + + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writeStroke(this.domainGridlineStroke, stream); + SerialUtils.writePaint(this.domainGridlinePaint, stream); + SerialUtils.writeStroke(this.rangeGridlineStroke, stream); + SerialUtils.writePaint(this.rangeGridlinePaint, stream); + SerialUtils.writeStroke(this.domainMinorGridlineStroke, stream); + SerialUtils.writePaint(this.domainMinorGridlinePaint, stream); + SerialUtils.writeStroke(this.rangeMinorGridlineStroke, stream); + SerialUtils.writePaint(this.rangeMinorGridlinePaint, stream); + SerialUtils.writeStroke(this.rangeZeroBaselineStroke, stream); + SerialUtils.writePaint(this.rangeZeroBaselinePaint, stream); + SerialUtils.writeStroke(this.domainCrosshairStroke, stream); + SerialUtils.writePaint(this.domainCrosshairPaint, stream); + SerialUtils.writeStroke(this.rangeCrosshairStroke, stream); + SerialUtils.writePaint(this.rangeCrosshairPaint, stream); + SerialUtils.writePaint(this.domainTickBandPaint, stream); + SerialUtils.writePaint(this.rangeTickBandPaint, stream); + SerialUtils.writePoint2D(this.quadrantOrigin, stream); + for (int i = 0; i < 4; i++) { + SerialUtils.writePaint(this.quadrantPaint[i], stream); + } + SerialUtils.writeStroke(this.domainZeroBaselineStroke, stream); + SerialUtils.writePaint(this.domainZeroBaselinePaint, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + + stream.defaultReadObject(); + this.domainGridlineStroke = SerialUtils.readStroke(stream); + this.domainGridlinePaint = SerialUtils.readPaint(stream); + this.rangeGridlineStroke = SerialUtils.readStroke(stream); + this.rangeGridlinePaint = SerialUtils.readPaint(stream); + this.domainMinorGridlineStroke = SerialUtils.readStroke(stream); + this.domainMinorGridlinePaint = SerialUtils.readPaint(stream); + this.rangeMinorGridlineStroke = SerialUtils.readStroke(stream); + this.rangeMinorGridlinePaint = SerialUtils.readPaint(stream); + this.rangeZeroBaselineStroke = SerialUtils.readStroke(stream); + this.rangeZeroBaselinePaint = SerialUtils.readPaint(stream); + this.domainCrosshairStroke = SerialUtils.readStroke(stream); + this.domainCrosshairPaint = SerialUtils.readPaint(stream); + this.rangeCrosshairStroke = SerialUtils.readStroke(stream); + this.rangeCrosshairPaint = SerialUtils.readPaint(stream); + this.domainTickBandPaint = SerialUtils.readPaint(stream); + this.rangeTickBandPaint = SerialUtils.readPaint(stream); + this.quadrantOrigin = SerialUtils.readPoint2D(stream); + this.quadrantPaint = new Paint[4]; + for (int i = 0; i < 4; i++) { + this.quadrantPaint[i] = SerialUtils.readPaint(stream); + } + + this.domainZeroBaselineStroke = SerialUtils.readStroke(stream); + this.domainZeroBaselinePaint = SerialUtils.readPaint(stream); + + // register the plot as a listener with its axes, datasets, and + // renderers... + for (ValueAxis axis : this.domainAxes.values()) { + if (axis != null) { + axis.setPlot(this); + axis.addChangeListener(this); + } + } + for (ValueAxis axis : this.rangeAxes.values()) { + if (axis != null) { + axis.setPlot(this); + axis.addChangeListener(this); + } + } + for (XYDataset dataset : this.datasets.values()) { + if (dataset != null) { + dataset.addChangeListener(this); + } + } + for (XYItemRenderer renderer : this.renderers.values()) { + if (renderer != null) { + renderer.addChangeListener(this); + } + } + + } + +} diff --git a/src/main/java/org/jfree/chart/plot/Zoomable.java b/src/main/java/org/jfree/chart/plot/Zoomable.java index 6e4f053ea..3f8380fcd 100644 --- a/src/main/java/org/jfree/chart/plot/Zoomable.java +++ b/src/main/java/org/jfree/chart/plot/Zoomable.java @@ -39,11 +39,9 @@ import java.awt.geom.Point2D; -import org.jfree.chart.ChartPanel; - /** * A plot that is zoomable must implement this interface to provide a - * mechanism for the {@link ChartPanel} to control the zooming. + * mechanism for the {@link org.jfree.chart.ChartPanel} to control the zooming. */ public interface Zoomable { diff --git a/src/main/java/org/jfree/chart/plot/dial/DialFrame.java b/src/main/java/org/jfree/chart/plot/dial/DialFrame.java index 40b29d048..31457795d 100644 --- a/src/main/java/org/jfree/chart/plot/dial/DialFrame.java +++ b/src/main/java/org/jfree/chart/plot/dial/DialFrame.java @@ -1,64 +1,63 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------- - * DialFrame.java - * -------------- - * (C) Copyright 2006-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.chart.plot.dial; - -import java.awt.Shape; -import java.awt.geom.Rectangle2D; -import java.io.Serializable; - -/** - * A dial frame is the face plate for a dial plot - it is always drawn last. - * JFreeChart includes a couple of implementations of this interface - * ({@link StandardDialFrame} and {@link ArcDialFrame}). - *

- * Classes that implement this interface should be {@link Serializable}, - * otherwise chart serialization may fail. - */ -public interface DialFrame extends DialLayer { - - /** - * Returns the shape of the viewing window for the dial, or - * {@code null} if the dial is completely open. Other layers in the - * plot will rely on their drawing to be clipped within this window. - * - * @param frame the reference frame for the dial. - * - * @return The window. - */ - Shape getWindow(Rectangle2D frame); - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * -------------- + * DialFrame.java + * -------------- + * (C) Copyright 2006-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.chart.plot.dial; + +import java.awt.Shape; +import java.awt.geom.Rectangle2D; + +/** + * A dial frame is the face plate for a dial plot - it is always drawn last. + * JFreeChart includes a couple of implementations of this interface + * ({@link StandardDialFrame} and {@link ArcDialFrame}). + *

+ * Classes that implement this interface should be {@link java.io.Serializable}, + * otherwise chart serialization may fail. + */ +public interface DialFrame extends DialLayer { + + /** + * Returns the shape of the viewing window for the dial, or + * {@code null} if the dial is completely open. Other layers in the + * plot will rely on their drawing to be clipped within this window. + * + * @param frame the reference frame for the dial. + * + * @return The window. + */ + Shape getWindow(Rectangle2D frame); + +} diff --git a/src/main/java/org/jfree/chart/plot/dial/DialLayer.java b/src/main/java/org/jfree/chart/plot/dial/DialLayer.java index 1fbb1ac14..e80b6ca54 100644 --- a/src/main/java/org/jfree/chart/plot/dial/DialLayer.java +++ b/src/main/java/org/jfree/chart/plot/dial/DialLayer.java @@ -1,112 +1,111 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------- - * DialLayer.java - * -------------- - * (C) Copyright 2006-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.chart.plot.dial; - -import java.awt.Graphics2D; -import java.awt.geom.Rectangle2D; -import java.io.Serializable; -import java.util.EventListener; - -/** - * A dial layer draws itself within a reference frame. The view frame is a - * subset of the reference frame, and defines the area that is actually - * visible. - *

- * Classes that implement this interface should be {@link Serializable}, - * otherwise chart serialization may fail. - */ -public interface DialLayer { - - /** - * Returns a flag that indicates whether or not the layer is visible. - * - * @return A boolean. - */ - boolean isVisible(); - - /** - * Registers a listener with this layer, so that it receives notification - * of changes to this layer. - * - * @param listener the listener. - */ - void addChangeListener(DialLayerChangeListener listener); - - /** - * Deregisters a listener, so that it no longer receives notification of - * changes to this layer. - * - * @param listener the listener. - */ - void removeChangeListener(DialLayerChangeListener listener); - - /** - * Returns {@code true} if the specified listener is currently - * registered with the this layer. - * - * @param listener the listener. - * - * @return A boolean. - */ - boolean hasListener(EventListener listener); - - /** - * Returns {@code true} if the drawing should be clipped to the - * dial window (which is defined by the {@link DialFrame}), and - * {@code false} otherwise. - * - * @return A boolean. - */ - boolean isClippedToWindow(); - - /** - * Draws the content of this layer. - * - * @param g2 the graphics target ({@code null} not permitted). - * @param plot the plot (typically this should not be {@code null}, - * but for a layer that doesn't need to reference the plot, it may - * be permitted). - * @param frame the reference frame for the dial's geometry - * ({@code null} not permitted). This is typically larger than - * the visible area of the dial (see the next parameter). - * @param view the visible area for the dial ({@code null} not - * permitted). - */ - void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, - Rectangle2D view); - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * -------------- + * DialLayer.java + * -------------- + * (C) Copyright 2006-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.chart.plot.dial; + +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; +import java.util.EventListener; + +/** + * A dial layer draws itself within a reference frame. The view frame is a + * subset of the reference frame, and defines the area that is actually + * visible. + *

+ * Classes that implement this interface should be {@link java.io.Serializable}, + * otherwise chart serialization may fail. + */ +public interface DialLayer { + + /** + * Returns a flag that indicates whether or not the layer is visible. + * + * @return A boolean. + */ + boolean isVisible(); + + /** + * Registers a listener with this layer, so that it receives notification + * of changes to this layer. + * + * @param listener the listener. + */ + void addChangeListener(DialLayerChangeListener listener); + + /** + * Deregisters a listener, so that it no longer receives notification of + * changes to this layer. + * + * @param listener the listener. + */ + void removeChangeListener(DialLayerChangeListener listener); + + /** + * Returns {@code true} if the specified listener is currently + * registered with the this layer. + * + * @param listener the listener. + * + * @return A boolean. + */ + boolean hasListener(EventListener listener); + + /** + * Returns {@code true} if the drawing should be clipped to the + * dial window (which is defined by the {@link DialFrame}), and + * {@code false} otherwise. + * + * @return A boolean. + */ + boolean isClippedToWindow(); + + /** + * Draws the content of this layer. + * + * @param g2 the graphics target ({@code null} not permitted). + * @param plot the plot (typically this should not be {@code null}, + * but for a layer that doesn't need to reference the plot, it may + * be permitted). + * @param frame the reference frame for the dial's geometry + * ({@code null} not permitted). This is typically larger than + * the visible area of the dial (see the next parameter). + * @param view the visible area for the dial ({@code null} not + * permitted). + */ + void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, + Rectangle2D view); + +} diff --git a/src/main/java/org/jfree/chart/plot/dial/DialPlot.java b/src/main/java/org/jfree/chart/plot/dial/DialPlot.java index 855d32d73..e3ffb2f81 100644 --- a/src/main/java/org/jfree/chart/plot/dial/DialPlot.java +++ b/src/main/java/org/jfree/chart/plot/dial/DialPlot.java @@ -1,823 +1,809 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------- - * DialPlot.java - * ------------- - * (C) Copyright 2006-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.chart.plot.dial; - -import java.awt.Graphics2D; -import java.awt.Shape; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; - -import org.jfree.chart.JFreeChart; -import org.jfree.chart.event.PlotChangeEvent; -import org.jfree.chart.plot.Plot; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.PlotState; -import org.jfree.chart.util.ObjectList; -import org.jfree.chart.util.ObjectUtils; -import org.jfree.chart.util.Args; -import org.jfree.data.general.DatasetChangeEvent; -import org.jfree.data.general.ValueDataset; - -/** - * A dial plot composed of user-definable layers. - * The example shown here is generated by the {@code DialDemo2.java} - * program included in the JFreeChart Demo Collection: - *

- * DialPlotSample.png - */ -public class DialPlot extends Plot implements DialLayerChangeListener { - - /** - * The background layer (optional). - */ - private DialLayer background; - - /** - * The needle cap (optional). - */ - private DialLayer cap; - - /** - * The dial frame. - */ - private DialFrame dialFrame; - - /** - * The dataset(s) for the dial plot. - */ - private ObjectList datasets; - - /** - * The scale(s) for the dial plot. - */ - private ObjectList scales; - - /** Storage for keys that map datasets to scales. */ - private ObjectList datasetToScaleMap; - - /** - * The drawing layers for the dial plot. - */ - private List layers; - - /** - * The pointer(s) for the dial. - */ - private List pointers; - - /** - * The x-coordinate for the view window. - */ - private double viewX; - - /** - * The y-coordinate for the view window. - */ - private double viewY; - - /** - * The width of the view window, expressed as a percentage. - */ - private double viewW; - - /** - * The height of the view window, expressed as a percentage. - */ - private double viewH; - - /** - * Creates a new instance of {@code DialPlot}. - */ - public DialPlot() { - this(null); - } - - /** - * Creates a new instance of {@code DialPlot}. - * - * @param dataset the dataset ({@code null} permitted). - */ - public DialPlot(ValueDataset dataset) { - this.background = null; - this.cap = null; - this.dialFrame = new ArcDialFrame(); - this.datasets = new ObjectList(); - if (dataset != null) { - setDataset(dataset); - } - this.scales = new ObjectList(); - this.datasetToScaleMap = new ObjectList(); - this.layers = new java.util.ArrayList(); - this.pointers = new java.util.ArrayList(); - this.viewX = 0.0; - this.viewY = 0.0; - this.viewW = 1.0; - this.viewH = 1.0; - } - - /** - * Returns the background. - * - * @return The background (possibly {@code null}). - * - * @see #setBackground(DialLayer) - */ - public DialLayer getBackground() { - return this.background; - } - - /** - * Sets the background layer and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param background the background layer ({@code null} permitted). - * - * @see #getBackground() - */ - public void setBackground(DialLayer background) { - if (this.background != null) { - this.background.removeChangeListener(this); - } - this.background = background; - if (background != null) { - background.addChangeListener(this); - } - fireChangeEvent(); - } - - /** - * Returns the cap. - * - * @return The cap (possibly {@code null}). - * - * @see #setCap(DialLayer) - */ - public DialLayer getCap() { - return this.cap; - } - - /** - * Sets the cap and sends a {@link PlotChangeEvent} to all registered - * listeners. - * - * @param cap the cap ({@code null} permitted). - * - * @see #getCap() - */ - public void setCap(DialLayer cap) { - if (this.cap != null) { - this.cap.removeChangeListener(this); - } - this.cap = cap; - if (cap != null) { - cap.addChangeListener(this); - } - fireChangeEvent(); - } - - /** - * Returns the dial's frame. - * - * @return The dial's frame (never {@code null}). - * - * @see #setDialFrame(DialFrame) - */ - public DialFrame getDialFrame() { - return this.dialFrame; - } - - /** - * Sets the dial's frame and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param frame the frame ({@code null} not permitted). - * - * @see #getDialFrame() - */ - public void setDialFrame(DialFrame frame) { - Args.nullNotPermitted(frame, "frame"); - this.dialFrame.removeChangeListener(this); - this.dialFrame = frame; - frame.addChangeListener(this); - fireChangeEvent(); - } - - /** - * Returns the x-coordinate of the viewing rectangle. This is specified - * in the range 0.0 to 1.0, relative to the dial's framing rectangle. - * - * @return The x-coordinate of the viewing rectangle. - * - * @see #setView(double, double, double, double) - */ - public double getViewX() { - return this.viewX; - } - - /** - * Returns the y-coordinate of the viewing rectangle. This is specified - * in the range 0.0 to 1.0, relative to the dial's framing rectangle. - * - * @return The y-coordinate of the viewing rectangle. - * - * @see #setView(double, double, double, double) - */ - public double getViewY() { - return this.viewY; - } - - /** - * Returns the width of the viewing rectangle. This is specified - * in the range 0.0 to 1.0, relative to the dial's framing rectangle. - * - * @return The width of the viewing rectangle. - * - * @see #setView(double, double, double, double) - */ - public double getViewWidth() { - return this.viewW; - } - - /** - * Returns the height of the viewing rectangle. This is specified - * in the range 0.0 to 1.0, relative to the dial's framing rectangle. - * - * @return The height of the viewing rectangle. - * - * @see #setView(double, double, double, double) - */ - public double getViewHeight() { - return this.viewH; - } - - /** - * Sets the viewing rectangle, relative to the dial's framing rectangle, - * and sends a {@link PlotChangeEvent} to all registered listeners. - * - * @param x the x-coordinate (in the range 0.0 to 1.0). - * @param y the y-coordinate (in the range 0.0 to 1.0). - * @param w the width (in the range 0.0 to 1.0). - * @param h the height (in the range 0.0 to 1.0). - * - * @see #getViewX() - * @see #getViewY() - * @see #getViewWidth() - * @see #getViewHeight() - */ - public void setView(double x, double y, double w, double h) { - this.viewX = x; - this.viewY = y; - this.viewW = w; - this.viewH = h; - fireChangeEvent(); - } - - /** - * Adds a layer to the plot and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param layer the layer ({@code null} not permitted). - */ - public void addLayer(DialLayer layer) { - Args.nullNotPermitted(layer, "layer"); - this.layers.add(layer); - layer.addChangeListener(this); - fireChangeEvent(); - } - - /** - * Returns the index for the specified layer. - * - * @param layer the layer ({@code null} not permitted). - * - * @return The layer index. - */ - public int getLayerIndex(DialLayer layer) { - Args.nullNotPermitted(layer, "layer"); - return this.layers.indexOf(layer); - } - - /** - * Removes the layer at the specified index and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the index. - */ - public void removeLayer(int index) { - DialLayer layer = (DialLayer) this.layers.get(index); - if (layer != null) { - layer.removeChangeListener(this); - } - this.layers.remove(index); - fireChangeEvent(); - } - - /** - * Removes the specified layer and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param layer the layer ({@code null} not permitted). - */ - public void removeLayer(DialLayer layer) { - // defer argument checking - removeLayer(getLayerIndex(layer)); - } - - /** - * Adds a pointer to the plot and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param pointer the pointer ({@code null} not permitted). - */ - public void addPointer(DialPointer pointer) { - Args.nullNotPermitted(pointer, "pointer"); - this.pointers.add(pointer); - pointer.addChangeListener(this); - fireChangeEvent(); - } - - /** - * Returns the index for the specified pointer. - * - * @param pointer the pointer ({@code null} not permitted). - * - * @return The pointer index. - */ - public int getPointerIndex(DialPointer pointer) { - Args.nullNotPermitted(pointer, "pointer"); - return this.pointers.indexOf(pointer); - } - - /** - * Removes the pointer at the specified index and sends a - * {@link PlotChangeEvent} to all registered listeners. - * - * @param index the index. - */ - public void removePointer(int index) { - DialPointer pointer = (DialPointer) this.pointers.get(index); - if (pointer != null) { - pointer.removeChangeListener(this); - } - this.pointers.remove(index); - fireChangeEvent(); - } - - /** - * Removes the specified pointer and sends a {@link PlotChangeEvent} to all - * registered listeners. - * - * @param pointer the pointer ({@code null} not permitted). - */ - public void removePointer(DialPointer pointer) { - // defer argument checking - removeLayer(getPointerIndex(pointer)); - } - - /** - * Returns the dial pointer that is associated with the specified - * dataset, or {@code null}. - * - * @param datasetIndex the dataset index. - * - * @return The pointer. - */ - public DialPointer getPointerForDataset(int datasetIndex) { - DialPointer result = null; - Iterator iterator = this.pointers.iterator(); - while (iterator.hasNext()) { - DialPointer p = (DialPointer) iterator.next(); - if (p.getDatasetIndex() == datasetIndex) { - return p; - } - } - return result; - } - - /** - * Returns the primary dataset for the plot. - * - * @return The primary dataset (possibly {@code null}). - */ - public ValueDataset getDataset() { - return getDataset(0); - } - - /** - * Returns the dataset at the given index. - * - * @param index the dataset index. - * - * @return The dataset (possibly {@code null}). - */ - public ValueDataset getDataset(int index) { - ValueDataset result = null; - if (this.datasets.size() > index) { - result = (ValueDataset) this.datasets.get(index); - } - return result; - } - - /** - * Sets the dataset for the plot, replacing the existing dataset, if there - * is one, and sends a {@link PlotChangeEvent} to all registered - * listeners. - * - * @param dataset the dataset ({@code null} permitted). - */ - public void setDataset(ValueDataset dataset) { - setDataset(0, dataset); - } - - /** - * Sets a dataset for the plot. - * - * @param index the dataset index. - * @param dataset the dataset ({@code null} permitted). - */ - public void setDataset(int index, ValueDataset dataset) { - - ValueDataset existing = (ValueDataset) this.datasets.get(index); - if (existing != null) { - existing.removeChangeListener(this); - } - this.datasets.set(index, dataset); - if (dataset != null) { - dataset.addChangeListener(this); - } - - // send a dataset change event to self... - DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); - datasetChanged(event); - - } - - /** - * Returns the number of datasets. - * - * @return The number of datasets. - */ - public int getDatasetCount() { - return this.datasets.size(); - } - - /** - * Draws the plot. This method is usually called by the {@link JFreeChart} - * instance that manages the plot. - * - * @param g2 the graphics target. - * @param area the area in which the plot should be drawn. - * @param anchor the anchor point (typically the last point that the - * mouse clicked on, {@code null} is permitted). - * @param parentState the state for the parent plot (if any). - * @param info used to collect plot rendering info ({@code null} - * permitted). - */ - @Override - public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, - PlotState parentState, PlotRenderingInfo info) { - - Shape origClip = g2.getClip(); - g2.setClip(area); - - // first, expand the viewing area into a drawing frame - Rectangle2D frame = viewToFrame(area); - - // draw the background if there is one... - if (this.background != null && this.background.isVisible()) { - if (this.background.isClippedToWindow()) { - Shape savedClip = g2.getClip(); - g2.clip(this.dialFrame.getWindow(frame)); - this.background.draw(g2, this, frame, area); - g2.setClip(savedClip); - } - else { - this.background.draw(g2, this, frame, area); - } - } - - Iterator iterator = this.layers.iterator(); - while (iterator.hasNext()) { - DialLayer current = (DialLayer) iterator.next(); - if (current.isVisible()) { - if (current.isClippedToWindow()) { - Shape savedClip = g2.getClip(); - g2.clip(this.dialFrame.getWindow(frame)); - current.draw(g2, this, frame, area); - g2.setClip(savedClip); - } - else { - current.draw(g2, this, frame, area); - } - } - } - - // draw the pointers - iterator = this.pointers.iterator(); - while (iterator.hasNext()) { - DialPointer current = (DialPointer) iterator.next(); - if (current.isVisible()) { - if (current.isClippedToWindow()) { - Shape savedClip = g2.getClip(); - g2.clip(this.dialFrame.getWindow(frame)); - current.draw(g2, this, frame, area); - g2.setClip(savedClip); - } - else { - current.draw(g2, this, frame, area); - } - } - } - - // draw the cap if there is one... - if (this.cap != null && this.cap.isVisible()) { - if (this.cap.isClippedToWindow()) { - Shape savedClip = g2.getClip(); - g2.clip(this.dialFrame.getWindow(frame)); - this.cap.draw(g2, this, frame, area); - g2.setClip(savedClip); - } - else { - this.cap.draw(g2, this, frame, area); - } - } - - if (this.dialFrame.isVisible()) { - this.dialFrame.draw(g2, this, frame, area); - } - - g2.setClip(origClip); - - } - - /** - * Returns the frame surrounding the specified view rectangle. - * - * @param view the view rectangle ({@code null} not permitted). - * - * @return The frame rectangle. - */ - private Rectangle2D viewToFrame(Rectangle2D view) { - double width = view.getWidth() / this.viewW; - double height = view.getHeight() / this.viewH; - double x = view.getX() - (width * this.viewX); - double y = view.getY() - (height * this.viewY); - return new Rectangle2D.Double(x, y, width, height); - } - - /** - * Returns the value from the specified dataset. - * - * @param datasetIndex the dataset index. - * - * @return The data value. - */ - public double getValue(int datasetIndex) { - double result = Double.NaN; - ValueDataset dataset = getDataset(datasetIndex); - if (dataset != null) { - Number n = dataset.getValue(); - if (n != null) { - result = n.doubleValue(); - } - } - return result; - } - - /** - * Adds a dial scale to the plot and sends a {@link PlotChangeEvent} to - * all registered listeners. - * - * @param index the scale index. - * @param scale the scale ({@code null} not permitted). - */ - public void addScale(int index, DialScale scale) { - Args.nullNotPermitted(scale, "scale"); - DialScale existing = (DialScale) this.scales.get(index); - if (existing != null) { - removeLayer(existing); - } - this.layers.add(scale); - this.scales.set(index, scale); - scale.addChangeListener(this); - fireChangeEvent(); - } - - /** - * Returns the scale at the given index. - * - * @param index the scale index. - * - * @return The scale (possibly {@code null}). - */ - public DialScale getScale(int index) { - DialScale result = null; - if (this.scales.size() > index) { - result = (DialScale) this.scales.get(index); - } - return result; - } - - /** - * Maps a dataset to a particular scale. - * - * @param index the dataset index (zero-based). - * @param scaleIndex the scale index (zero-based). - */ - public void mapDatasetToScale(int index, int scaleIndex) { - this.datasetToScaleMap.set(index, scaleIndex); - fireChangeEvent(); - } - - /** - * Returns the dial scale for a specific dataset. - * - * @param datasetIndex the dataset index. - * - * @return The dial scale. - */ - public DialScale getScaleForDataset(int datasetIndex) { - DialScale result = (DialScale) this.scales.get(0); - Integer scaleIndex = (Integer) this.datasetToScaleMap.get(datasetIndex); - if (scaleIndex != null) { - result = getScale(scaleIndex); - } - return result; - } - - /** - * A utility method that computes a rectangle using relative radius values. - * - * @param rect the reference rectangle ({@code null} not permitted). - * @param radiusW the width radius (must be > 0.0) - * @param radiusH the height radius. - * - * @return A new rectangle. - */ - public static Rectangle2D rectangleByRadius(Rectangle2D rect, - double radiusW, double radiusH) { - Args.nullNotPermitted(rect, "rect"); - double x = rect.getCenterX(); - double y = rect.getCenterY(); - double w = rect.getWidth() * radiusW; - double h = rect.getHeight() * radiusH; - return new Rectangle2D.Double(x - w / 2.0, y - h / 2.0, w, h); - } - - /** - * Receives notification when a layer has changed, and responds by - * forwarding a {@link PlotChangeEvent} to all registered listeners. - * - * @param event the event. - */ - @Override - public void dialLayerChanged(DialLayerChangeEvent event) { - fireChangeEvent(); - } - - /** - * Tests this {@code DialPlot} instance for equality with an - * arbitrary object. The plot's dataset(s) is (are) not included in - * the test. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof DialPlot)) { - return false; - } - DialPlot that = (DialPlot) obj; - if (!Objects.equals(this.background, that.background)) { - return false; - } - if (!Objects.equals(this.cap, that.cap)) { - return false; - } - if (!this.dialFrame.equals(that.dialFrame)) { - return false; - } - if (this.viewX != that.viewX) { - return false; - } - if (this.viewY != that.viewY) { - return false; - } - if (this.viewW != that.viewW) { - return false; - } - if (this.viewH != that.viewH) { - return false; - } - if (!this.layers.equals(that.layers)) { - return false; - } - if (!this.pointers.equals(that.pointers)) { - return false; - } - return super.equals(obj); - } - - /** - * Returns a hash code for this instance. - * - * @return The hash code. - */ - @Override - public int hashCode() { - int result = 193; - result = 37 * result + ObjectUtils.hashCode(this.background); - result = 37 * result + ObjectUtils.hashCode(this.cap); - result = 37 * result + this.dialFrame.hashCode(); - long temp = Double.doubleToLongBits(this.viewX); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(this.viewY); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(this.viewW); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(this.viewH); - result = 37 * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Returns the plot type. - * - * @return {@code "DialPlot"} - */ - @Override - public String getPlotType() { - return "DialPlot"; - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - } - - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------- + * DialPlot.java + * ------------- + * (C) Copyright 2006-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.chart.plot.dial; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.PlotState; +import org.jfree.chart.util.ObjectList; +import org.jfree.chart.util.ObjectUtils; +import org.jfree.chart.util.Args; +import org.jfree.data.general.DatasetChangeEvent; +import org.jfree.data.general.ValueDataset; + +/** + * A dial plot composed of user-definable layers. + * The example shown here is generated by the {@code DialDemo2.java} + * program included in the JFreeChart Demo Collection: + *

+ * DialPlotSample.png + */ +public class DialPlot extends Plot implements DialLayerChangeListener { + + /** + * The background layer (optional). + */ + private DialLayer background; + + /** + * The needle cap (optional). + */ + private DialLayer cap; + + /** + * The dial frame. + */ + private DialFrame dialFrame; + + /** + * The dataset(s) for the dial plot. + */ + private ObjectList datasets; + + /** + * The scale(s) for the dial plot. + */ + private ObjectList scales; + + /** Storage for keys that map datasets to scales. */ + private ObjectList datasetToScaleMap; + + /** + * The drawing layers for the dial plot. + */ + private List layers; + + /** + * The pointer(s) for the dial. + */ + private List pointers; + + /** + * The x-coordinate for the view window. + */ + private double viewX; + + /** + * The y-coordinate for the view window. + */ + private double viewY; + + /** + * The width of the view window, expressed as a percentage. + */ + private double viewW; + + /** + * The height of the view window, expressed as a percentage. + */ + private double viewH; + + /** + * Creates a new instance of {@code DialPlot}. + */ + public DialPlot() { + this(null); + } + + /** + * Creates a new instance of {@code DialPlot}. + * + * @param dataset the dataset ({@code null} permitted). + */ + public DialPlot(ValueDataset dataset) { + this.background = null; + this.cap = null; + this.dialFrame = new ArcDialFrame(); + this.datasets = new ObjectList(); + if (dataset != null) { + setDataset(dataset); + } + this.scales = new ObjectList(); + this.datasetToScaleMap = new ObjectList(); + this.layers = new java.util.ArrayList(); + this.pointers = new java.util.ArrayList(); + this.viewX = 0.0; + this.viewY = 0.0; + this.viewW = 1.0; + this.viewH = 1.0; + } + + /** + * Returns the background. + * + * @return The background (possibly {@code null}). + * + * @see #setBackground(DialLayer) + */ + public DialLayer getBackground() { + return this.background; + } + + /** + * Sets the background layer and calls {@link #fireChangeEvent()}. + * + * @param background the background layer ({@code null} permitted). + * + * @see #getBackground() + */ + public void setBackground(DialLayer background) { + if (this.background != null) { + this.background.removeChangeListener(this); + } + this.background = background; + if (background != null) { + background.addChangeListener(this); + } + fireChangeEvent(); + } + + /** + * Returns the cap. + * + * @return The cap (possibly {@code null}). + * + * @see #setCap(DialLayer) + */ + public DialLayer getCap() { + return this.cap; + } + + /** + * Sets the cap and calls {@link #fireChangeEvent()}. + * + * @param cap the cap ({@code null} permitted). + * + * @see #getCap() + */ + public void setCap(DialLayer cap) { + if (this.cap != null) { + this.cap.removeChangeListener(this); + } + this.cap = cap; + if (cap != null) { + cap.addChangeListener(this); + } + fireChangeEvent(); + } + + /** + * Returns the dial's frame. + * + * @return The dial's frame (never {@code null}). + * + * @see #setDialFrame(DialFrame) + */ + public DialFrame getDialFrame() { + return this.dialFrame; + } + + /** + * Sets the dial's frame and calls {@link #fireChangeEvent()}. + * + * @param frame the frame ({@code null} not permitted). + * + * @see #getDialFrame() + */ + public void setDialFrame(DialFrame frame) { + Args.nullNotPermitted(frame, "frame"); + this.dialFrame.removeChangeListener(this); + this.dialFrame = frame; + frame.addChangeListener(this); + fireChangeEvent(); + } + + /** + * Returns the x-coordinate of the viewing rectangle. This is specified + * in the range 0.0 to 1.0, relative to the dial's framing rectangle. + * + * @return The x-coordinate of the viewing rectangle. + * + * @see #setView(double, double, double, double) + */ + public double getViewX() { + return this.viewX; + } + + /** + * Returns the y-coordinate of the viewing rectangle. This is specified + * in the range 0.0 to 1.0, relative to the dial's framing rectangle. + * + * @return The y-coordinate of the viewing rectangle. + * + * @see #setView(double, double, double, double) + */ + public double getViewY() { + return this.viewY; + } + + /** + * Returns the width of the viewing rectangle. This is specified + * in the range 0.0 to 1.0, relative to the dial's framing rectangle. + * + * @return The width of the viewing rectangle. + * + * @see #setView(double, double, double, double) + */ + public double getViewWidth() { + return this.viewW; + } + + /** + * Returns the height of the viewing rectangle. This is specified + * in the range 0.0 to 1.0, relative to the dial's framing rectangle. + * + * @return The height of the viewing rectangle. + * + * @see #setView(double, double, double, double) + */ + public double getViewHeight() { + return this.viewH; + } + + /** + * Sets the viewing rectangle, relative to the dial's framing rectangle, + * and calls {@link #fireChangeEvent()}. + * + * @param x the x-coordinate (in the range 0.0 to 1.0). + * @param y the y-coordinate (in the range 0.0 to 1.0). + * @param w the width (in the range 0.0 to 1.0). + * @param h the height (in the range 0.0 to 1.0). + * + * @see #getViewX() + * @see #getViewY() + * @see #getViewWidth() + * @see #getViewHeight() + */ + public void setView(double x, double y, double w, double h) { + this.viewX = x; + this.viewY = y; + this.viewW = w; + this.viewH = h; + fireChangeEvent(); + } + + /** + * Adds a layer to the plot and calls {@link #fireChangeEvent()}. + * + * @param layer the layer ({@code null} not permitted). + */ + public void addLayer(DialLayer layer) { + Args.nullNotPermitted(layer, "layer"); + this.layers.add(layer); + layer.addChangeListener(this); + fireChangeEvent(); + } + + /** + * Returns the index for the specified layer. + * + * @param layer the layer ({@code null} not permitted). + * + * @return The layer index. + */ + public int getLayerIndex(DialLayer layer) { + Args.nullNotPermitted(layer, "layer"); + return this.layers.indexOf(layer); + } + + /** + * Removes the layer at the specified index and calls {@link #fireChangeEvent()}. + * + * @param index the index. + */ + public void removeLayer(int index) { + DialLayer layer = (DialLayer) this.layers.get(index); + if (layer != null) { + layer.removeChangeListener(this); + } + this.layers.remove(index); + fireChangeEvent(); + } + + /** + * Removes the specified layer and calls {@link #fireChangeEvent()}. + * + * @param layer the layer ({@code null} not permitted). + */ + public void removeLayer(DialLayer layer) { + // defer argument checking + removeLayer(getLayerIndex(layer)); + } + + /** + * Adds a pointer to the plot and calls {@link #fireChangeEvent()}. + * + * @param pointer the pointer ({@code null} not permitted). + */ + public void addPointer(DialPointer pointer) { + Args.nullNotPermitted(pointer, "pointer"); + this.pointers.add(pointer); + pointer.addChangeListener(this); + fireChangeEvent(); + } + + /** + * Returns the index for the specified pointer. + * + * @param pointer the pointer ({@code null} not permitted). + * + * @return The pointer index. + */ + public int getPointerIndex(DialPointer pointer) { + Args.nullNotPermitted(pointer, "pointer"); + return this.pointers.indexOf(pointer); + } + + /** + * Removes the pointer at the specified index and calls {@link #fireChangeEvent()}. + * + * @param index the index. + */ + public void removePointer(int index) { + DialPointer pointer = (DialPointer) this.pointers.get(index); + if (pointer != null) { + pointer.removeChangeListener(this); + } + this.pointers.remove(index); + fireChangeEvent(); + } + + /** + * Removes the specified pointer and calls {@link #fireChangeEvent()}. + * + * @param pointer the pointer ({@code null} not permitted). + */ + public void removePointer(DialPointer pointer) { + // defer argument checking + removeLayer(getPointerIndex(pointer)); + } + + /** + * Returns the dial pointer that is associated with the specified + * dataset, or {@code null}. + * + * @param datasetIndex the dataset index. + * + * @return The pointer. + */ + public DialPointer getPointerForDataset(int datasetIndex) { + DialPointer result = null; + Iterator iterator = this.pointers.iterator(); + while (iterator.hasNext()) { + DialPointer p = (DialPointer) iterator.next(); + if (p.getDatasetIndex() == datasetIndex) { + return p; + } + } + return result; + } + + /** + * Returns the primary dataset for the plot. + * + * @return The primary dataset (possibly {@code null}). + */ + public ValueDataset getDataset() { + return getDataset(0); + } + + /** + * Returns the dataset at the given index. + * + * @param index the dataset index. + * + * @return The dataset (possibly {@code null}). + */ + public ValueDataset getDataset(int index) { + ValueDataset result = null; + if (this.datasets.size() > index) { + result = (ValueDataset) this.datasets.get(index); + } + return result; + } + + /** + * Sets the dataset for the plot, replacing the existing dataset, if there + * is one, and calls {@link #fireChangeEvent()}. + * + * @param dataset the dataset ({@code null} permitted). + */ + public void setDataset(ValueDataset dataset) { + setDataset(0, dataset); + } + + /** + * Sets a dataset for the plot. + * + * @param index the dataset index. + * @param dataset the dataset ({@code null} permitted). + */ + public void setDataset(int index, ValueDataset dataset) { + + ValueDataset existing = (ValueDataset) this.datasets.get(index); + if (existing != null) { + existing.removeChangeListener(this); + } + this.datasets.set(index, dataset); + if (dataset != null) { + dataset.addChangeListener(this); + } + + // send a dataset change event to self... + DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); + datasetChanged(event); + + } + + /** + * Returns the number of datasets. + * + * @return The number of datasets. + */ + public int getDatasetCount() { + return this.datasets.size(); + } + + /** + * Draws the plot. This method is usually called by the + * {@link org.jfree.chart.JFreeChart} instance that manages the plot. + * + * @param g2 the graphics target. + * @param area the area in which the plot should be drawn. + * @param anchor the anchor point (typically the last point that the + * mouse clicked on, {@code null} is permitted). + * @param parentState the state for the parent plot (if any). + * @param info used to collect plot rendering info ({@code null} + * permitted). + */ + @Override + public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, + PlotState parentState, PlotRenderingInfo info) { + + Shape origClip = g2.getClip(); + g2.setClip(area); + + // first, expand the viewing area into a drawing frame + Rectangle2D frame = viewToFrame(area); + + // draw the background if there is one... + if (this.background != null && this.background.isVisible()) { + if (this.background.isClippedToWindow()) { + Shape savedClip = g2.getClip(); + g2.clip(this.dialFrame.getWindow(frame)); + this.background.draw(g2, this, frame, area); + g2.setClip(savedClip); + } + else { + this.background.draw(g2, this, frame, area); + } + } + + Iterator iterator = this.layers.iterator(); + while (iterator.hasNext()) { + DialLayer current = (DialLayer) iterator.next(); + if (current.isVisible()) { + if (current.isClippedToWindow()) { + Shape savedClip = g2.getClip(); + g2.clip(this.dialFrame.getWindow(frame)); + current.draw(g2, this, frame, area); + g2.setClip(savedClip); + } + else { + current.draw(g2, this, frame, area); + } + } + } + + // draw the pointers + iterator = this.pointers.iterator(); + while (iterator.hasNext()) { + DialPointer current = (DialPointer) iterator.next(); + if (current.isVisible()) { + if (current.isClippedToWindow()) { + Shape savedClip = g2.getClip(); + g2.clip(this.dialFrame.getWindow(frame)); + current.draw(g2, this, frame, area); + g2.setClip(savedClip); + } + else { + current.draw(g2, this, frame, area); + } + } + } + + // draw the cap if there is one... + if (this.cap != null && this.cap.isVisible()) { + if (this.cap.isClippedToWindow()) { + Shape savedClip = g2.getClip(); + g2.clip(this.dialFrame.getWindow(frame)); + this.cap.draw(g2, this, frame, area); + g2.setClip(savedClip); + } + else { + this.cap.draw(g2, this, frame, area); + } + } + + if (this.dialFrame.isVisible()) { + this.dialFrame.draw(g2, this, frame, area); + } + + g2.setClip(origClip); + + } + + /** + * Returns the frame surrounding the specified view rectangle. + * + * @param view the view rectangle ({@code null} not permitted). + * + * @return The frame rectangle. + */ + private Rectangle2D viewToFrame(Rectangle2D view) { + double width = view.getWidth() / this.viewW; + double height = view.getHeight() / this.viewH; + double x = view.getX() - (width * this.viewX); + double y = view.getY() - (height * this.viewY); + return new Rectangle2D.Double(x, y, width, height); + } + + /** + * Returns the value from the specified dataset. + * + * @param datasetIndex the dataset index. + * + * @return The data value. + */ + public double getValue(int datasetIndex) { + double result = Double.NaN; + ValueDataset dataset = getDataset(datasetIndex); + if (dataset != null) { + Number n = dataset.getValue(); + if (n != null) { + result = n.doubleValue(); + } + } + return result; + } + + /** + * Adds a dial scale to the plot and calls {@link #fireChangeEvent()}. + * + * @param index the scale index. + * @param scale the scale ({@code null} not permitted). + */ + public void addScale(int index, DialScale scale) { + Args.nullNotPermitted(scale, "scale"); + DialScale existing = (DialScale) this.scales.get(index); + if (existing != null) { + removeLayer(existing); + } + this.layers.add(scale); + this.scales.set(index, scale); + scale.addChangeListener(this); + fireChangeEvent(); + } + + /** + * Returns the scale at the given index. + * + * @param index the scale index. + * + * @return The scale (possibly {@code null}). + */ + public DialScale getScale(int index) { + DialScale result = null; + if (this.scales.size() > index) { + result = (DialScale) this.scales.get(index); + } + return result; + } + + /** + * Maps a dataset to a particular scale. + * + * @param index the dataset index (zero-based). + * @param scaleIndex the scale index (zero-based). + */ + public void mapDatasetToScale(int index, int scaleIndex) { + this.datasetToScaleMap.set(index, scaleIndex); + fireChangeEvent(); + } + + /** + * Returns the dial scale for a specific dataset. + * + * @param datasetIndex the dataset index. + * + * @return The dial scale. + */ + public DialScale getScaleForDataset(int datasetIndex) { + DialScale result = (DialScale) this.scales.get(0); + Integer scaleIndex = (Integer) this.datasetToScaleMap.get(datasetIndex); + if (scaleIndex != null) { + result = getScale(scaleIndex); + } + return result; + } + + /** + * A utility method that computes a rectangle using relative radius values. + * + * @param rect the reference rectangle ({@code null} not permitted). + * @param radiusW the width radius (must be > 0.0) + * @param radiusH the height radius. + * + * @return A new rectangle. + */ + public static Rectangle2D rectangleByRadius(Rectangle2D rect, + double radiusW, double radiusH) { + Args.nullNotPermitted(rect, "rect"); + double x = rect.getCenterX(); + double y = rect.getCenterY(); + double w = rect.getWidth() * radiusW; + double h = rect.getHeight() * radiusH; + return new Rectangle2D.Double(x - w / 2.0, y - h / 2.0, w, h); + } + + /** + * Called when a layer has changed, and calls {@link #fireChangeEvent()}. + * + * @param event the event. + */ + @Override + public void dialLayerChanged(DialLayerChangeEvent event) { + fireChangeEvent(); + } + + /** + * Tests this {@code DialPlot} instance for equality with an + * arbitrary object. The plot's dataset(s) is (are) not included in + * the test. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof DialPlot)) { + return false; + } + DialPlot that = (DialPlot) obj; + if (!Objects.equals(this.background, that.background)) { + return false; + } + if (!Objects.equals(this.cap, that.cap)) { + return false; + } + if (!this.dialFrame.equals(that.dialFrame)) { + return false; + } + if (this.viewX != that.viewX) { + return false; + } + if (this.viewY != that.viewY) { + return false; + } + if (this.viewW != that.viewW) { + return false; + } + if (this.viewH != that.viewH) { + return false; + } + if (!this.layers.equals(that.layers)) { + return false; + } + if (!this.pointers.equals(that.pointers)) { + return false; + } + return super.equals(obj); + } + + /** + * Returns a hash code for this instance. + * + * @return The hash code. + */ + @Override + public int hashCode() { + int result = 193; + result = 37 * result + ObjectUtils.hashCode(this.background); + result = 37 * result + ObjectUtils.hashCode(this.cap); + result = 37 * result + this.dialFrame.hashCode(); + long temp = Double.doubleToLongBits(this.viewX); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(this.viewY); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(this.viewW); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(this.viewH); + result = 37 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + /** + * Returns the plot type. + * + * @return {@code "DialPlot"} + */ + @Override + public String getPlotType() { + return "DialPlot"; + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + } + + +} diff --git a/src/main/java/org/jfree/chart/renderer/AbstractRenderer.java b/src/main/java/org/jfree/chart/renderer/AbstractRenderer.java index 7fba691d4..2db0fe4f3 100644 --- a/src/main/java/org/jfree/chart/renderer/AbstractRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/AbstractRenderer.java @@ -68,7 +68,6 @@ import org.jfree.chart.labels.ItemLabelPosition; import org.jfree.chart.plot.DrawingSupplier; import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.title.LegendTitle; import org.jfree.chart.ui.RectangleInsets; import org.jfree.chart.ui.TextAnchor; import org.jfree.chart.util.BooleanList; @@ -290,9 +289,9 @@ public abstract class AbstractRenderer implements Cloneable, Serializable { private PaintList legendTextPaint; /** - * The default paint for the legend text items (if this is - * {@code null}, the {@link LegendTitle} class will determine the - * text paint to use. + * The default paint for the legend text items (if this is {@code null}, the + * {@link org.jfree.chart.title.LegendTitle} class will determine the text + * paint to use. */ private transient Paint defaultLegendTextPaint; diff --git a/src/main/java/org/jfree/chart/renderer/DefaultPolarItemRenderer.java b/src/main/java/org/jfree/chart/renderer/DefaultPolarItemRenderer.java index b158fc8b1..21741c84d 100644 --- a/src/main/java/org/jfree/chart/renderer/DefaultPolarItemRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/DefaultPolarItemRenderer.java @@ -1,928 +1,918 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ----------------------------- - * DefaultPolarItemRenderer.java - * ----------------------------- - * (C) Copyright 2004-present, by Solution Engineering, Inc. and - * Contributors. - * - * Original Author: Daniel Bridenbecker, Solution Engineering, Inc.; - * Contributor(s): David Gilbert; - * Martin Hoeller (patch 2850344); - * - */ - -package org.jfree.chart.renderer; - -import java.awt.AlphaComposite; -import java.awt.Composite; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Point; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Ellipse2D; -import java.awt.geom.GeneralPath; -import java.awt.geom.Line2D; -import java.awt.geom.PathIterator; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; - -import org.jfree.chart.LegendItem; -import org.jfree.chart.axis.NumberTick; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.entity.XYItemEntity; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.labels.XYSeriesLabelGenerator; -import org.jfree.chart.labels.XYToolTipGenerator; -import org.jfree.chart.plot.DrawingSupplier; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.PolarPlot; -import org.jfree.chart.text.TextUtils; -import org.jfree.chart.urls.XYURLGenerator; -import org.jfree.chart.util.BooleanList; -import org.jfree.chart.util.ObjectList; -import org.jfree.chart.util.ObjectUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.SerialUtils; -import org.jfree.chart.util.ShapeUtils; -import org.jfree.data.xy.XYDataset; - -/** - * A renderer that can be used with the {@link PolarPlot} class. - */ -public class DefaultPolarItemRenderer extends AbstractRenderer - implements PolarItemRenderer { - - /** The plot that the renderer is assigned to. */ - private PolarPlot plot; - - /** Flags that control whether the renderer fills each series or not. */ - private BooleanList seriesFilled; - - /** - * Flag that controls whether an outline is drawn for filled series or - * not. - */ - private boolean drawOutlineWhenFilled; - - /** - * The composite to use when filling series. - */ - private transient Composite fillComposite; - - /** - * A flag that controls whether the fill paint is used for filling - * shapes. - */ - private boolean useFillPaint; - - /** - * The shape that is used to represent a line in the legend. - */ - private transient Shape legendLine; - - /** - * Flag that controls whether item shapes are visible or not. - */ - private boolean shapesVisible; - - /** - * Flag that controls if the first and last point of the dataset should be - * connected or not. - */ - private boolean connectFirstAndLastPoint; - - /** - * A list of tool tip generators (one per series). - */ - private ObjectList toolTipGeneratorList; - - /** - * The base tool tip generator. - */ - private XYToolTipGenerator baseToolTipGenerator; - - /** - * The URL text generator. - */ - private XYURLGenerator urlGenerator; - - /** - * The legend item tool tip generator. - */ - private XYSeriesLabelGenerator legendItemToolTipGenerator; - - /** - * The legend item URL generator. - */ - private XYSeriesLabelGenerator legendItemURLGenerator; - - /** - * Creates a new instance of DefaultPolarItemRenderer - */ - public DefaultPolarItemRenderer() { - this.seriesFilled = new BooleanList(); - this.drawOutlineWhenFilled = true; - this.fillComposite = AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, 0.3f); - this.useFillPaint = false; // use item paint for fills by default - this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); - this.shapesVisible = true; - this.connectFirstAndLastPoint = true; - - this.toolTipGeneratorList = new ObjectList(); - this.urlGenerator = null; - this.legendItemToolTipGenerator = null; - this.legendItemURLGenerator = null; - } - - /** - * Set the plot associated with this renderer. - * - * @param plot the plot. - * - * @see #getPlot() - */ - @Override - public void setPlot(PolarPlot plot) { - this.plot = plot; - } - - /** - * Return the plot associated with this renderer. - * - * @return The plot. - * - * @see #setPlot(PolarPlot) - */ - @Override - public PolarPlot getPlot() { - return this.plot; - } - - /** - * Returns {@code true} if the renderer will draw an outline around - * a filled polygon, {@code false} otherwise. - * - * @return A boolean. - */ - public boolean getDrawOutlineWhenFilled() { - return this.drawOutlineWhenFilled; - } - - /** - * Set the flag that controls whether the outline around a filled - * polygon will be drawn or not and sends a {@link RendererChangeEvent} - * to all registered listeners. - * - * @param drawOutlineWhenFilled the flag. - */ - public void setDrawOutlineWhenFilled(boolean drawOutlineWhenFilled) { - this.drawOutlineWhenFilled = drawOutlineWhenFilled; - fireChangeEvent(); - } - - /** - * Get the composite that is used for filling. - * - * @return The composite (never {@code null}). - */ - public Composite getFillComposite() { - return this.fillComposite; - } - - /** - * Sets the composite which will be used for filling polygons and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param composite the composite to use ({@code null} not - * permitted). - */ - public void setFillComposite(Composite composite) { - Args.nullNotPermitted(composite, "composite"); - this.fillComposite = composite; - fireChangeEvent(); - } - - /** - * Returns {@code true} if a shape will be drawn for every item, or - * {@code false} if not. - * - * @return A boolean. - */ - public boolean getShapesVisible() { - return this.shapesVisible; - } - - /** - * Set the flag that controls whether a shape will be drawn for every - * item, or not and sends a {@link RendererChangeEvent} to all registered - * listeners. - * - * @param visible the flag. - */ - public void setShapesVisible(boolean visible) { - this.shapesVisible = visible; - fireChangeEvent(); - } - - /** - * Returns {@code true} if first and last point of a series will be - * connected, {@code false} otherwise. - * - * @return The current status of the flag. - */ - public boolean getConnectFirstAndLastPoint() { - return this.connectFirstAndLastPoint; - } - - /** - * Set the flag that controls whether the first and last point of a series - * will be connected or not and sends a {@link RendererChangeEvent} to all - * registered listeners. - * - * @param connect the flag. - */ - public void setConnectFirstAndLastPoint(boolean connect) { - this.connectFirstAndLastPoint = connect; - fireChangeEvent(); - } - - /** - * Returns the drawing supplier from the plot. - * - * @return The drawing supplier. - */ - @Override - public DrawingSupplier getDrawingSupplier() { - DrawingSupplier result = null; - PolarPlot p = getPlot(); - if (p != null) { - result = p.getDrawingSupplier(); - } - return result; - } - - /** - * Returns {@code true} if the renderer should fill the specified - * series, and {@code false} otherwise. - * - * @param series the series index (zero-based). - * - * @return A boolean. - */ - public boolean isSeriesFilled(int series) { - boolean result = false; - Boolean b = this.seriesFilled.getBoolean(series); - if (b != null) { - result = b; - } - return result; - } - - /** - * Sets a flag that controls whether or not a series is filled. - * - * @param series the series index. - * @param filled the flag. - */ - public void setSeriesFilled(int series, boolean filled) { - this.seriesFilled.setBoolean(series, filled); - } - - /** - * Returns {@code true} if the renderer should use the fill paint - * setting to fill shapes, and {@code false} if it should just - * use the regular paint. - * - * @return A boolean. - * - * @see #setUseFillPaint(boolean) - */ - public boolean getUseFillPaint() { - return this.useFillPaint; - } - - /** - * Sets the flag that controls whether the fill paint is used to fill - * shapes, and sends a {@link RendererChangeEvent} to all - * registered listeners. - * - * @param flag the flag. - * - * @see #getUseFillPaint() - */ - public void setUseFillPaint(boolean flag) { - this.useFillPaint = flag; - fireChangeEvent(); - } - - /** - * Returns the shape used to represent a line in the legend. - * - * @return The legend line (never {@code null}). - * - * @see #setLegendLine(Shape) - */ - public Shape getLegendLine() { - return this.legendLine; - } - - /** - * Sets the shape used as a line in each legend item and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param line the line ({@code null} not permitted). - * - * @see #getLegendLine() - */ - public void setLegendLine(Shape line) { - Args.nullNotPermitted(line, "line"); - this.legendLine = line; - fireChangeEvent(); - } - - /** - * Adds an entity to the collection. - * - * @param entities the entity collection being populated. - * @param area the entity area (if {@code null} a default will be - * used). - * @param dataset the dataset. - * @param series the series. - * @param item the item. - * @param entityX the entity's center x-coordinate in user space (only - * used if {@code area} is {@code null}). - * @param entityY the entity's center y-coordinate in user space (only - * used if {@code area} is {@code null}). - */ - protected void addEntity(EntityCollection entities, Shape area, - XYDataset dataset, int series, int item, - double entityX, double entityY) { - if (!getItemCreateEntity(series, item)) { - return; - } - Shape hotspot = area; - if (hotspot == null) { - double r = getDefaultEntityRadius(); - double w = r * 2; - if (getPlot().getOrientation() == PlotOrientation.VERTICAL) { - hotspot = new Ellipse2D.Double(entityX - r, entityY - r, w, w); - } - else { - hotspot = new Ellipse2D.Double(entityY - r, entityX - r, w, w); - } - } - String tip = null; - XYToolTipGenerator generator = getToolTipGenerator(series, item); - if (generator != null) { - tip = generator.generateToolTip(dataset, series, item); - } - String url = null; - if (getURLGenerator() != null) { - url = getURLGenerator().generateURL(dataset, series, item); - } - XYItemEntity entity = new XYItemEntity(hotspot, dataset, series, item, - tip, url); - entities.add(entity); - } - - /** - * Plots the data for a given series. - * - * @param g2 the drawing surface. - * @param dataArea the data area. - * @param info collects plot rendering info. - * @param plot the plot. - * @param dataset the dataset. - * @param seriesIndex the series index. - */ - @Override - public void drawSeries(Graphics2D g2, Rectangle2D dataArea, - PlotRenderingInfo info, PolarPlot plot, XYDataset dataset, - int seriesIndex) { - - final int numPoints = dataset.getItemCount(seriesIndex); - if (numPoints == 0) { - return; - } - GeneralPath poly = null; - ValueAxis axis = plot.getAxisForDataset(plot.indexOf(dataset)); - for (int i = 0; i < numPoints; i++) { - double theta = dataset.getXValue(seriesIndex, i); - double radius = dataset.getYValue(seriesIndex, i); - Point p = plot.translateToJava2D(theta, radius, axis, dataArea); - if (poly == null) { - poly = new GeneralPath(); - poly.moveTo(p.x, p.y); - } - else { - poly.lineTo(p.x, p.y); - } - } - assert poly != null; - if (getConnectFirstAndLastPoint()) { - poly.closePath(); - } - - g2.setPaint(lookupSeriesPaint(seriesIndex)); - g2.setStroke(lookupSeriesStroke(seriesIndex)); - if (isSeriesFilled(seriesIndex)) { - Composite savedComposite = g2.getComposite(); - g2.setComposite(this.fillComposite); - g2.fill(poly); - g2.setComposite(savedComposite); - if (this.drawOutlineWhenFilled) { - // draw the outline of the filled polygon - g2.setPaint(lookupSeriesOutlinePaint(seriesIndex)); - g2.draw(poly); - } - } - else { - // just the lines, no filling - g2.draw(poly); - } - - // draw the item shapes - if (this.shapesVisible) { - // setup for collecting optional entity info... - EntityCollection entities = null; - if (info != null) { - entities = info.getOwner().getEntityCollection(); - } - - PathIterator pi = poly.getPathIterator(null); - int i = 0; - while (!pi.isDone()) { - final float[] coords = new float[6]; - final int segType = pi.currentSegment(coords); - pi.next(); - if (segType != PathIterator.SEG_LINETO && - segType != PathIterator.SEG_MOVETO) { - continue; - } - final int x = Math.round(coords[0]); - final int y = Math.round(coords[1]); - final Shape shape = ShapeUtils.createTranslatedShape( - getItemShape(seriesIndex, i++), x, y); - - Paint paint; - if (useFillPaint) { - paint = lookupSeriesFillPaint(seriesIndex); - } - else { - paint = lookupSeriesPaint(seriesIndex); - } - g2.setPaint(paint); - g2.fill(shape); - if (isSeriesFilled(seriesIndex) && this.drawOutlineWhenFilled) { - g2.setPaint(lookupSeriesOutlinePaint(seriesIndex)); - g2.setStroke(lookupSeriesOutlineStroke(seriesIndex)); - g2.draw(shape); - } - - // add an entity for the item, but only if it falls within the - // data area... - if (entities != null && ShapeUtils.isPointInRect(dataArea, x, - y)) { - addEntity(entities, shape, dataset, seriesIndex, i-1, x, y); - } - } - } - } - - /** - * Draw the angular gridlines - the spokes. - * - * @param g2 the drawing surface. - * @param plot the plot ({@code null} not permitted). - * @param ticks the ticks ({@code null} not permitted). - * @param dataArea the data area. - */ - @Override - public void drawAngularGridLines(Graphics2D g2, PolarPlot plot, - List ticks, Rectangle2D dataArea) { - - g2.setFont(plot.getAngleLabelFont()); - g2.setStroke(plot.getAngleGridlineStroke()); - g2.setPaint(plot.getAngleGridlinePaint()); - - ValueAxis axis = plot.getAxis(); - double centerValue, outerValue; - if (axis.isInverted()) { - outerValue = axis.getLowerBound(); - centerValue = axis.getUpperBound(); - } else { - outerValue = axis.getUpperBound(); - centerValue = axis.getLowerBound(); - } - Point center = plot.translateToJava2D(0, centerValue, axis, dataArea); - Iterator iterator = ticks.iterator(); - while (iterator.hasNext()) { - NumberTick tick = (NumberTick) iterator.next(); - double tickVal = tick.getNumber().doubleValue(); - Point p = plot.translateToJava2D(tickVal, outerValue, axis, - dataArea); - g2.setPaint(plot.getAngleGridlinePaint()); - g2.drawLine(center.x, center.y, p.x, p.y); - if (plot.isAngleLabelsVisible()) { - int x = p.x; - int y = p.y; - g2.setPaint(plot.getAngleLabelPaint()); - TextUtils.drawAlignedString(tick.getText(), g2, x, y, - tick.getTextAnchor()); - } - } - } - - /** - * Draw the radial gridlines - the rings. - * - * @param g2 the drawing surface ({@code null} not permitted). - * @param plot the plot ({@code null} not permitted). - * @param radialAxis the radial axis ({@code null} not permitted). - * @param ticks the ticks ({@code null} not permitted). - * @param dataArea the data area. - */ - @Override - public void drawRadialGridLines(Graphics2D g2, PolarPlot plot, - ValueAxis radialAxis, List ticks, Rectangle2D dataArea) { - - Args.nullNotPermitted(radialAxis, "radialAxis"); - g2.setFont(radialAxis.getTickLabelFont()); - g2.setPaint(plot.getRadiusGridlinePaint()); - g2.setStroke(plot.getRadiusGridlineStroke()); - - double centerValue; - if (radialAxis.isInverted()) { - centerValue = radialAxis.getUpperBound(); - } else { - centerValue = radialAxis.getLowerBound(); - } - Point center = plot.translateToJava2D(0, centerValue, radialAxis, dataArea); - - Iterator iterator = ticks.iterator(); - while (iterator.hasNext()) { - NumberTick tick = (NumberTick) iterator.next(); - double angleDegrees = plot.isCounterClockwise() - ? plot.getAngleOffset() : -plot.getAngleOffset(); - Point p = plot.translateToJava2D(angleDegrees, - tick.getNumber().doubleValue(), radialAxis, dataArea); - int r = p.x - center.x; - int upperLeftX = center.x - r; - int upperLeftY = center.y - r; - int d = 2 * r; - Ellipse2D ring = new Ellipse2D.Double(upperLeftX, upperLeftY, d, d); - g2.setPaint(plot.getRadiusGridlinePaint()); - g2.draw(ring); - } - } - - /** - * Return the legend for the given series. - * - * @param series the series index. - * - * @return The legend item. - */ - @Override - public LegendItem getLegendItem(int series) { - LegendItem result; - PolarPlot p = getPlot(); - if (p == null) { - return null; - } - XYDataset dataset = p.getDataset(p.getIndexOf(this)); - if (dataset == null) { - return null; - } - - String toolTipText = null; - if (getLegendItemToolTipGenerator() != null) { - toolTipText = getLegendItemToolTipGenerator().generateLabel( - dataset, series); - } - String urlText = null; - if (getLegendItemURLGenerator() != null) { - urlText = getLegendItemURLGenerator().generateLabel(dataset, - series); - } - - Comparable seriesKey = dataset.getSeriesKey(series); - String label = seriesKey.toString(); - String description = label; - Shape shape = lookupSeriesShape(series); - Paint paint; - if (this.useFillPaint) { - paint = lookupSeriesFillPaint(series); - } - else { - paint = lookupSeriesPaint(series); - } - Stroke stroke = lookupSeriesStroke(series); - Paint outlinePaint = lookupSeriesOutlinePaint(series); - Stroke outlineStroke = lookupSeriesOutlineStroke(series); - boolean shapeOutlined = isSeriesFilled(series) - && this.drawOutlineWhenFilled; - result = new LegendItem(label, description, toolTipText, urlText, - getShapesVisible(), shape, /* shapeFilled=*/ true, paint, - shapeOutlined, outlinePaint, outlineStroke, - /* lineVisible= */ true, this.legendLine, stroke, paint); - result.setToolTipText(toolTipText); - result.setURLText(urlText); - result.setDataset(dataset); - result.setSeriesKey(seriesKey); - result.setSeriesIndex(series); - - return result; - } - - /** - * Returns the tooltip generator for the specified series and item. - * - * @param series the series index. - * @param item the item index. - * - * @return The tooltip generator (possibly {@code null}). - */ - @Override - public XYToolTipGenerator getToolTipGenerator(int series, int item) { - XYToolTipGenerator generator - = (XYToolTipGenerator) this.toolTipGeneratorList.get(series); - if (generator == null) { - generator = this.baseToolTipGenerator; - } - return generator; - } - - /** - * Returns the tool tip generator for the specified series. - * - * @return The tooltip generator (possibly {@code null}). - */ - @Override - public XYToolTipGenerator getSeriesToolTipGenerator(int series) { - return (XYToolTipGenerator) this.toolTipGeneratorList.get(series); - } - - /** - * Sets the tooltip generator for the specified series. - * - * @param series the series index. - * @param generator the tool tip generator ({@code null} permitted). - */ - @Override - public void setSeriesToolTipGenerator(int series, - XYToolTipGenerator generator) { - this.toolTipGeneratorList.set(series, generator); - fireChangeEvent(); - } - - /** - * Returns the default tool tip generator. - * - * @return The default tool tip generator (possibly {@code null}). - */ - @Override - public XYToolTipGenerator getBaseToolTipGenerator() { - return this.baseToolTipGenerator; - } - - /** - * Sets the default tool tip generator and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - */ - @Override - public void setBaseToolTipGenerator(XYToolTipGenerator generator) { - this.baseToolTipGenerator = generator; - fireChangeEvent(); - } - - /** - * Returns the URL generator. - * - * @return The URL generator (possibly {@code null}). - */ - @Override - public XYURLGenerator getURLGenerator() { - return this.urlGenerator; - } - - /** - * Sets the URL generator. - * - * @param urlGenerator the generator ({@code null} permitted) - */ - @Override - public void setURLGenerator(XYURLGenerator urlGenerator) { - this.urlGenerator = urlGenerator; - fireChangeEvent(); - } - - /** - * Returns the legend item tool tip generator. - * - * @return The tool tip generator (possibly {@code null}). - * - * @see #setLegendItemToolTipGenerator(XYSeriesLabelGenerator) - */ - public XYSeriesLabelGenerator getLegendItemToolTipGenerator() { - return this.legendItemToolTipGenerator; - } - - /** - * Sets the legend item tool tip generator and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - * - * @see #getLegendItemToolTipGenerator() - */ - public void setLegendItemToolTipGenerator( - XYSeriesLabelGenerator generator) { - this.legendItemToolTipGenerator = generator; - fireChangeEvent(); - } - - /** - * Returns the legend item URL generator. - * - * @return The URL generator (possibly {@code null}). - * - * @see #setLegendItemURLGenerator(XYSeriesLabelGenerator) - */ - public XYSeriesLabelGenerator getLegendItemURLGenerator() { - return this.legendItemURLGenerator; - } - - /** - * Sets the legend item URL generator and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - * - * @see #getLegendItemURLGenerator() - */ - public void setLegendItemURLGenerator(XYSeriesLabelGenerator generator) { - this.legendItemURLGenerator = generator; - fireChangeEvent(); - } - - /** - * Tests this renderer for equality with an arbitrary object. - * - * @param obj the object ({@code null} not permitted). - * - * @return {@code true} if this renderer is equal to {@code obj}, - * and {@code false} otherwise. - */ - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (!(obj instanceof DefaultPolarItemRenderer)) { - return false; - } - DefaultPolarItemRenderer that = (DefaultPolarItemRenderer) obj; - if (!this.seriesFilled.equals(that.seriesFilled)) { - return false; - } - if (this.drawOutlineWhenFilled != that.drawOutlineWhenFilled) { - return false; - } - if (!Objects.equals(this.fillComposite, that.fillComposite)) { - return false; - } - if (this.useFillPaint != that.useFillPaint) { - return false; - } - if (!ShapeUtils.equal(this.legendLine, that.legendLine)) { - return false; - } - if (this.shapesVisible != that.shapesVisible) { - return false; - } - if (this.connectFirstAndLastPoint != that.connectFirstAndLastPoint) { - return false; - } - if (!this.toolTipGeneratorList.equals(that.toolTipGeneratorList)) { - return false; - } - if (!Objects.equals(this.baseToolTipGenerator, - that.baseToolTipGenerator)) { - return false; - } - if (!Objects.equals(this.urlGenerator, that.urlGenerator)) { - return false; - } - if (!Objects.equals(this.legendItemToolTipGenerator, - that.legendItemToolTipGenerator)) { - return false; - } - if (!Objects.equals(this.legendItemURLGenerator, - that.legendItemURLGenerator)) { - return false; - } - return super.equals(obj); - } - - /** - * Returns a clone of the renderer. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the renderer cannot be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - DefaultPolarItemRenderer clone - = (DefaultPolarItemRenderer) super.clone(); - if (this.legendLine != null) { - clone.legendLine = ShapeUtils.clone(this.legendLine); - } - clone.seriesFilled = (BooleanList) this.seriesFilled.clone(); - clone.toolTipGeneratorList - = (ObjectList) this.toolTipGeneratorList.clone(); - if (clone.baseToolTipGenerator instanceof PublicCloneable) { - clone.baseToolTipGenerator = (XYToolTipGenerator) - ObjectUtils.clone(this.baseToolTipGenerator); - } - if (clone.urlGenerator instanceof PublicCloneable) { - clone.urlGenerator = (XYURLGenerator) - ObjectUtils.clone(this.urlGenerator); - } - if (clone.legendItemToolTipGenerator instanceof PublicCloneable) { - clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator) - ObjectUtils.clone(this.legendItemToolTipGenerator); - } - if (clone.legendItemURLGenerator instanceof PublicCloneable) { - clone.legendItemURLGenerator = (XYSeriesLabelGenerator) - ObjectUtils.clone(this.legendItemURLGenerator); - } - return clone; - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - this.legendLine = SerialUtils.readShape(stream); - this.fillComposite = SerialUtils.readComposite(stream); - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writeShape(this.legendLine, stream); - SerialUtils.writeComposite(this.fillComposite, stream); - } -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ----------------------------- + * DefaultPolarItemRenderer.java + * ----------------------------- + * (C) Copyright 2004-present, by Solution Engineering, Inc. and + * Contributors. + * + * Original Author: Daniel Bridenbecker, Solution Engineering, Inc.; + * Contributor(s): David Gilbert; + * Martin Hoeller (patch 2850344); + * + */ + +package org.jfree.chart.renderer; + +import java.awt.AlphaComposite; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Point; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import org.jfree.chart.LegendItem; +import org.jfree.chart.axis.NumberTick; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.entity.EntityCollection; +import org.jfree.chart.entity.XYItemEntity; +import org.jfree.chart.labels.XYSeriesLabelGenerator; +import org.jfree.chart.labels.XYToolTipGenerator; +import org.jfree.chart.plot.DrawingSupplier; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.PolarPlot; +import org.jfree.chart.text.TextUtils; +import org.jfree.chart.urls.XYURLGenerator; +import org.jfree.chart.util.BooleanList; +import org.jfree.chart.util.ObjectList; +import org.jfree.chart.util.ObjectUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.SerialUtils; +import org.jfree.chart.util.ShapeUtils; +import org.jfree.data.xy.XYDataset; + +/** + * A renderer that can be used with the {@link PolarPlot} class. + */ +public class DefaultPolarItemRenderer extends AbstractRenderer + implements PolarItemRenderer { + + /** The plot that the renderer is assigned to. */ + private PolarPlot plot; + + /** Flags that control whether the renderer fills each series or not. */ + private BooleanList seriesFilled; + + /** + * Flag that controls whether an outline is drawn for filled series or + * not. + */ + private boolean drawOutlineWhenFilled; + + /** + * The composite to use when filling series. + */ + private transient Composite fillComposite; + + /** + * A flag that controls whether the fill paint is used for filling + * shapes. + */ + private boolean useFillPaint; + + /** + * The shape that is used to represent a line in the legend. + */ + private transient Shape legendLine; + + /** + * Flag that controls whether item shapes are visible or not. + */ + private boolean shapesVisible; + + /** + * Flag that controls if the first and last point of the dataset should be + * connected or not. + */ + private boolean connectFirstAndLastPoint; + + /** + * A list of tool tip generators (one per series). + */ + private ObjectList toolTipGeneratorList; + + /** + * The base tool tip generator. + */ + private XYToolTipGenerator baseToolTipGenerator; + + /** + * The URL text generator. + */ + private XYURLGenerator urlGenerator; + + /** + * The legend item tool tip generator. + */ + private XYSeriesLabelGenerator legendItemToolTipGenerator; + + /** + * The legend item URL generator. + */ + private XYSeriesLabelGenerator legendItemURLGenerator; + + /** + * Creates a new instance of DefaultPolarItemRenderer + */ + public DefaultPolarItemRenderer() { + this.seriesFilled = new BooleanList(); + this.drawOutlineWhenFilled = true; + this.fillComposite = AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, 0.3f); + this.useFillPaint = false; // use item paint for fills by default + this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); + this.shapesVisible = true; + this.connectFirstAndLastPoint = true; + + this.toolTipGeneratorList = new ObjectList(); + this.urlGenerator = null; + this.legendItemToolTipGenerator = null; + this.legendItemURLGenerator = null; + } + + /** + * Set the plot associated with this renderer. + * + * @param plot the plot. + * + * @see #getPlot() + */ + @Override + public void setPlot(PolarPlot plot) { + this.plot = plot; + } + + /** + * Return the plot associated with this renderer. + * + * @return The plot. + * + * @see #setPlot(PolarPlot) + */ + @Override + public PolarPlot getPlot() { + return this.plot; + } + + /** + * Returns {@code true} if the renderer will draw an outline around + * a filled polygon, {@code false} otherwise. + * + * @return A boolean. + */ + public boolean getDrawOutlineWhenFilled() { + return this.drawOutlineWhenFilled; + } + + /** + * Set the flag that controls whether the outline around a filled + * polygon will be drawn or not and calls {@link #fireChangeEvent()}. + * + * @param drawOutlineWhenFilled the flag. + */ + public void setDrawOutlineWhenFilled(boolean drawOutlineWhenFilled) { + this.drawOutlineWhenFilled = drawOutlineWhenFilled; + fireChangeEvent(); + } + + /** + * Get the composite that is used for filling. + * + * @return The composite (never {@code null}). + */ + public Composite getFillComposite() { + return this.fillComposite; + } + + /** + * Sets the composite which will be used for filling polygons and calls + * {@link #fireChangeEvent()}. + * + * @param composite the composite to use ({@code null} not permitted). + */ + public void setFillComposite(Composite composite) { + Args.nullNotPermitted(composite, "composite"); + this.fillComposite = composite; + fireChangeEvent(); + } + + /** + * Returns {@code true} if a shape will be drawn for every item, or + * {@code false} if not. + * + * @return A boolean. + */ + public boolean getShapesVisible() { + return this.shapesVisible; + } + + /** + * Set the flag that controls whether a shape will be drawn for every + * item, or not and calls {@link #fireChangeEvent()}. + * + * @param visible the flag. + */ + public void setShapesVisible(boolean visible) { + this.shapesVisible = visible; + fireChangeEvent(); + } + + /** + * Returns {@code true} if first and last point of a series will be + * connected, {@code false} otherwise. + * + * @return The current status of the flag. + */ + public boolean getConnectFirstAndLastPoint() { + return this.connectFirstAndLastPoint; + } + + /** + * Set the flag that controls whether the first and last point of a series + * will be connected or not and calls {@link #fireChangeEvent()}. + * + * @param connect the flag. + */ + public void setConnectFirstAndLastPoint(boolean connect) { + this.connectFirstAndLastPoint = connect; + fireChangeEvent(); + } + + /** + * Returns the drawing supplier from the plot. + * + * @return The drawing supplier. + */ + @Override + public DrawingSupplier getDrawingSupplier() { + DrawingSupplier result = null; + PolarPlot p = getPlot(); + if (p != null) { + result = p.getDrawingSupplier(); + } + return result; + } + + /** + * Returns {@code true} if the renderer should fill the specified + * series, and {@code false} otherwise. + * + * @param series the series index (zero-based). + * + * @return A boolean. + */ + public boolean isSeriesFilled(int series) { + boolean result = false; + Boolean b = this.seriesFilled.getBoolean(series); + if (b != null) { + result = b; + } + return result; + } + + /** + * Sets a flag that controls whether or not a series is filled. + * + * @param series the series index. + * @param filled the flag. + */ + public void setSeriesFilled(int series, boolean filled) { + this.seriesFilled.setBoolean(series, filled); + } + + /** + * Returns {@code true} if the renderer should use the fill paint + * setting to fill shapes, and {@code false} if it should just + * use the regular paint. + * + * @return A boolean. + * + * @see #setUseFillPaint(boolean) + */ + public boolean getUseFillPaint() { + return this.useFillPaint; + } + + /** + * Sets the flag that controls whether the fill paint is used to fill + * shapes, and calls {@link #fireChangeEvent()}. + * + * @param flag the flag. + * + * @see #getUseFillPaint() + */ + public void setUseFillPaint(boolean flag) { + this.useFillPaint = flag; + fireChangeEvent(); + } + + /** + * Returns the shape used to represent a line in the legend. + * + * @return The legend line (never {@code null}). + * + * @see #setLegendLine(Shape) + */ + public Shape getLegendLine() { + return this.legendLine; + } + + /** + * Sets the shape used as a line in each legend item and calls {@link #fireChangeEvent()}. + * + * @param line the line ({@code null} not permitted). + * + * @see #getLegendLine() + */ + public void setLegendLine(Shape line) { + Args.nullNotPermitted(line, "line"); + this.legendLine = line; + fireChangeEvent(); + } + + /** + * Adds an entity to the collection. + * + * @param entities the entity collection being populated. + * @param area the entity area (if {@code null} a default will be + * used). + * @param dataset the dataset. + * @param series the series. + * @param item the item. + * @param entityX the entity's center x-coordinate in user space (only + * used if {@code area} is {@code null}). + * @param entityY the entity's center y-coordinate in user space (only + * used if {@code area} is {@code null}). + */ + protected void addEntity(EntityCollection entities, Shape area, + XYDataset dataset, int series, int item, + double entityX, double entityY) { + if (!getItemCreateEntity(series, item)) { + return; + } + Shape hotspot = area; + if (hotspot == null) { + double r = getDefaultEntityRadius(); + double w = r * 2; + if (getPlot().getOrientation() == PlotOrientation.VERTICAL) { + hotspot = new Ellipse2D.Double(entityX - r, entityY - r, w, w); + } + else { + hotspot = new Ellipse2D.Double(entityY - r, entityX - r, w, w); + } + } + String tip = null; + XYToolTipGenerator generator = getToolTipGenerator(series, item); + if (generator != null) { + tip = generator.generateToolTip(dataset, series, item); + } + String url = null; + if (getURLGenerator() != null) { + url = getURLGenerator().generateURL(dataset, series, item); + } + XYItemEntity entity = new XYItemEntity(hotspot, dataset, series, item, + tip, url); + entities.add(entity); + } + + /** + * Plots the data for a given series. + * + * @param g2 the drawing surface. + * @param dataArea the data area. + * @param info collects plot rendering info. + * @param plot the plot. + * @param dataset the dataset. + * @param seriesIndex the series index. + */ + @Override + public void drawSeries(Graphics2D g2, Rectangle2D dataArea, + PlotRenderingInfo info, PolarPlot plot, XYDataset dataset, + int seriesIndex) { + + final int numPoints = dataset.getItemCount(seriesIndex); + if (numPoints == 0) { + return; + } + GeneralPath poly = null; + ValueAxis axis = plot.getAxisForDataset(plot.indexOf(dataset)); + for (int i = 0; i < numPoints; i++) { + double theta = dataset.getXValue(seriesIndex, i); + double radius = dataset.getYValue(seriesIndex, i); + Point p = plot.translateToJava2D(theta, radius, axis, dataArea); + if (poly == null) { + poly = new GeneralPath(); + poly.moveTo(p.x, p.y); + } + else { + poly.lineTo(p.x, p.y); + } + } + assert poly != null; + if (getConnectFirstAndLastPoint()) { + poly.closePath(); + } + + g2.setPaint(lookupSeriesPaint(seriesIndex)); + g2.setStroke(lookupSeriesStroke(seriesIndex)); + if (isSeriesFilled(seriesIndex)) { + Composite savedComposite = g2.getComposite(); + g2.setComposite(this.fillComposite); + g2.fill(poly); + g2.setComposite(savedComposite); + if (this.drawOutlineWhenFilled) { + // draw the outline of the filled polygon + g2.setPaint(lookupSeriesOutlinePaint(seriesIndex)); + g2.draw(poly); + } + } + else { + // just the lines, no filling + g2.draw(poly); + } + + // draw the item shapes + if (this.shapesVisible) { + // setup for collecting optional entity info... + EntityCollection entities = null; + if (info != null) { + entities = info.getOwner().getEntityCollection(); + } + + PathIterator pi = poly.getPathIterator(null); + int i = 0; + while (!pi.isDone()) { + final float[] coords = new float[6]; + final int segType = pi.currentSegment(coords); + pi.next(); + if (segType != PathIterator.SEG_LINETO && + segType != PathIterator.SEG_MOVETO) { + continue; + } + final int x = Math.round(coords[0]); + final int y = Math.round(coords[1]); + final Shape shape = ShapeUtils.createTranslatedShape( + getItemShape(seriesIndex, i++), x, y); + + Paint paint; + if (useFillPaint) { + paint = lookupSeriesFillPaint(seriesIndex); + } + else { + paint = lookupSeriesPaint(seriesIndex); + } + g2.setPaint(paint); + g2.fill(shape); + if (isSeriesFilled(seriesIndex) && this.drawOutlineWhenFilled) { + g2.setPaint(lookupSeriesOutlinePaint(seriesIndex)); + g2.setStroke(lookupSeriesOutlineStroke(seriesIndex)); + g2.draw(shape); + } + + // add an entity for the item, but only if it falls within the + // data area... + if (entities != null && ShapeUtils.isPointInRect(dataArea, x, + y)) { + addEntity(entities, shape, dataset, seriesIndex, i-1, x, y); + } + } + } + } + + /** + * Draw the angular gridlines - the spokes. + * + * @param g2 the drawing surface. + * @param plot the plot ({@code null} not permitted). + * @param ticks the ticks ({@code null} not permitted). + * @param dataArea the data area. + */ + @Override + public void drawAngularGridLines(Graphics2D g2, PolarPlot plot, + List ticks, Rectangle2D dataArea) { + + g2.setFont(plot.getAngleLabelFont()); + g2.setStroke(plot.getAngleGridlineStroke()); + g2.setPaint(plot.getAngleGridlinePaint()); + + ValueAxis axis = plot.getAxis(); + double centerValue, outerValue; + if (axis.isInverted()) { + outerValue = axis.getLowerBound(); + centerValue = axis.getUpperBound(); + } else { + outerValue = axis.getUpperBound(); + centerValue = axis.getLowerBound(); + } + Point center = plot.translateToJava2D(0, centerValue, axis, dataArea); + Iterator iterator = ticks.iterator(); + while (iterator.hasNext()) { + NumberTick tick = (NumberTick) iterator.next(); + double tickVal = tick.getNumber().doubleValue(); + Point p = plot.translateToJava2D(tickVal, outerValue, axis, + dataArea); + g2.setPaint(plot.getAngleGridlinePaint()); + g2.drawLine(center.x, center.y, p.x, p.y); + if (plot.isAngleLabelsVisible()) { + int x = p.x; + int y = p.y; + g2.setPaint(plot.getAngleLabelPaint()); + TextUtils.drawAlignedString(tick.getText(), g2, x, y, + tick.getTextAnchor()); + } + } + } + + /** + * Draw the radial gridlines - the rings. + * + * @param g2 the drawing surface ({@code null} not permitted). + * @param plot the plot ({@code null} not permitted). + * @param radialAxis the radial axis ({@code null} not permitted). + * @param ticks the ticks ({@code null} not permitted). + * @param dataArea the data area. + */ + @Override + public void drawRadialGridLines(Graphics2D g2, PolarPlot plot, + ValueAxis radialAxis, List ticks, Rectangle2D dataArea) { + + Args.nullNotPermitted(radialAxis, "radialAxis"); + g2.setFont(radialAxis.getTickLabelFont()); + g2.setPaint(plot.getRadiusGridlinePaint()); + g2.setStroke(plot.getRadiusGridlineStroke()); + + double centerValue; + if (radialAxis.isInverted()) { + centerValue = radialAxis.getUpperBound(); + } else { + centerValue = radialAxis.getLowerBound(); + } + Point center = plot.translateToJava2D(0, centerValue, radialAxis, dataArea); + + Iterator iterator = ticks.iterator(); + while (iterator.hasNext()) { + NumberTick tick = (NumberTick) iterator.next(); + double angleDegrees = plot.isCounterClockwise() + ? plot.getAngleOffset() : -plot.getAngleOffset(); + Point p = plot.translateToJava2D(angleDegrees, + tick.getNumber().doubleValue(), radialAxis, dataArea); + int r = p.x - center.x; + int upperLeftX = center.x - r; + int upperLeftY = center.y - r; + int d = 2 * r; + Ellipse2D ring = new Ellipse2D.Double(upperLeftX, upperLeftY, d, d); + g2.setPaint(plot.getRadiusGridlinePaint()); + g2.draw(ring); + } + } + + /** + * Return the legend for the given series. + * + * @param series the series index. + * + * @return The legend item. + */ + @Override + public LegendItem getLegendItem(int series) { + LegendItem result; + PolarPlot p = getPlot(); + if (p == null) { + return null; + } + XYDataset dataset = p.getDataset(p.getIndexOf(this)); + if (dataset == null) { + return null; + } + + String toolTipText = null; + if (getLegendItemToolTipGenerator() != null) { + toolTipText = getLegendItemToolTipGenerator().generateLabel( + dataset, series); + } + String urlText = null; + if (getLegendItemURLGenerator() != null) { + urlText = getLegendItemURLGenerator().generateLabel(dataset, + series); + } + + Comparable seriesKey = dataset.getSeriesKey(series); + String label = seriesKey.toString(); + String description = label; + Shape shape = lookupSeriesShape(series); + Paint paint; + if (this.useFillPaint) { + paint = lookupSeriesFillPaint(series); + } + else { + paint = lookupSeriesPaint(series); + } + Stroke stroke = lookupSeriesStroke(series); + Paint outlinePaint = lookupSeriesOutlinePaint(series); + Stroke outlineStroke = lookupSeriesOutlineStroke(series); + boolean shapeOutlined = isSeriesFilled(series) + && this.drawOutlineWhenFilled; + result = new LegendItem(label, description, toolTipText, urlText, + getShapesVisible(), shape, /* shapeFilled=*/ true, paint, + shapeOutlined, outlinePaint, outlineStroke, + /* lineVisible= */ true, this.legendLine, stroke, paint); + result.setToolTipText(toolTipText); + result.setURLText(urlText); + result.setDataset(dataset); + result.setSeriesKey(seriesKey); + result.setSeriesIndex(series); + + return result; + } + + /** + * Returns the tooltip generator for the specified series and item. + * + * @param series the series index. + * @param item the item index. + * + * @return The tooltip generator (possibly {@code null}). + */ + @Override + public XYToolTipGenerator getToolTipGenerator(int series, int item) { + XYToolTipGenerator generator + = (XYToolTipGenerator) this.toolTipGeneratorList.get(series); + if (generator == null) { + generator = this.baseToolTipGenerator; + } + return generator; + } + + /** + * Returns the tool tip generator for the specified series. + * + * @return The tooltip generator (possibly {@code null}). + */ + @Override + public XYToolTipGenerator getSeriesToolTipGenerator(int series) { + return (XYToolTipGenerator) this.toolTipGeneratorList.get(series); + } + + /** + * Sets the tooltip generator for the specified series. + * + * @param series the series index. + * @param generator the tool tip generator ({@code null} permitted). + */ + @Override + public void setSeriesToolTipGenerator(int series, + XYToolTipGenerator generator) { + this.toolTipGeneratorList.set(series, generator); + fireChangeEvent(); + } + + /** + * Returns the default tool tip generator. + * + * @return The default tool tip generator (possibly {@code null}). + */ + @Override + public XYToolTipGenerator getBaseToolTipGenerator() { + return this.baseToolTipGenerator; + } + + /** + * Sets the default tool tip generator and calls {@link #fireChangeEvent()}. + * + * @param generator the generator ({@code null} permitted). + */ + @Override + public void setBaseToolTipGenerator(XYToolTipGenerator generator) { + this.baseToolTipGenerator = generator; + fireChangeEvent(); + } + + /** + * Returns the URL generator. + * + * @return The URL generator (possibly {@code null}). + */ + @Override + public XYURLGenerator getURLGenerator() { + return this.urlGenerator; + } + + /** + * Sets the URL generator. + * + * @param urlGenerator the generator ({@code null} permitted) + */ + @Override + public void setURLGenerator(XYURLGenerator urlGenerator) { + this.urlGenerator = urlGenerator; + fireChangeEvent(); + } + + /** + * Returns the legend item tool tip generator. + * + * @return The tool tip generator (possibly {@code null}). + * + * @see #setLegendItemToolTipGenerator(XYSeriesLabelGenerator) + */ + public XYSeriesLabelGenerator getLegendItemToolTipGenerator() { + return this.legendItemToolTipGenerator; + } + + /** + * Sets the legend item tool tip generator and calls {@link #fireChangeEvent()}. + * + * @param generator the generator ({@code null} permitted). + * + * @see #getLegendItemToolTipGenerator() + */ + public void setLegendItemToolTipGenerator( + XYSeriesLabelGenerator generator) { + this.legendItemToolTipGenerator = generator; + fireChangeEvent(); + } + + /** + * Returns the legend item URL generator. + * + * @return The URL generator (possibly {@code null}). + * + * @see #setLegendItemURLGenerator(XYSeriesLabelGenerator) + */ + public XYSeriesLabelGenerator getLegendItemURLGenerator() { + return this.legendItemURLGenerator; + } + + /** + * Sets the legend item URL generator and calls {@link #fireChangeEvent()}. + * + * @param generator the generator ({@code null} permitted). + * + * @see #getLegendItemURLGenerator() + */ + public void setLegendItemURLGenerator(XYSeriesLabelGenerator generator) { + this.legendItemURLGenerator = generator; + fireChangeEvent(); + } + + /** + * Tests this renderer for equality with an arbitrary object. + * + * @param obj the object ({@code null} not permitted). + * + * @return {@code true} if this renderer is equal to {@code obj}, + * and {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof DefaultPolarItemRenderer)) { + return false; + } + DefaultPolarItemRenderer that = (DefaultPolarItemRenderer) obj; + if (!this.seriesFilled.equals(that.seriesFilled)) { + return false; + } + if (this.drawOutlineWhenFilled != that.drawOutlineWhenFilled) { + return false; + } + if (!Objects.equals(this.fillComposite, that.fillComposite)) { + return false; + } + if (this.useFillPaint != that.useFillPaint) { + return false; + } + if (!ShapeUtils.equal(this.legendLine, that.legendLine)) { + return false; + } + if (this.shapesVisible != that.shapesVisible) { + return false; + } + if (this.connectFirstAndLastPoint != that.connectFirstAndLastPoint) { + return false; + } + if (!this.toolTipGeneratorList.equals(that.toolTipGeneratorList)) { + return false; + } + if (!Objects.equals(this.baseToolTipGenerator, + that.baseToolTipGenerator)) { + return false; + } + if (!Objects.equals(this.urlGenerator, that.urlGenerator)) { + return false; + } + if (!Objects.equals(this.legendItemToolTipGenerator, + that.legendItemToolTipGenerator)) { + return false; + } + if (!Objects.equals(this.legendItemURLGenerator, + that.legendItemURLGenerator)) { + return false; + } + return super.equals(obj); + } + + /** + * Returns a clone of the renderer. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the renderer cannot be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + DefaultPolarItemRenderer clone + = (DefaultPolarItemRenderer) super.clone(); + if (this.legendLine != null) { + clone.legendLine = ShapeUtils.clone(this.legendLine); + } + clone.seriesFilled = (BooleanList) this.seriesFilled.clone(); + clone.toolTipGeneratorList + = (ObjectList) this.toolTipGeneratorList.clone(); + if (clone.baseToolTipGenerator instanceof PublicCloneable) { + clone.baseToolTipGenerator = (XYToolTipGenerator) + ObjectUtils.clone(this.baseToolTipGenerator); + } + if (clone.urlGenerator instanceof PublicCloneable) { + clone.urlGenerator = (XYURLGenerator) + ObjectUtils.clone(this.urlGenerator); + } + if (clone.legendItemToolTipGenerator instanceof PublicCloneable) { + clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator) + ObjectUtils.clone(this.legendItemToolTipGenerator); + } + if (clone.legendItemURLGenerator instanceof PublicCloneable) { + clone.legendItemURLGenerator = (XYSeriesLabelGenerator) + ObjectUtils.clone(this.legendItemURLGenerator); + } + return clone; + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + this.legendLine = SerialUtils.readShape(stream); + this.fillComposite = SerialUtils.readComposite(stream); + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writeShape(this.legendLine, stream); + SerialUtils.writeComposite(this.fillComposite, stream); + } +} diff --git a/src/main/java/org/jfree/chart/renderer/PaintScale.java b/src/main/java/org/jfree/chart/renderer/PaintScale.java index 093a8babf..4d7d61b3f 100644 --- a/src/main/java/org/jfree/chart/renderer/PaintScale.java +++ b/src/main/java/org/jfree/chart/renderer/PaintScale.java @@ -38,11 +38,9 @@ import java.awt.Paint; -import org.jfree.chart.renderer.xy.XYBlockRenderer; - /** * A source for {@code Paint} instances, used by the - * {@link XYBlockRenderer}. + * {@link org.jfree.chart.renderer.xy.XYBlockRenderer}. *

* NOTE: Classes that implement this interface should also implement * {@code PublicCloneable} and {@code Serializable}, so diff --git a/src/main/java/org/jfree/chart/renderer/PolarItemRenderer.java b/src/main/java/org/jfree/chart/renderer/PolarItemRenderer.java index 76b7858a3..9c782ab55 100644 --- a/src/main/java/org/jfree/chart/renderer/PolarItemRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/PolarItemRenderer.java @@ -1,206 +1,203 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ---------------------- - * PolarItemRenderer.java - * ---------------------- - * (C) Copyright 2004-present, by Solution Engineering, Inc. and Contributors. - * - * Original Author: Daniel Bridenbecker, Solution Engineering, Inc.; - * Contributor(s): David Gilbert; - * - */ - -package org.jfree.chart.renderer; - -import java.awt.Graphics2D; -import java.awt.geom.Rectangle2D; -import java.util.List; - -import org.jfree.chart.LegendItem; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.event.RendererChangeListener; -import org.jfree.chart.labels.XYToolTipGenerator; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.PolarPlot; -import org.jfree.chart.urls.XYURLGenerator; -import org.jfree.data.xy.XYDataset; - -/** - * The interface for a renderer that can be used by the {@link PolarPlot} - * class. - */ -public interface PolarItemRenderer { - - /** - * Plots the data for a given series. - * - * @param g2 the drawing surface. - * @param dataArea the data area. - * @param info collects plot rendering info. - * @param plot the plot. - * @param dataset the dataset. - * @param seriesIndex the series index. - */ - void drawSeries(Graphics2D g2, Rectangle2D dataArea, - PlotRenderingInfo info, PolarPlot plot, XYDataset dataset, - int seriesIndex); - - /** - * Draw the angular gridlines - the spokes. - * - * @param g2 the drawing surface. - * @param plot the plot. - * @param ticks the ticks. - * @param dataArea the data area. - */ - void drawAngularGridLines(Graphics2D g2, PolarPlot plot, - List ticks, Rectangle2D dataArea); - - /** - * Draw the radial gridlines - the rings. - * - * @param g2 the drawing surface. - * @param plot the plot. - * @param radialAxis the radial axis. - * @param ticks the ticks. - * @param dataArea the data area. - */ - void drawRadialGridLines(Graphics2D g2, PolarPlot plot, - ValueAxis radialAxis, List ticks, Rectangle2D dataArea); - - /** - * Return the legend for the given series. - * - * @param series the series index. - * - * @return The legend item. - */ - LegendItem getLegendItem(int series); - - /** - * Returns the plot that this renderer has been assigned to. - * - * @return The plot. - */ - PolarPlot getPlot(); - - /** - * Sets the plot that this renderer is assigned to. This method will be - * called by the plot class...you do not need to call it yourself. - * - * @param plot the plot. - */ - void setPlot(PolarPlot plot); - - /** - * Adds a change listener. - * - * @param listener the listener. - */ - void addChangeListener(RendererChangeListener listener); - - /** - * Removes a change listener. - * - * @param listener the listener. - */ - void removeChangeListener(RendererChangeListener listener); - - - //// TOOL TIP GENERATOR /////////////////////////////////////////////////// - - /** - * Returns the tool tip generator for a data item. - * - * @param row the row index (zero based). - * @param column the column index (zero based). - * - * @return The generator (possibly {@code null}). - */ - XYToolTipGenerator getToolTipGenerator(int row, int column); - - /** - * Returns the tool tip generator for a series. - * - * @param series the series index (zero based). - * - * @return The generator (possibly {@code null}). - * - * @see #setSeriesToolTipGenerator(int, XYToolTipGenerator) - */ - XYToolTipGenerator getSeriesToolTipGenerator(int series); - - /** - * Sets the tool tip generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero based). - * @param generator the generator ({@code null} permitted). - * - * @see #getSeriesToolTipGenerator(int) - */ - void setSeriesToolTipGenerator(int series, - XYToolTipGenerator generator); - - /** - * Returns the base tool tip generator. - * - * @return The generator (possibly {@code null}). - * - * @see #setBaseToolTipGenerator(XYToolTipGenerator) - */ - XYToolTipGenerator getBaseToolTipGenerator(); - - /** - * Sets the base tool tip generator and sends a {@link RendererChangeEvent} - * to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - * - * @see #getBaseToolTipGenerator() - */ - void setBaseToolTipGenerator(XYToolTipGenerator generator); - - - //// URL GENERATOR //////////////////////////////////////////////////////// - - /** - * Returns the URL generator for HTML image maps. - * - * @return The URL generator (possibly null). - */ - XYURLGenerator getURLGenerator(); - - /** - * Sets the URL generator for HTML image maps. - * - * @param urlGenerator the URL generator (null permitted). - */ - void setURLGenerator(XYURLGenerator urlGenerator); - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ---------------------- + * PolarItemRenderer.java + * ---------------------- + * (C) Copyright 2004-present, by Solution Engineering, Inc. and Contributors. + * + * Original Author: Daniel Bridenbecker, Solution Engineering, Inc.; + * Contributor(s): David Gilbert; + * + */ + +package org.jfree.chart.renderer; + +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; +import java.util.List; + +import org.jfree.chart.LegendItem; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.event.RendererChangeListener; +import org.jfree.chart.labels.XYToolTipGenerator; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.PolarPlot; +import org.jfree.chart.urls.XYURLGenerator; +import org.jfree.data.xy.XYDataset; + +/** + * The interface for a renderer that can be used by the {@link PolarPlot} + * class. + */ +public interface PolarItemRenderer { + + /** + * Plots the data for a given series. + * + * @param g2 the drawing surface. + * @param dataArea the data area. + * @param info collects plot rendering info. + * @param plot the plot. + * @param dataset the dataset. + * @param seriesIndex the series index. + */ + void drawSeries(Graphics2D g2, Rectangle2D dataArea, + PlotRenderingInfo info, PolarPlot plot, XYDataset dataset, + int seriesIndex); + + /** + * Draw the angular gridlines - the spokes. + * + * @param g2 the drawing surface. + * @param plot the plot. + * @param ticks the ticks. + * @param dataArea the data area. + */ + void drawAngularGridLines(Graphics2D g2, PolarPlot plot, + List ticks, Rectangle2D dataArea); + + /** + * Draw the radial gridlines - the rings. + * + * @param g2 the drawing surface. + * @param plot the plot. + * @param radialAxis the radial axis. + * @param ticks the ticks. + * @param dataArea the data area. + */ + void drawRadialGridLines(Graphics2D g2, PolarPlot plot, + ValueAxis radialAxis, List ticks, Rectangle2D dataArea); + + /** + * Return the legend for the given series. + * + * @param series the series index. + * + * @return The legend item. + */ + LegendItem getLegendItem(int series); + + /** + * Returns the plot that this renderer has been assigned to. + * + * @return The plot. + */ + PolarPlot getPlot(); + + /** + * Sets the plot that this renderer is assigned to. This method will be + * called by the plot class...you do not need to call it yourself. + * + * @param plot the plot. + */ + void setPlot(PolarPlot plot); + + /** + * Adds a change listener. + * + * @param listener the listener. + */ + void addChangeListener(RendererChangeListener listener); + + /** + * Removes a change listener. + * + * @param listener the listener. + */ + void removeChangeListener(RendererChangeListener listener); + + + //// TOOL TIP GENERATOR /////////////////////////////////////////////////// + + /** + * Returns the tool tip generator for a data item. + * + * @param row the row index (zero based). + * @param column the column index (zero based). + * + * @return The generator (possibly {@code null}). + */ + XYToolTipGenerator getToolTipGenerator(int row, int column); + + /** + * Returns the tool tip generator for a series. + * + * @param series the series index (zero based). + * + * @return The generator (possibly {@code null}). + * + * @see #setSeriesToolTipGenerator(int, XYToolTipGenerator) + */ + XYToolTipGenerator getSeriesToolTipGenerator(int series); + + /** + * Sets the tool tip generator for a series and notifies all registered listeners. + * + * @param series the series index (zero based). + * @param generator the generator ({@code null} permitted). + * + * @see #getSeriesToolTipGenerator(int) + */ + void setSeriesToolTipGenerator(int series, + XYToolTipGenerator generator); + + /** + * Returns the base tool tip generator. + * + * @return The generator (possibly {@code null}). + * + * @see #setBaseToolTipGenerator(XYToolTipGenerator) + */ + XYToolTipGenerator getBaseToolTipGenerator(); + + /** + * Sets the base tool tip generator and notifies all registered listeners. + * + * @param generator the generator ({@code null} permitted). + * + * @see #getBaseToolTipGenerator() + */ + void setBaseToolTipGenerator(XYToolTipGenerator generator); + + + //// URL GENERATOR //////////////////////////////////////////////////////// + + /** + * Returns the URL generator for HTML image maps. + * + * @return The URL generator (possibly null). + */ + XYURLGenerator getURLGenerator(); + + /** + * Sets the URL generator for HTML image maps. + * + * @param urlGenerator the URL generator (null permitted). + */ + void setURLGenerator(XYURLGenerator urlGenerator); + +} diff --git a/src/main/java/org/jfree/chart/renderer/category/AbstractCategoryItemRenderer.java b/src/main/java/org/jfree/chart/renderer/category/AbstractCategoryItemRenderer.java index 434e1b671..07270b859 100644 --- a/src/main/java/org/jfree/chart/renderer/category/AbstractCategoryItemRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/AbstractCategoryItemRenderer.java @@ -63,7 +63,6 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.CategoryItemEntity; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.CategoryItemLabelGenerator; import org.jfree.chart.labels.CategorySeriesLabelGenerator; import org.jfree.chart.labels.CategoryToolTipGenerator; @@ -242,8 +241,8 @@ public CategoryItemLabelGenerator getSeriesItemLabelGenerator(int series) { } /** - * Sets the item label generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label generator for a series by calling + * {@link #setSeriesItemLabelGenerator(int, CategoryItemLabelGenerator, boolean)} * * @param series the series index (zero based). * @param generator the generator ({@code null} permitted). @@ -257,8 +256,8 @@ public void setSeriesItemLabelGenerator(int series, } /** - * Sets the item label generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label generator for a series and, if requested, calls + * {@link #fireChangeEvent()}. * * @param series the series index (zero based). * @param generator the generator ({@code null} permitted). @@ -288,8 +287,8 @@ public CategoryItemLabelGenerator getDefaultItemLabelGenerator() { } /** - * Sets the default item label generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item label generator by calling + * {@link #setDefaultItemLabelGenerator(CategoryItemLabelGenerator, boolean)}. * * @param generator the generator ({@code null} permitted). * @@ -302,8 +301,8 @@ public void setDefaultItemLabelGenerator( } /** - * Sets the default item label generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item label generator and, if requested, calls + * {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} permitted). * @param notify notify listeners? @@ -359,8 +358,8 @@ public CategoryToolTipGenerator getSeriesToolTipGenerator(int series) { } /** - * Sets the tool tip generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the tool tip generator for a series by calling + * {@link #setSeriesToolTipGenerator(int, CategoryToolTipGenerator, boolean)}. * * @param series the series index (zero-based). * @param generator the generator ({@code null} permitted). @@ -374,8 +373,8 @@ public void setSeriesToolTipGenerator(int series, } /** - * Sets the tool tip generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the tool tip generator for a series and, if requested, calls + * {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param generator the generator ({@code null} permitted). @@ -405,8 +404,8 @@ public CategoryToolTipGenerator getDefaultToolTipGenerator() { } /** - * Sets the default tool tip generator and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the default tool tip generator by calling + * {@link #setDefaultToolTipGenerator(org.jfree.chart.labels.CategoryToolTipGenerator, boolean) }. * * @param generator the generator ({@code null} permitted). * @@ -418,8 +417,8 @@ public void setDefaultToolTipGenerator(CategoryToolTipGenerator generator) { } /** - * Sets the default tool tip generator and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the default tool tip generator and, if requested, calls + * {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} permitted). * @param notify notify listeners? @@ -427,7 +426,8 @@ public void setDefaultToolTipGenerator(CategoryToolTipGenerator generator) { * @see #getDefaultToolTipGenerator() */ @Override - public void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, boolean notify) { + public void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, + boolean notify) { this.defaultToolTipGenerator = generator; if (notify) { fireChangeEvent(); @@ -471,8 +471,8 @@ public CategoryURLGenerator getSeriesItemURLGenerator(int series) { } /** - * Sets the URL generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the URL generator for a series by calling + * {@link #setSeriesItemURLGenerator(int, org.jfree.chart.urls.CategoryURLGenerator, boolean)} * * @param series the series index (zero based). * @param generator the generator. @@ -486,8 +486,8 @@ public void setSeriesItemURLGenerator(int series, } /** - * Sets the URL generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the URL generator for a series and, if requested, calls + * {@link #fireChangeEvent()}. * * @param series the series index (zero based). * @param generator the generator. @@ -517,8 +517,8 @@ public CategoryURLGenerator getDefaultItemURLGenerator() { } /** - * Sets the default item URL generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item URL generator by calling + * {@link #setDefaultItemURLGenerator(org.jfree.chart.urls.CategoryURLGenerator, boolean)}. * * @param generator the item URL generator ({@code null} permitted). * @@ -530,8 +530,8 @@ public void setDefaultItemURLGenerator(CategoryURLGenerator generator) { } /** - * Sets the default item URL generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item URL generator and, if requested, calls + * {@link #fireChangeEvent()}. * * @param generator the item URL generator ({@code null} permitted). * @param notify notify listeners? @@ -1534,8 +1534,7 @@ public CategorySeriesLabelGenerator getLegendItemLabelGenerator() { } /** - * Sets the legend item label generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the legend item label generator and calls {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} not permitted). * @@ -1560,8 +1559,7 @@ public CategorySeriesLabelGenerator getLegendItemToolTipGenerator() { } /** - * Sets the legend item tool tip generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the legend item tool tip generator and calls {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} permitted). * @@ -1585,8 +1583,7 @@ public CategorySeriesLabelGenerator getLegendItemURLGenerator() { } /** - * Sets the legend item URL generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the legend item URL generator and calls {@link #fireChangeEvent()}. * * @param generator the generator ({@code null} permitted). * diff --git a/src/main/java/org/jfree/chart/renderer/category/AreaRenderer.java b/src/main/java/org/jfree/chart/renderer/category/AreaRenderer.java index abb09348b..98965f98a 100644 --- a/src/main/java/org/jfree/chart/renderer/category/AreaRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/AreaRenderer.java @@ -49,7 +49,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.renderer.AreaRendererEndType; @@ -98,7 +97,7 @@ public AreaRendererEndType getEndType() { /** * Sets a token that controls how the renderer draws the end points, and - * sends a {@link RendererChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param type the end type ({@code null} not permitted). * diff --git a/src/main/java/org/jfree/chart/renderer/category/BarRenderer.java b/src/main/java/org/jfree/chart/renderer/category/BarRenderer.java index a7e12207b..cc19b9716 100644 --- a/src/main/java/org/jfree/chart/renderer/category/BarRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/BarRenderer.java @@ -57,7 +57,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.CategoryItemLabelGenerator; import org.jfree.chart.labels.ItemLabelAnchor; import org.jfree.chart.labels.ItemLabelPosition; @@ -257,8 +256,7 @@ public double getBase() { } /** - * Sets the base value for the bars and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the base value for the bars and calls {@link #fireChangeEvent()}. * * @param base the new base value. * @@ -282,8 +280,8 @@ public double getItemMargin() { } /** - * Sets the item margin and sends a {@link RendererChangeEvent} to all - * registered listeners. The value is expressed as a percentage of the + * Sets the item margin and calls {@link #fireChangeEvent()}. + * The value is expressed as a percentage of the * available width for plotting all the bars, with the resulting amount to * be distributed between all the bars evenly. * @@ -309,7 +307,7 @@ public boolean isDrawBarOutline() { /** * Sets the flag that controls whether or not bar outlines are drawn and - * sends a {@link RendererChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param draw the flag. * @@ -334,8 +332,7 @@ public double getMaximumBarWidth() { /** * Sets the maximum bar width, which is specified as a percentage of the - * available space for all bars, and sends a {@link RendererChangeEvent} to - * all registered listeners. + * available space for all bars, and calls {@link #fireChangeEvent()}. * * @param percent the percent (where 0.05 is five percent). * @@ -359,8 +356,8 @@ public double getMinimumBarLength() { } /** - * Sets the minimum bar length and sends a {@link RendererChangeEvent} to - * all registered listeners. The minimum bar length is specified in Java2D + * Sets the minimum bar length and calls {@link #fireChangeEvent()}. + * The minimum bar length is specified in Java2D * units, and can be used to prevent bars that represent very small data * values from disappearing when drawn on the screen. Typically you would * set this to (say) 0.5 or 1.0 Java 2D units. Use this attribute with @@ -393,8 +390,7 @@ public GradientPaintTransformer getGradientPaintTransformer() { } /** - * Sets the gradient paint transformer and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the gradient paint transformer and calls {@link #fireChangeEvent()}. * * @param transformer the transformer ({@code null} permitted). * @@ -420,8 +416,7 @@ public ItemLabelPosition getPositiveItemLabelPositionFallback() { /** * Sets the fallback position for positive item labels that don't fit - * within a bar, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * within a bar, and calls {@link #fireChangeEvent()}. * * @param position the position ({@code null} permitted). * @@ -447,8 +442,7 @@ public ItemLabelPosition getNegativeItemLabelPositionFallback() { /** * Sets the fallback position for negative item labels that don't fit - * within a bar, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * within a bar, and calls {@link #fireChangeEvent()}. * * @param position the position ({@code null} permitted). * @@ -478,7 +472,7 @@ public boolean getIncludeBaseInRange() { * Sets the flag that controls whether or not the base value for the bars * is included in the range calculated by * {@link #findRangeBounds(CategoryDataset)}. If the flag is changed, - * a {@link RendererChangeEvent} is sent to all registered listeners. + * {@link #fireChangeEvent()} is called. * * @param include the new value for the flag. * @@ -503,8 +497,7 @@ public BarPainter getBarPainter() { } /** - * Sets the bar painter for this renderer and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the bar painter for this renderer and calls {@link #fireChangeEvent()}. * * @param painter the painter ({@code null} not permitted). * @@ -549,8 +542,7 @@ public Paint getShadowPaint() { } /** - * Sets the shadow paint and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the shadow paint and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -572,8 +564,7 @@ public double getShadowXOffset() { } /** - * Sets the x-offset for the bar shadow and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the x-offset for the bar shadow and calls {@link #fireChangeEvent()}. * * @param offset the offset. */ @@ -592,8 +583,7 @@ public double getShadowYOffset() { } /** - * Sets the y-offset for the bar shadow and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the y-offset for the bar shadow and calls {@link #fireChangeEvent()}. * * @param offset the offset. */ diff --git a/src/main/java/org/jfree/chart/renderer/category/BoxAndWhiskerRenderer.java b/src/main/java/org/jfree/chart/renderer/category/BoxAndWhiskerRenderer.java index 86903c259..79833c42f 100644 --- a/src/main/java/org/jfree/chart/renderer/category/BoxAndWhiskerRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/BoxAndWhiskerRenderer.java @@ -1,1119 +1,1111 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------------------- - * BoxAndWhiskerRenderer.java - * -------------------------- - * (C) Copyright 2003-present, by David Browning and Contributors. - * - * Original Author: David Browning (for the Australian Institute of Marine - * Science); - * Contributor(s): David Gilbert; - * Tim Bardzil; - * Rob Van der Sanden (patches 1866446 and 1888422); - * Peter Becker (patches 2868585 and 2868608); - * Martin Krauskopf (patch 3421088); - * Martin Hoeller; - * John Matthews; - * - */ - -package org.jfree.chart.renderer.category; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Ellipse2D; -import java.awt.geom.Line2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import org.jfree.chart.LegendItem; -import org.jfree.chart.axis.CategoryAxis; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.plot.CategoryPlot; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.renderer.Outlier; -import org.jfree.chart.renderer.OutlierList; -import org.jfree.chart.renderer.OutlierListCollection; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.util.PaintUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.SerialUtils; -import org.jfree.data.Range; -import org.jfree.data.category.CategoryDataset; -import org.jfree.data.statistics.BoxAndWhiskerCategoryDataset; - -/** - * A box-and-whisker renderer. This renderer requires a - * {@link BoxAndWhiskerCategoryDataset} and is for use with the - * {@link CategoryPlot} class. The example shown here is generated - * by the {@code BoxAndWhiskerChartDemo1.java} program included in the - * JFreeChart Demo Collection: - *

- * BoxAndWhiskerRendererSample.png - */ -public class BoxAndWhiskerRenderer extends AbstractCategoryItemRenderer - implements Cloneable, PublicCloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = 632027470694481177L; - - /** The color used to paint the median line and average marker. */ - private transient Paint artifactPaint; - - /** A flag that controls whether or not the box is filled. */ - private boolean fillBox; - - /** The margin between items (boxes) within a category. */ - private double itemMargin; - - /** - * The maximum bar width as percentage of the available space in the plot. - * Take care with the encoding - for example, 0.05 is five percent. - */ - private double maximumBarWidth; - - /** - * A flag that controls whether or not the median indicator is drawn. - */ - private boolean medianVisible; - - /** - * A flag that controls whether or not the mean indicator is drawn. - */ - private boolean meanVisible; - - /** - * A flag that controls whether or not the maxOutlier is visible. - */ - private boolean maxOutlierVisible; - - /** - * A flag that controls whether or not the minOutlier is visible. - */ - private boolean minOutlierVisible; - - /** - * A flag that, if {@code true}, causes the whiskers to be drawn - * using the outline paint for the series. The default value is - * {@code false} and in that case the regular series paint is used. - */ - private boolean useOutlinePaintForWhiskers; - - /** - * The width of the whiskers as fraction of the bar width. - */ - private double whiskerWidth; - - /** - * Default constructor. - */ - public BoxAndWhiskerRenderer() { - this.artifactPaint = Color.BLACK; - this.fillBox = true; - this.itemMargin = 0.20; - this.maximumBarWidth = 1.0; - this.medianVisible = true; - this.meanVisible = true; - this.minOutlierVisible = true; - this.maxOutlierVisible = true; - this.useOutlinePaintForWhiskers = false; - this.whiskerWidth = 1.0; - setDefaultLegendShape(new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0)); - } - - /** - * Returns the paint used to color the median and average markers. - * - * @return The paint used to draw the median and average markers (never - * {@code null}). - * - * @see #setArtifactPaint(Paint) - */ - public Paint getArtifactPaint() { - return this.artifactPaint; - } - - /** - * Sets the paint used to color the median and average markers and sends - * a {@link RendererChangeEvent} to all registered listeners. - * - * @param paint the paint ({@code null} not permitted). - * - * @see #getArtifactPaint() - */ - public void setArtifactPaint(Paint paint) { - Args.nullNotPermitted(paint, "paint"); - this.artifactPaint = paint; - fireChangeEvent(); - } - - /** - * Returns the flag that controls whether or not the box is filled. - * - * @return A boolean. - * - * @see #setFillBox(boolean) - */ - public boolean getFillBox() { - return this.fillBox; - } - - /** - * Sets the flag that controls whether or not the box is filled and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param flag the flag. - * - * @see #getFillBox() - */ - public void setFillBox(boolean flag) { - this.fillBox = flag; - fireChangeEvent(); - } - - /** - * Returns the item margin. This is a percentage of the available space - * that is allocated to the space between items in the chart. - * - * @return The margin. - * - * @see #setItemMargin(double) - */ - public double getItemMargin() { - return this.itemMargin; - } - - /** - * Sets the item margin and sends a {@link RendererChangeEvent} to all - * registered listeners. - * - * @param margin the margin (a percentage). - * - * @see #getItemMargin() - */ - public void setItemMargin(double margin) { - this.itemMargin = margin; - fireChangeEvent(); - } - - /** - * Returns the maximum bar width as a percentage of the available drawing - * space. Take care with the encoding, for example 0.10 is ten percent. - * - * @return The maximum bar width. - * - * @see #setMaximumBarWidth(double) - */ - public double getMaximumBarWidth() { - return this.maximumBarWidth; - } - - /** - * Sets the maximum bar width, which is specified as a percentage of the - * available space for all bars, and sends a {@link RendererChangeEvent} - * to all registered listeners. - * - * @param percent the maximum bar width (a percentage, where 0.10 is ten - * percent). - * - * @see #getMaximumBarWidth() - */ - public void setMaximumBarWidth(double percent) { - this.maximumBarWidth = percent; - fireChangeEvent(); - } - - /** - * Returns the flag that controls whether or not the mean indicator is - * draw for each item. - * - * @return A boolean. - * - * @see #setMeanVisible(boolean) - */ - public boolean isMeanVisible() { - return this.meanVisible; - } - - /** - * Sets the flag that controls whether or not the mean indicator is drawn - * for each item, and sends a {@link RendererChangeEvent} to all - * registered listeners. - * - * @param visible the new flag value. - * - * @see #isMeanVisible() - */ - public void setMeanVisible(boolean visible) { - if (this.meanVisible == visible) { - return; - } - this.meanVisible = visible; - fireChangeEvent(); - } - - /** - * Returns the flag that controls whether or not the median indicator is - * draw for each item. - * - * @return A boolean. - * - * @see #setMedianVisible(boolean) - */ - public boolean isMedianVisible() { - return this.medianVisible; - } - - /** - * Sets the flag that controls whether or not the median indicator is drawn - * for each item, and sends a {@link RendererChangeEvent} to all - * registered listeners. - * - * @param visible the new flag value. - * - * @see #isMedianVisible() - */ - public void setMedianVisible(boolean visible) { - if (this.medianVisible == visible) { - return; - } - this.medianVisible = visible; - fireChangeEvent(); - } - - /** - * Returns the flag that controls whether or not the minimum outlier is - * draw for each item. - * - * @return A boolean. - * - * @see #setMinOutlierVisible(boolean) - * - * @since 1.5.2 - */ - public boolean isMinOutlierVisible() { - return this.minOutlierVisible; - } - - /** - * Sets the flag that controls whether or not the minimum outlier is drawn - * for each item, and sends a {@link RendererChangeEvent} to all - * registered listeners. - * - * @param visible the new flag value. - * - * @see #isMinOutlierVisible() - * - * @since 1.5.2 - */ - public void setMinOutlierVisible(boolean visible) { - if (this.minOutlierVisible == visible) { - return; - } - this.minOutlierVisible = visible; - fireChangeEvent(); - } - - /** - * Returns the flag that controls whether or not the maximum outlier is - * draw for each item. - * - * @return A boolean. - * - * @see #setMaxOutlierVisible(boolean) - * - * @since 1.5.2 - */ - public boolean isMaxOutlierVisible() { - return this.maxOutlierVisible; - } - - /** - * Sets the flag that controls whether or not the maximum outlier is drawn - * for each item, and sends a {@link RendererChangeEvent} to all - * registered listeners. - * - * @param visible the new flag value. - * - * @see #isMaxOutlierVisible() - * - * @since 1.5.2 - */ - public void setMaxOutlierVisible(boolean visible) { - if (this.maxOutlierVisible == visible) { - return; - } - this.maxOutlierVisible = visible; - fireChangeEvent(); - } - - /** - * Returns the flag that, if {@code true}, causes the whiskers to - * be drawn using the series outline paint. - * - * @return A boolean. - */ - public boolean getUseOutlinePaintForWhiskers() { - return this.useOutlinePaintForWhiskers; - } - - /** - * Sets the flag that, if {@code true}, causes the whiskers to - * be drawn using the series outline paint, and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param flag the new flag value. - */ - public void setUseOutlinePaintForWhiskers(boolean flag) { - if (this.useOutlinePaintForWhiskers == flag) { - return; - } - this.useOutlinePaintForWhiskers = flag; - fireChangeEvent(); - } - - /** - * Returns the width of the whiskers as fraction of the bar width. - * - * @return The width of the whiskers. - * - * @see #setWhiskerWidth(double) - */ - public double getWhiskerWidth() { - return this.whiskerWidth; - } - - /** - * Sets the width of the whiskers as a fraction of the bar width and sends - * a {@link RendererChangeEvent} to all registered listeners. - * - * @param width a value between 0 and 1 indicating how wide the - * whisker is supposed to be compared to the bar. - * @see #getWhiskerWidth() - * @see CategoryItemRendererState#getBarWidth() - */ - public void setWhiskerWidth(double width) { - if (width < 0 || width > 1) { - throw new IllegalArgumentException( - "Value for whisker width out of range"); - } - if (width == this.whiskerWidth) { - return; - } - this.whiskerWidth = width; - fireChangeEvent(); - } - - /** - * Returns a legend item for a series. - * - * @param datasetIndex the dataset index (zero-based). - * @param series the series index (zero-based). - * - * @return The legend item (possibly {@code null}). - */ - @Override - public LegendItem getLegendItem(int datasetIndex, int series) { - - CategoryPlot cp = getPlot(); - if (cp == null) { - return null; - } - - // check that a legend item needs to be displayed... - if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) { - return null; - } - - CategoryDataset dataset = cp.getDataset(datasetIndex); - String label = getLegendItemLabelGenerator().generateLabel(dataset, - series); - String description = label; - String toolTipText = null; - if (getLegendItemToolTipGenerator() != null) { - toolTipText = getLegendItemToolTipGenerator().generateLabel( - dataset, series); - } - String urlText = null; - if (getLegendItemURLGenerator() != null) { - urlText = getLegendItemURLGenerator().generateLabel(dataset, - series); - } - Shape shape = lookupLegendShape(series); - Paint paint = lookupSeriesPaint(series); - Paint outlinePaint = lookupSeriesOutlinePaint(series); - Stroke outlineStroke = lookupSeriesOutlineStroke(series); - LegendItem result = new LegendItem(label, description, toolTipText, - urlText, shape, paint, outlineStroke, outlinePaint); - result.setLabelFont(lookupLegendTextFont(series)); - Paint labelPaint = lookupLegendTextPaint(series); - if (labelPaint != null) { - result.setLabelPaint(labelPaint); - } - result.setDataset(dataset); - result.setDatasetIndex(datasetIndex); - result.setSeriesKey(dataset.getRowKey(series)); - result.setSeriesIndex(series); - return result; - - } - - /** - * Returns the range of values from the specified dataset that the - * renderer will require to display all the data. - * - * @param dataset the dataset. - * - * @return The range. - */ - @Override - public Range findRangeBounds(CategoryDataset dataset) { - return super.findRangeBounds(dataset, true); - } - - /** - * Initialises the renderer. This method gets called once at the start of - * the process of drawing a chart. - * - * @param g2 the graphics device. - * @param dataArea the area in which the data is to be plotted. - * @param plot the plot. - * @param rendererIndex the renderer index. - * @param info collects chart rendering information for return to caller. - * - * @return The renderer state. - */ - @Override - public CategoryItemRendererState initialise(Graphics2D g2, - Rectangle2D dataArea, CategoryPlot plot, int rendererIndex, - PlotRenderingInfo info) { - - CategoryItemRendererState state = super.initialise(g2, dataArea, plot, - rendererIndex, info); - // calculate the box width - CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex); - CategoryDataset dataset = plot.getDataset(rendererIndex); - if (dataset != null) { - int columns = dataset.getColumnCount(); - int rows = dataset.getRowCount(); - double space = 0.0; - PlotOrientation orientation = plot.getOrientation(); - if (orientation == PlotOrientation.HORIZONTAL) { - space = dataArea.getHeight(); - } - else if (orientation == PlotOrientation.VERTICAL) { - space = dataArea.getWidth(); - } - double maxWidth = space * getMaximumBarWidth(); - double categoryMargin = 0.0; - double currentItemMargin = 0.0; - if (columns > 1) { - categoryMargin = domainAxis.getCategoryMargin(); - } - if (rows > 1) { - currentItemMargin = getItemMargin(); - } - double used = space * (1 - domainAxis.getLowerMargin() - - domainAxis.getUpperMargin() - - categoryMargin - currentItemMargin); - if ((rows * columns) > 0) { - state.setBarWidth(Math.min(used / (dataset.getColumnCount() - * dataset.getRowCount()), maxWidth)); - } else { - state.setBarWidth(Math.min(used, maxWidth)); - } - } - return state; - - } - - /** - * Draw a single data item. - * - * @param g2 the graphics device. - * @param state the renderer state. - * @param dataArea the area in which the data is drawn. - * @param plot the plot. - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - * @param dataset the data (must be an instance of - * {@link BoxAndWhiskerCategoryDataset}). - * @param row the row index (zero-based). - * @param column the column index (zero-based). - * @param pass the pass index. - */ - @Override - public void drawItem(Graphics2D g2, CategoryItemRendererState state, - Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, - ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, - int pass) { - - // do nothing if item is not visible - if (!getItemVisible(row, column)) { - return; - } - - if (!(dataset instanceof BoxAndWhiskerCategoryDataset)) { - throw new IllegalArgumentException( - "BoxAndWhiskerRenderer.drawItem() : the data should be " - + "of type BoxAndWhiskerCategoryDataset only."); - } - - PlotOrientation orientation = plot.getOrientation(); - - if (orientation == PlotOrientation.HORIZONTAL) { - drawHorizontalItem(g2, state, dataArea, plot, domainAxis, - rangeAxis, dataset, row, column); - } else if (orientation == PlotOrientation.VERTICAL) { - drawVerticalItem(g2, state, dataArea, plot, domainAxis, - rangeAxis, dataset, row, column); - } - - } - - /** - * Draws the visual representation of a single data item when the plot has - * a horizontal orientation. - * - * @param g2 the graphics device. - * @param state the renderer state. - * @param dataArea the area within which the plot is being drawn. - * @param plot the plot (can be used to obtain standard color - * information etc). - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - * @param dataset the dataset (must be an instance of - * {@link BoxAndWhiskerCategoryDataset}). - * @param row the row index (zero-based). - * @param column the column index (zero-based). - */ - public void drawHorizontalItem(Graphics2D g2, - CategoryItemRendererState state, Rectangle2D dataArea, - CategoryPlot plot, CategoryAxis domainAxis, ValueAxis rangeAxis, - CategoryDataset dataset, int row, int column) { - - BoxAndWhiskerCategoryDataset bawDataset - = (BoxAndWhiskerCategoryDataset) dataset; - - double categoryEnd = domainAxis.getCategoryEnd(column, - getColumnCount(), dataArea, plot.getDomainAxisEdge()); - double categoryStart = domainAxis.getCategoryStart(column, - getColumnCount(), dataArea, plot.getDomainAxisEdge()); - double categoryWidth = Math.abs(categoryEnd - categoryStart); - - double yy = categoryStart; - int seriesCount = getRowCount(); - int categoryCount = getColumnCount(); - - if (seriesCount > 1) { - double seriesGap = dataArea.getHeight() * getItemMargin() - / (categoryCount * (seriesCount - 1)); - double usedWidth = (state.getBarWidth() * seriesCount) - + (seriesGap * (seriesCount - 1)); - // offset the start of the boxes if the total width used is smaller - // than the category width - double offset = (categoryWidth - usedWidth) / 2; - yy = yy + offset + (row * (state.getBarWidth() + seriesGap)); - } else { - // offset the start of the box if the box width is smaller than - // the category width - double offset = (categoryWidth - state.getBarWidth()) / 2; - yy = yy + offset; - } - - g2.setPaint(getItemPaint(row, column)); - Stroke s = getItemStroke(row, column); - g2.setStroke(s); - - RectangleEdge location = plot.getRangeAxisEdge(); - - Number xQ1 = bawDataset.getQ1Value(row, column); - Number xQ3 = bawDataset.getQ3Value(row, column); - Number xMax = bawDataset.getMaxRegularValue(row, column); - Number xMin = bawDataset.getMinRegularValue(row, column); - - Shape box = null; - if (xQ1 != null && xQ3 != null && xMax != null && xMin != null) { - - double xxQ1 = rangeAxis.valueToJava2D(xQ1.doubleValue(), dataArea, - location); - double xxQ3 = rangeAxis.valueToJava2D(xQ3.doubleValue(), dataArea, - location); - double xxMax = rangeAxis.valueToJava2D(xMax.doubleValue(), dataArea, - location); - double xxMin = rangeAxis.valueToJava2D(xMin.doubleValue(), dataArea, - location); - double yymid = yy + state.getBarWidth() / 2.0; - double halfW = (state.getBarWidth() / 2.0) * this.whiskerWidth; - - // draw the box... - box = new Rectangle2D.Double(Math.min(xxQ1, xxQ3), yy, - Math.abs(xxQ1 - xxQ3), state.getBarWidth()); - if (this.fillBox) { - g2.fill(box); - } - - Paint outlinePaint = getItemOutlinePaint(row, column); - if (this.useOutlinePaintForWhiskers) { - g2.setPaint(outlinePaint); - } - // draw the upper shadow... - g2.draw(new Line2D.Double(xxMax, yymid, xxQ3, yymid)); - g2.draw(new Line2D.Double(xxMax, yymid - halfW, xxMax, - yymid + halfW)); - - // draw the lower shadow... - g2.draw(new Line2D.Double(xxMin, yymid, xxQ1, yymid)); - g2.draw(new Line2D.Double(xxMin, yymid - halfW, xxMin, - yymid + halfW)); - - g2.setStroke(getItemOutlineStroke(row, column)); - g2.setPaint(outlinePaint); - g2.draw(box); - } - - // draw mean - SPECIAL AIMS REQUIREMENT... - g2.setPaint(this.artifactPaint); - double aRadius; // average radius - if (this.meanVisible) { - Number xMean = bawDataset.getMeanValue(row, column); - if (xMean != null) { - double xxMean = rangeAxis.valueToJava2D(xMean.doubleValue(), - dataArea, location); - aRadius = state.getBarWidth() / 4; - // here we check that the average marker will in fact be - // visible before drawing it... - if ((xxMean > (dataArea.getMinX() - aRadius)) - && (xxMean < (dataArea.getMaxX() + aRadius))) { - Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xxMean - - aRadius, yy + aRadius, aRadius * 2, aRadius * 2); - g2.fill(avgEllipse); - g2.draw(avgEllipse); - } - } - } - - // draw median... - if (this.medianVisible) { - Number xMedian = bawDataset.getMedianValue(row, column); - if (xMedian != null) { - double xxMedian = rangeAxis.valueToJava2D(xMedian.doubleValue(), - dataArea, location); - g2.draw(new Line2D.Double(xxMedian, yy, xxMedian, - yy + state.getBarWidth())); - } - } - - // collect entity and tool tip information... - if (state.getInfo() != null && box != null) { - EntityCollection entities = state.getEntityCollection(); - if (entities != null) { - addItemEntity(entities, dataset, row, column, box); - } - } - - } - - /** - * Draws the visual representation of a single data item when the plot has - * a vertical orientation. - * - * @param g2 the graphics device. - * @param state the renderer state. - * @param dataArea the area within which the plot is being drawn. - * @param plot the plot (can be used to obtain standard color information - * etc). - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - * @param dataset the dataset (must be an instance of - * {@link BoxAndWhiskerCategoryDataset}). - * @param row the row index (zero-based). - * @param column the column index (zero-based). - */ - public void drawVerticalItem(Graphics2D g2, CategoryItemRendererState state, - Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, - ValueAxis rangeAxis, CategoryDataset dataset, int row, int column) { - - BoxAndWhiskerCategoryDataset bawDataset - = (BoxAndWhiskerCategoryDataset) dataset; - - double categoryEnd = domainAxis.getCategoryEnd(column, - getColumnCount(), dataArea, plot.getDomainAxisEdge()); - double categoryStart = domainAxis.getCategoryStart(column, - getColumnCount(), dataArea, plot.getDomainAxisEdge()); - double categoryWidth = categoryEnd - categoryStart; - - double xx = categoryStart; - int seriesCount = getRowCount(); - int categoryCount = getColumnCount(); - - if (seriesCount > 1) { - double seriesGap = dataArea.getWidth() * getItemMargin() - / (categoryCount * (seriesCount - 1)); - double usedWidth = (state.getBarWidth() * seriesCount) - + (seriesGap * (seriesCount - 1)); - // offset the start of the boxes if the total width used is smaller - // than the category width - double offset = (categoryWidth - usedWidth) / 2; - xx = xx + offset + (row * (state.getBarWidth() + seriesGap)); - } - else { - // offset the start of the box if the box width is smaller than the - // category width - double offset = (categoryWidth - state.getBarWidth()) / 2; - xx = xx + offset; - } - - double yyAverage; - double yyOutlier; - - Paint itemPaint = getItemPaint(row, column); - g2.setPaint(itemPaint); - Stroke s = getItemStroke(row, column); - g2.setStroke(s); - - double aRadius = 0; // average radius - - RectangleEdge location = plot.getRangeAxisEdge(); - - Number yQ1 = bawDataset.getQ1Value(row, column); - Number yQ3 = bawDataset.getQ3Value(row, column); - Number yMax = bawDataset.getMaxRegularValue(row, column); - Number yMin = bawDataset.getMinRegularValue(row, column); - Shape box = null; - if (yQ1 != null && yQ3 != null && yMax != null && yMin != null) { - - double yyQ1 = rangeAxis.valueToJava2D(yQ1.doubleValue(), dataArea, - location); - double yyQ3 = rangeAxis.valueToJava2D(yQ3.doubleValue(), dataArea, - location); - double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), - dataArea, location); - double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), - dataArea, location); - double xxmid = xx + state.getBarWidth() / 2.0; - double halfW = (state.getBarWidth() / 2.0) * this.whiskerWidth; - - // draw the body... - box = new Rectangle2D.Double(xx, Math.min(yyQ1, yyQ3), - state.getBarWidth(), Math.abs(yyQ1 - yyQ3)); - if (this.fillBox) { - g2.fill(box); - } - - Paint outlinePaint = getItemOutlinePaint(row, column); - if (this.useOutlinePaintForWhiskers) { - g2.setPaint(outlinePaint); - } - // draw the upper shadow... - g2.draw(new Line2D.Double(xxmid, yyMax, xxmid, yyQ3)); - g2.draw(new Line2D.Double(xxmid - halfW, yyMax, xxmid + halfW, yyMax)); - - // draw the lower shadow... - g2.draw(new Line2D.Double(xxmid, yyMin, xxmid, yyQ1)); - g2.draw(new Line2D.Double(xxmid - halfW, yyMin, xxmid + halfW, yyMin)); - - g2.setStroke(getItemOutlineStroke(row, column)); - g2.setPaint(outlinePaint); - g2.draw(box); - } - - g2.setPaint(this.artifactPaint); - - // draw mean - SPECIAL AIMS REQUIREMENT... - if (this.meanVisible) { - Number yMean = bawDataset.getMeanValue(row, column); - if (yMean != null) { - yyAverage = rangeAxis.valueToJava2D(yMean.doubleValue(), - dataArea, location); - aRadius = state.getBarWidth() / 4; - // here we check that the average marker will in fact be - // visible before drawing it... - if ((yyAverage > (dataArea.getMinY() - aRadius)) - && (yyAverage < (dataArea.getMaxY() + aRadius))) { - Ellipse2D.Double avgEllipse = new Ellipse2D.Double( - xx + aRadius, yyAverage - aRadius, aRadius * 2, - aRadius * 2); - g2.fill(avgEllipse); - g2.draw(avgEllipse); - } - } - } - - // draw median... - if (this.medianVisible) { - Number yMedian = bawDataset.getMedianValue(row, column); - if (yMedian != null) { - double yyMedian = rangeAxis.valueToJava2D( - yMedian.doubleValue(), dataArea, location); - g2.draw(new Line2D.Double(xx, yyMedian, - xx + state.getBarWidth(), yyMedian)); - } - } - - // draw yOutliers... - double maxAxisValue = rangeAxis.valueToJava2D( - rangeAxis.getUpperBound(), dataArea, location) + aRadius; - double minAxisValue = rangeAxis.valueToJava2D( - rangeAxis.getLowerBound(), dataArea, location) - aRadius; - - g2.setPaint(itemPaint); - - // draw outliers - double oRadius = state.getBarWidth() / 3; // outlier radius - List outliers = new ArrayList(); - OutlierListCollection outlierListCollection - = new OutlierListCollection(); - - // From outlier array sort out which are outliers and put these into a - // list If there are any farouts, set the flag on the - // OutlierListCollection - List yOutliers = bawDataset.getOutliers(row, column); - if (yOutliers != null) { - for (int i = 0; i < yOutliers.size(); i++) { - double outlier = ((Number) yOutliers.get(i)).doubleValue(); - Number minOutlier = bawDataset.getMinOutlier(row, column); - Number maxOutlier = bawDataset.getMaxOutlier(row, column); - Number minRegular = bawDataset.getMinRegularValue(row, column); - Number maxRegular = bawDataset.getMaxRegularValue(row, column); - if (outlier > maxOutlier.doubleValue()) { - outlierListCollection.setHighFarOut(true); - } else if (outlier < minOutlier.doubleValue()) { - outlierListCollection.setLowFarOut(true); - } else if (outlier > maxRegular.doubleValue()) { - yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, - location); - outliers.add(new Outlier(xx + state.getBarWidth() / 2.0, - yyOutlier, oRadius)); - } else if (outlier < minRegular.doubleValue()) { - yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, - location); - outliers.add(new Outlier(xx + state.getBarWidth() / 2.0, - yyOutlier, oRadius)); - } - Collections.sort(outliers); - } - - // Process outliers. Each outlier is either added to the - // appropriate outlier list or a new outlier list is made - for (Iterator iterator = outliers.iterator(); iterator.hasNext();) { - Outlier outlier = (Outlier) iterator.next(); - outlierListCollection.add(outlier); - } - - for (Iterator iterator = outlierListCollection.iterator(); - iterator.hasNext();) { - OutlierList list = (OutlierList) iterator.next(); - Outlier outlier = list.getAveragedOutlier(); - Point2D point = outlier.getPoint(); - - if (list.isMultiple()) { - drawMultipleEllipse(point, state.getBarWidth(), oRadius, - g2); - } else { - drawEllipse(point, oRadius, g2); - } - } - - // draw farout indicators - if (isMaxOutlierVisible() && outlierListCollection.isHighFarOut()) { - drawHighFarOut(aRadius / 2.0, g2, - xx + state.getBarWidth() / 2.0, maxAxisValue); - } - - if (isMinOutlierVisible() && outlierListCollection.isLowFarOut()) { - drawLowFarOut(aRadius / 2.0, g2, - xx + state.getBarWidth() / 2.0, minAxisValue); - } - } - // collect entity and tool tip information... - if (state.getInfo() != null && box != null) { - EntityCollection entities = state.getEntityCollection(); - if (entities != null) { - addItemEntity(entities, dataset, row, column, box); - } - } - - } - - /** - * Draws a dot to represent an outlier. - * - * @param point the location. - * @param oRadius the radius. - * @param g2 the graphics device. - */ - private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) { - Ellipse2D dot = new Ellipse2D.Double(point.getX() + oRadius / 2, - point.getY(), oRadius, oRadius); - g2.draw(dot); - } - - /** - * Draws two dots to represent the average value of more than one outlier. - * - * @param point the location - * @param boxWidth the box width. - * @param oRadius the radius. - * @param g2 the graphics device. - */ - private void drawMultipleEllipse(Point2D point, double boxWidth, - double oRadius, Graphics2D g2) { - - Ellipse2D dot1 = new Ellipse2D.Double(point.getX() - (boxWidth / 2) - + oRadius, point.getY(), oRadius, oRadius); - Ellipse2D dot2 = new Ellipse2D.Double(point.getX() + (boxWidth / 2), - point.getY(), oRadius, oRadius); - g2.draw(dot1); - g2.draw(dot2); - } - - /** - * Draws a triangle to indicate the presence of far-out values. - * - * @param aRadius the radius. - * @param g2 the graphics device. - * @param xx the x coordinate. - * @param m the y coordinate. - */ - private void drawHighFarOut(double aRadius, Graphics2D g2, double xx, - double m) { - double side = aRadius * 2; - g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side)); - g2.draw(new Line2D.Double(xx - side, m + side, xx, m)); - g2.draw(new Line2D.Double(xx + side, m + side, xx, m)); - } - - /** - * Draws a triangle to indicate the presence of far-out values. - * - * @param aRadius the radius. - * @param g2 the graphics device. - * @param xx the x coordinate. - * @param m the y coordinate. - */ - private void drawLowFarOut(double aRadius, Graphics2D g2, double xx, - double m) { - double side = aRadius * 2; - g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side)); - g2.draw(new Line2D.Double(xx - side, m - side, xx, m)); - g2.draw(new Line2D.Double(xx + side, m - side, xx, m)); - } - - /** - * Tests this renderer for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return {@code true} or {@code false}. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof BoxAndWhiskerRenderer)) { - return false; - } - BoxAndWhiskerRenderer that = (BoxAndWhiskerRenderer) obj; - if (this.fillBox != that.fillBox) { - return false; - } - if (this.itemMargin != that.itemMargin) { - return false; - } - if (this.maximumBarWidth != that.maximumBarWidth) { - return false; - } - if (this.meanVisible != that.meanVisible) { - return false; - } - if (this.medianVisible != that.medianVisible) { - return false; - } - if (this.minOutlierVisible != that.minOutlierVisible) { - return false; - } - if (this.maxOutlierVisible != that.maxOutlierVisible) { - return false; - } - if (this.useOutlinePaintForWhiskers - != that.useOutlinePaintForWhiskers) { - return false; - } - if (this.whiskerWidth != that.whiskerWidth) { - return false; - } - if (!PaintUtils.equal(this.artifactPaint, that.artifactPaint)) { - return false; - } - return super.equals(obj); - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writePaint(this.artifactPaint, stream); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - this.artifactPaint = SerialUtils.readPaint(stream); - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * -------------------------- + * BoxAndWhiskerRenderer.java + * -------------------------- + * (C) Copyright 2003-present, by David Browning and Contributors. + * + * Original Author: David Browning (for the Australian Institute of Marine + * Science); + * Contributor(s): David Gilbert; + * Tim Bardzil; + * Rob Van der Sanden (patches 1866446 and 1888422); + * Peter Becker (patches 2868585 and 2868608); + * Martin Krauskopf (patch 3421088); + * Martin Hoeller; + * John Matthews; + * + */ + +package org.jfree.chart.renderer.category; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.jfree.chart.LegendItem; +import org.jfree.chart.axis.CategoryAxis; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.entity.EntityCollection; +import org.jfree.chart.plot.CategoryPlot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.renderer.Outlier; +import org.jfree.chart.renderer.OutlierList; +import org.jfree.chart.renderer.OutlierListCollection; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.util.PaintUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.SerialUtils; +import org.jfree.data.Range; +import org.jfree.data.category.CategoryDataset; +import org.jfree.data.statistics.BoxAndWhiskerCategoryDataset; + +/** + * A box-and-whisker renderer. This renderer requires a + * {@link BoxAndWhiskerCategoryDataset} and is for use with the + * {@link CategoryPlot} class. The example shown here is generated + * by the {@code BoxAndWhiskerChartDemo1.java} program included in the + * JFreeChart Demo Collection: + *

+ * BoxAndWhiskerRendererSample.png + */ +public class BoxAndWhiskerRenderer extends AbstractCategoryItemRenderer + implements Cloneable, PublicCloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = 632027470694481177L; + + /** The color used to paint the median line and average marker. */ + private transient Paint artifactPaint; + + /** A flag that controls whether or not the box is filled. */ + private boolean fillBox; + + /** The margin between items (boxes) within a category. */ + private double itemMargin; + + /** + * The maximum bar width as percentage of the available space in the plot. + * Take care with the encoding - for example, 0.05 is five percent. + */ + private double maximumBarWidth; + + /** + * A flag that controls whether or not the median indicator is drawn. + */ + private boolean medianVisible; + + /** + * A flag that controls whether or not the mean indicator is drawn. + */ + private boolean meanVisible; + + /** + * A flag that controls whether or not the maxOutlier is visible. + */ + private boolean maxOutlierVisible; + + /** + * A flag that controls whether or not the minOutlier is visible. + */ + private boolean minOutlierVisible; + + /** + * A flag that, if {@code true}, causes the whiskers to be drawn + * using the outline paint for the series. The default value is + * {@code false} and in that case the regular series paint is used. + */ + private boolean useOutlinePaintForWhiskers; + + /** + * The width of the whiskers as fraction of the bar width. + */ + private double whiskerWidth; + + /** + * Default constructor. + */ + public BoxAndWhiskerRenderer() { + this.artifactPaint = Color.BLACK; + this.fillBox = true; + this.itemMargin = 0.20; + this.maximumBarWidth = 1.0; + this.medianVisible = true; + this.meanVisible = true; + this.minOutlierVisible = true; + this.maxOutlierVisible = true; + this.useOutlinePaintForWhiskers = false; + this.whiskerWidth = 1.0; + setDefaultLegendShape(new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0)); + } + + /** + * Returns the paint used to color the median and average markers. + * + * @return The paint used to draw the median and average markers (never + * {@code null}). + * + * @see #setArtifactPaint(Paint) + */ + public Paint getArtifactPaint() { + return this.artifactPaint; + } + + /** + * Sets the paint used to color the median and average markers and calls + * {@link #fireChangeEvent()}. + * + * @param paint the paint ({@code null} not permitted). + * + * @see #getArtifactPaint() + */ + public void setArtifactPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.artifactPaint = paint; + fireChangeEvent(); + } + + /** + * Returns the flag that controls whether or not the box is filled. + * + * @return A boolean. + * + * @see #setFillBox(boolean) + */ + public boolean getFillBox() { + return this.fillBox; + } + + /** + * Sets the flag that controls whether or not the box is filled and calls + * {@link #fireChangeEvent()}. + * + * @param flag the flag. + * + * @see #getFillBox() + */ + public void setFillBox(boolean flag) { + this.fillBox = flag; + fireChangeEvent(); + } + + /** + * Returns the item margin. This is a percentage of the available space + * that is allocated to the space between items in the chart. + * + * @return The margin. + * + * @see #setItemMargin(double) + */ + public double getItemMargin() { + return this.itemMargin; + } + + /** + * Sets the item margin and calls {@link #fireChangeEvent()}. + * + * @param margin the margin (a percentage). + * + * @see #getItemMargin() + */ + public void setItemMargin(double margin) { + this.itemMargin = margin; + fireChangeEvent(); + } + + /** + * Returns the maximum bar width as a percentage of the available drawing + * space. Take care with the encoding, for example 0.10 is ten percent. + * + * @return The maximum bar width. + * + * @see #setMaximumBarWidth(double) + */ + public double getMaximumBarWidth() { + return this.maximumBarWidth; + } + + /** + * Sets the maximum bar width, which is specified as a percentage of the + * available space for all bars, and calls {@link #fireChangeEvent()}. + * + * @param percent the maximum bar width (a percentage, where 0.10 is ten + * percent). + * + * @see #getMaximumBarWidth() + */ + public void setMaximumBarWidth(double percent) { + this.maximumBarWidth = percent; + fireChangeEvent(); + } + + /** + * Returns the flag that controls whether or not the mean indicator is + * draw for each item. + * + * @return A boolean. + * + * @see #setMeanVisible(boolean) + */ + public boolean isMeanVisible() { + return this.meanVisible; + } + + /** + * Sets the flag that controls whether or not the mean indicator is drawn + * for each item, and calls {@link #fireChangeEvent()}. + * + * @param visible the new flag value. + * + * @see #isMeanVisible() + */ + public void setMeanVisible(boolean visible) { + if (this.meanVisible == visible) { + return; + } + this.meanVisible = visible; + fireChangeEvent(); + } + + /** + * Returns the flag that controls whether or not the median indicator is + * draw for each item. + * + * @return A boolean. + * + * @see #setMedianVisible(boolean) + */ + public boolean isMedianVisible() { + return this.medianVisible; + } + + /** + * Sets the flag that controls whether or not the median indicator is drawn + * for each item, and calls {@link #fireChangeEvent()}. + * + * @param visible the new flag value. + * + * @see #isMedianVisible() + */ + public void setMedianVisible(boolean visible) { + if (this.medianVisible == visible) { + return; + } + this.medianVisible = visible; + fireChangeEvent(); + } + + /** + * Returns the flag that controls whether or not the minimum outlier is + * draw for each item. + * + * @return A boolean. + * + * @see #setMinOutlierVisible(boolean) + * + * @since 1.5.2 + */ + public boolean isMinOutlierVisible() { + return this.minOutlierVisible; + } + + /** + * Sets the flag that controls whether or not the minimum outlier is drawn + * for each item, and calls {@link #fireChangeEvent()}. + * + * @param visible the new flag value. + * + * @see #isMinOutlierVisible() + * + * @since 1.5.2 + */ + public void setMinOutlierVisible(boolean visible) { + if (this.minOutlierVisible == visible) { + return; + } + this.minOutlierVisible = visible; + fireChangeEvent(); + } + + /** + * Returns the flag that controls whether or not the maximum outlier is + * draw for each item. + * + * @return A boolean. + * + * @see #setMaxOutlierVisible(boolean) + * + * @since 1.5.2 + */ + public boolean isMaxOutlierVisible() { + return this.maxOutlierVisible; + } + + /** + * Sets the flag that controls whether or not the maximum outlier is drawn + * for each item, and calls {@link #fireChangeEvent()}. + * + * @param visible the new flag value. + * + * @see #isMaxOutlierVisible() + * + * @since 1.5.2 + */ + public void setMaxOutlierVisible(boolean visible) { + if (this.maxOutlierVisible == visible) { + return; + } + this.maxOutlierVisible = visible; + fireChangeEvent(); + } + + /** + * Returns the flag that, if {@code true}, causes the whiskers to + * be drawn using the series outline paint. + * + * @return A boolean. + */ + public boolean getUseOutlinePaintForWhiskers() { + return this.useOutlinePaintForWhiskers; + } + + /** + * Sets the flag that, if {@code true}, causes the whiskers to + * be drawn using the series outline paint, and calls {@link #fireChangeEvent()}. + * + * @param flag the new flag value. + */ + public void setUseOutlinePaintForWhiskers(boolean flag) { + if (this.useOutlinePaintForWhiskers == flag) { + return; + } + this.useOutlinePaintForWhiskers = flag; + fireChangeEvent(); + } + + /** + * Returns the width of the whiskers as fraction of the bar width. + * + * @return The width of the whiskers. + * + * @see #setWhiskerWidth(double) + */ + public double getWhiskerWidth() { + return this.whiskerWidth; + } + + /** + * Sets the width of the whiskers as a fraction of the bar width and calls + * {@link #fireChangeEvent()}. + * + * @param width a value between 0 and 1 indicating how wide the whisker is + * supposed to be compared to the bar. + * @see #getWhiskerWidth() + * @see CategoryItemRendererState#getBarWidth() + */ + public void setWhiskerWidth(double width) { + if (width < 0 || width > 1) { + throw new IllegalArgumentException( + "Value for whisker width out of range"); + } + if (width == this.whiskerWidth) { + return; + } + this.whiskerWidth = width; + fireChangeEvent(); + } + + /** + * Returns a legend item for a series. + * + * @param datasetIndex the dataset index (zero-based). + * @param series the series index (zero-based). + * + * @return The legend item (possibly {@code null}). + */ + @Override + public LegendItem getLegendItem(int datasetIndex, int series) { + + CategoryPlot cp = getPlot(); + if (cp == null) { + return null; + } + + // check that a legend item needs to be displayed... + if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) { + return null; + } + + CategoryDataset dataset = cp.getDataset(datasetIndex); + String label = getLegendItemLabelGenerator().generateLabel(dataset, + series); + String description = label; + String toolTipText = null; + if (getLegendItemToolTipGenerator() != null) { + toolTipText = getLegendItemToolTipGenerator().generateLabel( + dataset, series); + } + String urlText = null; + if (getLegendItemURLGenerator() != null) { + urlText = getLegendItemURLGenerator().generateLabel(dataset, + series); + } + Shape shape = lookupLegendShape(series); + Paint paint = lookupSeriesPaint(series); + Paint outlinePaint = lookupSeriesOutlinePaint(series); + Stroke outlineStroke = lookupSeriesOutlineStroke(series); + LegendItem result = new LegendItem(label, description, toolTipText, + urlText, shape, paint, outlineStroke, outlinePaint); + result.setLabelFont(lookupLegendTextFont(series)); + Paint labelPaint = lookupLegendTextPaint(series); + if (labelPaint != null) { + result.setLabelPaint(labelPaint); + } + result.setDataset(dataset); + result.setDatasetIndex(datasetIndex); + result.setSeriesKey(dataset.getRowKey(series)); + result.setSeriesIndex(series); + return result; + + } + + /** + * Returns the range of values from the specified dataset that the + * renderer will require to display all the data. + * + * @param dataset the dataset. + * + * @return The range. + */ + @Override + public Range findRangeBounds(CategoryDataset dataset) { + return super.findRangeBounds(dataset, true); + } + + /** + * Initialises the renderer. This method gets called once at the start of + * the process of drawing a chart. + * + * @param g2 the graphics device. + * @param dataArea the area in which the data is to be plotted. + * @param plot the plot. + * @param rendererIndex the renderer index. + * @param info collects chart rendering information for return to caller. + * + * @return The renderer state. + */ + @Override + public CategoryItemRendererState initialise(Graphics2D g2, + Rectangle2D dataArea, CategoryPlot plot, int rendererIndex, + PlotRenderingInfo info) { + + CategoryItemRendererState state = super.initialise(g2, dataArea, plot, + rendererIndex, info); + // calculate the box width + CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex); + CategoryDataset dataset = plot.getDataset(rendererIndex); + if (dataset != null) { + int columns = dataset.getColumnCount(); + int rows = dataset.getRowCount(); + double space = 0.0; + PlotOrientation orientation = plot.getOrientation(); + if (orientation == PlotOrientation.HORIZONTAL) { + space = dataArea.getHeight(); + } + else if (orientation == PlotOrientation.VERTICAL) { + space = dataArea.getWidth(); + } + double maxWidth = space * getMaximumBarWidth(); + double categoryMargin = 0.0; + double currentItemMargin = 0.0; + if (columns > 1) { + categoryMargin = domainAxis.getCategoryMargin(); + } + if (rows > 1) { + currentItemMargin = getItemMargin(); + } + double used = space * (1 - domainAxis.getLowerMargin() + - domainAxis.getUpperMargin() + - categoryMargin - currentItemMargin); + if ((rows * columns) > 0) { + state.setBarWidth(Math.min(used / (dataset.getColumnCount() + * dataset.getRowCount()), maxWidth)); + } else { + state.setBarWidth(Math.min(used, maxWidth)); + } + } + return state; + + } + + /** + * Draw a single data item. + * + * @param g2 the graphics device. + * @param state the renderer state. + * @param dataArea the area in which the data is drawn. + * @param plot the plot. + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + * @param dataset the data (must be an instance of + * {@link BoxAndWhiskerCategoryDataset}). + * @param row the row index (zero-based). + * @param column the column index (zero-based). + * @param pass the pass index. + */ + @Override + public void drawItem(Graphics2D g2, CategoryItemRendererState state, + Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, + ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, + int pass) { + + // do nothing if item is not visible + if (!getItemVisible(row, column)) { + return; + } + + if (!(dataset instanceof BoxAndWhiskerCategoryDataset)) { + throw new IllegalArgumentException( + "BoxAndWhiskerRenderer.drawItem() : the data should be " + + "of type BoxAndWhiskerCategoryDataset only."); + } + + PlotOrientation orientation = plot.getOrientation(); + + if (orientation == PlotOrientation.HORIZONTAL) { + drawHorizontalItem(g2, state, dataArea, plot, domainAxis, + rangeAxis, dataset, row, column); + } else if (orientation == PlotOrientation.VERTICAL) { + drawVerticalItem(g2, state, dataArea, plot, domainAxis, + rangeAxis, dataset, row, column); + } + + } + + /** + * Draws the visual representation of a single data item when the plot has + * a horizontal orientation. + * + * @param g2 the graphics device. + * @param state the renderer state. + * @param dataArea the area within which the plot is being drawn. + * @param plot the plot (can be used to obtain standard color + * information etc). + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + * @param dataset the dataset (must be an instance of + * {@link BoxAndWhiskerCategoryDataset}). + * @param row the row index (zero-based). + * @param column the column index (zero-based). + */ + public void drawHorizontalItem(Graphics2D g2, + CategoryItemRendererState state, Rectangle2D dataArea, + CategoryPlot plot, CategoryAxis domainAxis, ValueAxis rangeAxis, + CategoryDataset dataset, int row, int column) { + + BoxAndWhiskerCategoryDataset bawDataset + = (BoxAndWhiskerCategoryDataset) dataset; + + double categoryEnd = domainAxis.getCategoryEnd(column, + getColumnCount(), dataArea, plot.getDomainAxisEdge()); + double categoryStart = domainAxis.getCategoryStart(column, + getColumnCount(), dataArea, plot.getDomainAxisEdge()); + double categoryWidth = Math.abs(categoryEnd - categoryStart); + + double yy = categoryStart; + int seriesCount = getRowCount(); + int categoryCount = getColumnCount(); + + if (seriesCount > 1) { + double seriesGap = dataArea.getHeight() * getItemMargin() + / (categoryCount * (seriesCount - 1)); + double usedWidth = (state.getBarWidth() * seriesCount) + + (seriesGap * (seriesCount - 1)); + // offset the start of the boxes if the total width used is smaller + // than the category width + double offset = (categoryWidth - usedWidth) / 2; + yy = yy + offset + (row * (state.getBarWidth() + seriesGap)); + } else { + // offset the start of the box if the box width is smaller than + // the category width + double offset = (categoryWidth - state.getBarWidth()) / 2; + yy = yy + offset; + } + + g2.setPaint(getItemPaint(row, column)); + Stroke s = getItemStroke(row, column); + g2.setStroke(s); + + RectangleEdge location = plot.getRangeAxisEdge(); + + Number xQ1 = bawDataset.getQ1Value(row, column); + Number xQ3 = bawDataset.getQ3Value(row, column); + Number xMax = bawDataset.getMaxRegularValue(row, column); + Number xMin = bawDataset.getMinRegularValue(row, column); + + Shape box = null; + if (xQ1 != null && xQ3 != null && xMax != null && xMin != null) { + + double xxQ1 = rangeAxis.valueToJava2D(xQ1.doubleValue(), dataArea, + location); + double xxQ3 = rangeAxis.valueToJava2D(xQ3.doubleValue(), dataArea, + location); + double xxMax = rangeAxis.valueToJava2D(xMax.doubleValue(), dataArea, + location); + double xxMin = rangeAxis.valueToJava2D(xMin.doubleValue(), dataArea, + location); + double yymid = yy + state.getBarWidth() / 2.0; + double halfW = (state.getBarWidth() / 2.0) * this.whiskerWidth; + + // draw the box... + box = new Rectangle2D.Double(Math.min(xxQ1, xxQ3), yy, + Math.abs(xxQ1 - xxQ3), state.getBarWidth()); + if (this.fillBox) { + g2.fill(box); + } + + Paint outlinePaint = getItemOutlinePaint(row, column); + if (this.useOutlinePaintForWhiskers) { + g2.setPaint(outlinePaint); + } + // draw the upper shadow... + g2.draw(new Line2D.Double(xxMax, yymid, xxQ3, yymid)); + g2.draw(new Line2D.Double(xxMax, yymid - halfW, xxMax, + yymid + halfW)); + + // draw the lower shadow... + g2.draw(new Line2D.Double(xxMin, yymid, xxQ1, yymid)); + g2.draw(new Line2D.Double(xxMin, yymid - halfW, xxMin, + yymid + halfW)); + + g2.setStroke(getItemOutlineStroke(row, column)); + g2.setPaint(outlinePaint); + g2.draw(box); + } + + // draw mean - SPECIAL AIMS REQUIREMENT... + g2.setPaint(this.artifactPaint); + double aRadius; // average radius + if (this.meanVisible) { + Number xMean = bawDataset.getMeanValue(row, column); + if (xMean != null) { + double xxMean = rangeAxis.valueToJava2D(xMean.doubleValue(), + dataArea, location); + aRadius = state.getBarWidth() / 4; + // here we check that the average marker will in fact be + // visible before drawing it... + if ((xxMean > (dataArea.getMinX() - aRadius)) + && (xxMean < (dataArea.getMaxX() + aRadius))) { + Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xxMean + - aRadius, yy + aRadius, aRadius * 2, aRadius * 2); + g2.fill(avgEllipse); + g2.draw(avgEllipse); + } + } + } + + // draw median... + if (this.medianVisible) { + Number xMedian = bawDataset.getMedianValue(row, column); + if (xMedian != null) { + double xxMedian = rangeAxis.valueToJava2D(xMedian.doubleValue(), + dataArea, location); + g2.draw(new Line2D.Double(xxMedian, yy, xxMedian, + yy + state.getBarWidth())); + } + } + + // collect entity and tool tip information... + if (state.getInfo() != null && box != null) { + EntityCollection entities = state.getEntityCollection(); + if (entities != null) { + addItemEntity(entities, dataset, row, column, box); + } + } + + } + + /** + * Draws the visual representation of a single data item when the plot has + * a vertical orientation. + * + * @param g2 the graphics device. + * @param state the renderer state. + * @param dataArea the area within which the plot is being drawn. + * @param plot the plot (can be used to obtain standard color information + * etc). + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + * @param dataset the dataset (must be an instance of + * {@link BoxAndWhiskerCategoryDataset}). + * @param row the row index (zero-based). + * @param column the column index (zero-based). + */ + public void drawVerticalItem(Graphics2D g2, CategoryItemRendererState state, + Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, + ValueAxis rangeAxis, CategoryDataset dataset, int row, int column) { + + BoxAndWhiskerCategoryDataset bawDataset + = (BoxAndWhiskerCategoryDataset) dataset; + + double categoryEnd = domainAxis.getCategoryEnd(column, + getColumnCount(), dataArea, plot.getDomainAxisEdge()); + double categoryStart = domainAxis.getCategoryStart(column, + getColumnCount(), dataArea, plot.getDomainAxisEdge()); + double categoryWidth = categoryEnd - categoryStart; + + double xx = categoryStart; + int seriesCount = getRowCount(); + int categoryCount = getColumnCount(); + + if (seriesCount > 1) { + double seriesGap = dataArea.getWidth() * getItemMargin() + / (categoryCount * (seriesCount - 1)); + double usedWidth = (state.getBarWidth() * seriesCount) + + (seriesGap * (seriesCount - 1)); + // offset the start of the boxes if the total width used is smaller + // than the category width + double offset = (categoryWidth - usedWidth) / 2; + xx = xx + offset + (row * (state.getBarWidth() + seriesGap)); + } + else { + // offset the start of the box if the box width is smaller than the + // category width + double offset = (categoryWidth - state.getBarWidth()) / 2; + xx = xx + offset; + } + + double yyAverage; + double yyOutlier; + + Paint itemPaint = getItemPaint(row, column); + g2.setPaint(itemPaint); + Stroke s = getItemStroke(row, column); + g2.setStroke(s); + + double aRadius = 0; // average radius + + RectangleEdge location = plot.getRangeAxisEdge(); + + Number yQ1 = bawDataset.getQ1Value(row, column); + Number yQ3 = bawDataset.getQ3Value(row, column); + Number yMax = bawDataset.getMaxRegularValue(row, column); + Number yMin = bawDataset.getMinRegularValue(row, column); + Shape box = null; + if (yQ1 != null && yQ3 != null && yMax != null && yMin != null) { + + double yyQ1 = rangeAxis.valueToJava2D(yQ1.doubleValue(), dataArea, + location); + double yyQ3 = rangeAxis.valueToJava2D(yQ3.doubleValue(), dataArea, + location); + double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), + dataArea, location); + double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), + dataArea, location); + double xxmid = xx + state.getBarWidth() / 2.0; + double halfW = (state.getBarWidth() / 2.0) * this.whiskerWidth; + + // draw the body... + box = new Rectangle2D.Double(xx, Math.min(yyQ1, yyQ3), + state.getBarWidth(), Math.abs(yyQ1 - yyQ3)); + if (this.fillBox) { + g2.fill(box); + } + + Paint outlinePaint = getItemOutlinePaint(row, column); + if (this.useOutlinePaintForWhiskers) { + g2.setPaint(outlinePaint); + } + // draw the upper shadow... + g2.draw(new Line2D.Double(xxmid, yyMax, xxmid, yyQ3)); + g2.draw(new Line2D.Double(xxmid - halfW, yyMax, xxmid + halfW, yyMax)); + + // draw the lower shadow... + g2.draw(new Line2D.Double(xxmid, yyMin, xxmid, yyQ1)); + g2.draw(new Line2D.Double(xxmid - halfW, yyMin, xxmid + halfW, yyMin)); + + g2.setStroke(getItemOutlineStroke(row, column)); + g2.setPaint(outlinePaint); + g2.draw(box); + } + + g2.setPaint(this.artifactPaint); + + // draw mean - SPECIAL AIMS REQUIREMENT... + if (this.meanVisible) { + Number yMean = bawDataset.getMeanValue(row, column); + if (yMean != null) { + yyAverage = rangeAxis.valueToJava2D(yMean.doubleValue(), + dataArea, location); + aRadius = state.getBarWidth() / 4; + // here we check that the average marker will in fact be + // visible before drawing it... + if ((yyAverage > (dataArea.getMinY() - aRadius)) + && (yyAverage < (dataArea.getMaxY() + aRadius))) { + Ellipse2D.Double avgEllipse = new Ellipse2D.Double( + xx + aRadius, yyAverage - aRadius, aRadius * 2, + aRadius * 2); + g2.fill(avgEllipse); + g2.draw(avgEllipse); + } + } + } + + // draw median... + if (this.medianVisible) { + Number yMedian = bawDataset.getMedianValue(row, column); + if (yMedian != null) { + double yyMedian = rangeAxis.valueToJava2D( + yMedian.doubleValue(), dataArea, location); + g2.draw(new Line2D.Double(xx, yyMedian, + xx + state.getBarWidth(), yyMedian)); + } + } + + // draw yOutliers... + double maxAxisValue = rangeAxis.valueToJava2D( + rangeAxis.getUpperBound(), dataArea, location) + aRadius; + double minAxisValue = rangeAxis.valueToJava2D( + rangeAxis.getLowerBound(), dataArea, location) - aRadius; + + g2.setPaint(itemPaint); + + // draw outliers + double oRadius = state.getBarWidth() / 3; // outlier radius + List outliers = new ArrayList(); + OutlierListCollection outlierListCollection + = new OutlierListCollection(); + + // From outlier array sort out which are outliers and put these into a + // list If there are any farouts, set the flag on the + // OutlierListCollection + List yOutliers = bawDataset.getOutliers(row, column); + if (yOutliers != null) { + for (int i = 0; i < yOutliers.size(); i++) { + double outlier = ((Number) yOutliers.get(i)).doubleValue(); + Number minOutlier = bawDataset.getMinOutlier(row, column); + Number maxOutlier = bawDataset.getMaxOutlier(row, column); + Number minRegular = bawDataset.getMinRegularValue(row, column); + Number maxRegular = bawDataset.getMaxRegularValue(row, column); + if (outlier > maxOutlier.doubleValue()) { + outlierListCollection.setHighFarOut(true); + } else if (outlier < minOutlier.doubleValue()) { + outlierListCollection.setLowFarOut(true); + } else if (outlier > maxRegular.doubleValue()) { + yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, + location); + outliers.add(new Outlier(xx + state.getBarWidth() / 2.0, + yyOutlier, oRadius)); + } else if (outlier < minRegular.doubleValue()) { + yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, + location); + outliers.add(new Outlier(xx + state.getBarWidth() / 2.0, + yyOutlier, oRadius)); + } + Collections.sort(outliers); + } + + // Process outliers. Each outlier is either added to the + // appropriate outlier list or a new outlier list is made + for (Iterator iterator = outliers.iterator(); iterator.hasNext();) { + Outlier outlier = (Outlier) iterator.next(); + outlierListCollection.add(outlier); + } + + for (Iterator iterator = outlierListCollection.iterator(); + iterator.hasNext();) { + OutlierList list = (OutlierList) iterator.next(); + Outlier outlier = list.getAveragedOutlier(); + Point2D point = outlier.getPoint(); + + if (list.isMultiple()) { + drawMultipleEllipse(point, state.getBarWidth(), oRadius, + g2); + } else { + drawEllipse(point, oRadius, g2); + } + } + + // draw farout indicators + if (isMaxOutlierVisible() && outlierListCollection.isHighFarOut()) { + drawHighFarOut(aRadius / 2.0, g2, + xx + state.getBarWidth() / 2.0, maxAxisValue); + } + + if (isMinOutlierVisible() && outlierListCollection.isLowFarOut()) { + drawLowFarOut(aRadius / 2.0, g2, + xx + state.getBarWidth() / 2.0, minAxisValue); + } + } + // collect entity and tool tip information... + if (state.getInfo() != null && box != null) { + EntityCollection entities = state.getEntityCollection(); + if (entities != null) { + addItemEntity(entities, dataset, row, column, box); + } + } + + } + + /** + * Draws a dot to represent an outlier. + * + * @param point the location. + * @param oRadius the radius. + * @param g2 the graphics device. + */ + private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) { + Ellipse2D dot = new Ellipse2D.Double(point.getX() + oRadius / 2, + point.getY(), oRadius, oRadius); + g2.draw(dot); + } + + /** + * Draws two dots to represent the average value of more than one outlier. + * + * @param point the location + * @param boxWidth the box width. + * @param oRadius the radius. + * @param g2 the graphics device. + */ + private void drawMultipleEllipse(Point2D point, double boxWidth, + double oRadius, Graphics2D g2) { + + Ellipse2D dot1 = new Ellipse2D.Double(point.getX() - (boxWidth / 2) + + oRadius, point.getY(), oRadius, oRadius); + Ellipse2D dot2 = new Ellipse2D.Double(point.getX() + (boxWidth / 2), + point.getY(), oRadius, oRadius); + g2.draw(dot1); + g2.draw(dot2); + } + + /** + * Draws a triangle to indicate the presence of far-out values. + * + * @param aRadius the radius. + * @param g2 the graphics device. + * @param xx the x coordinate. + * @param m the y coordinate. + */ + private void drawHighFarOut(double aRadius, Graphics2D g2, double xx, + double m) { + double side = aRadius * 2; + g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side)); + g2.draw(new Line2D.Double(xx - side, m + side, xx, m)); + g2.draw(new Line2D.Double(xx + side, m + side, xx, m)); + } + + /** + * Draws a triangle to indicate the presence of far-out values. + * + * @param aRadius the radius. + * @param g2 the graphics device. + * @param xx the x coordinate. + * @param m the y coordinate. + */ + private void drawLowFarOut(double aRadius, Graphics2D g2, double xx, + double m) { + double side = aRadius * 2; + g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side)); + g2.draw(new Line2D.Double(xx - side, m - side, xx, m)); + g2.draw(new Line2D.Double(xx + side, m - side, xx, m)); + } + + /** + * Tests this renderer for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return {@code true} or {@code false}. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof BoxAndWhiskerRenderer)) { + return false; + } + BoxAndWhiskerRenderer that = (BoxAndWhiskerRenderer) obj; + if (this.fillBox != that.fillBox) { + return false; + } + if (this.itemMargin != that.itemMargin) { + return false; + } + if (this.maximumBarWidth != that.maximumBarWidth) { + return false; + } + if (this.meanVisible != that.meanVisible) { + return false; + } + if (this.medianVisible != that.medianVisible) { + return false; + } + if (this.minOutlierVisible != that.minOutlierVisible) { + return false; + } + if (this.maxOutlierVisible != that.maxOutlierVisible) { + return false; + } + if (this.useOutlinePaintForWhiskers + != that.useOutlinePaintForWhiskers) { + return false; + } + if (this.whiskerWidth != that.whiskerWidth) { + return false; + } + if (!PaintUtils.equal(this.artifactPaint, that.artifactPaint)) { + return false; + } + return super.equals(obj); + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writePaint(this.artifactPaint, stream); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + this.artifactPaint = SerialUtils.readPaint(stream); + } + +} diff --git a/src/main/java/org/jfree/chart/renderer/category/CategoryItemRenderer.java b/src/main/java/org/jfree/chart/renderer/category/CategoryItemRenderer.java index fa1718594..4b9db8a19 100644 --- a/src/main/java/org/jfree/chart/renderer/category/CategoryItemRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/CategoryItemRenderer.java @@ -48,7 +48,6 @@ import org.jfree.chart.LegendItemSource; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.event.RendererChangeListener; import org.jfree.chart.labels.CategoryItemLabelGenerator; import org.jfree.chart.labels.CategoryToolTipGenerator; @@ -195,8 +194,8 @@ CategoryItemRendererState initialise(Graphics2D g2, Boolean getSeriesVisible(int series); /** - * Sets the flag that controls whether a series is visible and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the flag that controls whether a series is visible and notifies all + * registered listeners. * * @param series the series index (zero-based). * @param visible the flag ({@code null} permitted). @@ -207,8 +206,7 @@ CategoryItemRendererState initialise(Graphics2D g2, /** * Sets the flag that controls whether a series is visible and, if - * requested, sends a {@link RendererChangeEvent} to all registered - * listeners. + * requested, notifies all registered listeners. * * @param series the series index. * @param visible the flag ({@code null} permitted). @@ -228,8 +226,7 @@ CategoryItemRendererState initialise(Graphics2D g2, boolean getDefaultSeriesVisible(); /** - * Sets the default visibility and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the default visibility and notifies all registered listeners. * * @param visible the flag. * @@ -238,8 +235,8 @@ CategoryItemRendererState initialise(Graphics2D g2, void setDefaultSeriesVisible(boolean visible); /** - * Sets the default visibility and, if requested, sends - * a {@link RendererChangeEvent} to all registered listeners. + * Sets the default visibility and, if requested, notifies all registered + * listeners. * * @param visible the visibility. * @param notify notify listeners? @@ -276,7 +273,7 @@ CategoryItemRendererState initialise(Graphics2D g2, /** * Sets the flag that controls whether a series is visible in the legend - * and sends a {@link RendererChangeEvent} to all registered listeners. + * and notifies all registered listeners. * * @param series the series index (zero-based). * @param visible the flag ({@code null} permitted). @@ -287,8 +284,7 @@ CategoryItemRendererState initialise(Graphics2D g2, /** * Sets the flag that controls whether a series is visible in the legend - * and, if requested, sends a {@link RendererChangeEvent} to all registered - * listeners. + * and, if requested, notifies all registered listeners. * * @param series the series index. * @param visible the flag ({@code null} permitted). @@ -309,8 +305,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, boolean getDefaultSeriesVisibleInLegend(); /** - * Sets the default visibility in the legend and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default visibility in the legend and notifies all registered + * listeners. * * @param visible the flag. * @@ -319,8 +315,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultSeriesVisibleInLegend(boolean visible); /** - * Sets the default visibility in the legend and, if requested, sends - * a {@link RendererChangeEvent} to all registered listeners. + * Sets the default visibility in the legend and, if requested, notifies all + * registered listeners. * * @param visible the visibility. * @param notify notify listeners? @@ -354,8 +350,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getSeriesPaint(int series); /** - * Sets the paint used for a series and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the paint used for a series and notifies all registered listeners. * * @param series the series index (zero-based). * @param paint the paint ({@code null} permitted). @@ -365,8 +360,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setSeriesPaint(int series, Paint paint); /** - * Sets the paint used for a series and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint used for a series and, if requested, notifies all + * registered listeners. * * @param series the series index (zero-based). * @param paint the paint ({@code null} permitted). @@ -386,8 +381,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getDefaultPaint(); /** - * Sets the default paint and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the default paint and notifies all registered listeners. * * @param paint the paint ({@code null} not permitted). * @@ -396,8 +390,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultPaint(Paint paint); /** - * Sets the default paint and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default paint and, if requested, notifies all registered + * listeners. * * @param paint the paint ({@code null} not permitted). * @param notify notify listeners? @@ -430,8 +424,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getSeriesFillPaint(int series); /** - * Sets the paint used for a series outline and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint used for a series outline and notifies all registered + * listeners. * * @param series the series index (zero-based). * @param paint the paint ({@code null} permitted). @@ -450,8 +444,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getDefaultFillPaint(); /** - * Sets the default outline paint and sends a {@link RendererChangeEvent} to - * all registered listeners. + * Sets the default outline paint and notifies all registered listeners. * * @param paint the paint ({@code null} not permitted). * @@ -483,8 +476,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getSeriesOutlinePaint(int series); /** - * Sets the paint used for a series outline and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint used for a series outline and notifies all registered + * listeners. * * @param series the series index (zero-based). * @param paint the paint ({@code null} permitted). @@ -494,8 +487,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setSeriesOutlinePaint(int series, Paint paint); /** - * Sets the paint used for a series outline and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint used for a series outline and, if requested, notifies all + * registered listeners. * * @param series the series index (zero-based). * @param paint the paint ({@code null} permitted). @@ -517,8 +510,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getDefaultOutlinePaint(); /** - * Sets the default outline paint and sends a {@link RendererChangeEvent} to - * all registered listeners. + * Sets the default outline paint and notifies all registered listeners. * * @param paint the paint ({@code null} not permitted). * @@ -527,8 +519,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultOutlinePaint(Paint paint); /** - * Sets the default outline paint and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default outline paint and, if requested, notifies all registered + * listeners. * * @param paint the paint ({@code null} not permitted). * @param notify notify listeners? @@ -561,8 +553,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Stroke getSeriesStroke(int series); /** - * Sets the stroke used for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the stroke used for a series and notifies all registered listeners. * * @param series the series index (zero-based). * @param stroke the stroke ({@code null} permitted). @@ -572,8 +563,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setSeriesStroke(int series, Stroke stroke); /** - * Sets the stroke used for a series and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the stroke used for a series and, if requested, notifies all + * registered listeners. * * @param series the series index (zero-based). * @param stroke the stroke ({@code null} permitted). @@ -593,8 +584,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Stroke getDefaultStroke(); /** - * Sets the default stroke and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the default stroke and notifies all registered listeners. * * @param stroke the stroke ({@code null} not permitted). * @@ -603,8 +593,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultStroke(Stroke stroke); /** - * Sets the default stroke and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default stroke and, if requested, notifies all registered + * listeners. * * @param stroke the stroke ({@code null} not permitted). * @param notify notify listeners? @@ -641,8 +631,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Stroke getSeriesOutlineStroke(int series); /** - * Sets the outline stroke used for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the outline stroke used for a series and notifies all registered + * listeners. * * @param series the series index (zero-based). * @param stroke the stroke ({@code null} permitted). @@ -652,8 +642,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setSeriesOutlineStroke(int series, Stroke stroke); /** - * Sets the outline stroke used for a series and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the outline stroke used for a series and, if requested, notifies + * all registered listeners. * * @param series the series index (zero-based). * @param stroke the stroke ({@code null} permitted). @@ -673,8 +663,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Stroke getDefaultOutlineStroke(); /** - * Sets the default outline stroke and sends a {@link RendererChangeEvent} to - * all registered listeners. + * Sets the default outline stroke and notifies all registered listeners. * * @param stroke the stroke ({@code null} not permitted). * @@ -683,8 +672,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultOutlineStroke(Stroke stroke); /** - * Sets the default outline stroke and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default outline stroke and, if requested, notifies all + * registered listeners. * * @param stroke the stroke ({@code null} not permitted). * @param notify notify listeners? @@ -717,8 +706,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Shape getSeriesShape(int series); /** - * Sets the shape used for a series and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the shape used for a series and notifies all registered listeners. * * @param series the series index (zero-based). * @param shape the shape ({@code null} permitted). @@ -728,8 +716,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setSeriesShape(int series, Shape shape); /** - * Sets the shape used for a series and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the shape used for a series and, if requested, notifies all + * registered listeners. * * @param series the series index (zero-based). * @param shape the shape ({@code null} permitted). @@ -749,8 +737,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Shape getDefaultShape(); /** - * Sets the default shape and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the default shape and notifies all registered listeners. * * @param shape the shape ({@code null} not permitted). * @@ -759,8 +746,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultShape(Shape shape); /** - * Sets the default shape and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default shape and, if requested, notifies all registered + * listeners. * * @param shape the shape ({@code null} not permitted). * @param notify notify listeners? @@ -815,8 +802,8 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setSeriesItemLabelsVisible(int series, Boolean visible); /** - * Sets the visibility of item labels for a series and, if requested, sends - * a {@link RendererChangeEvent} to all registered listeners. + * Sets the visibility of item labels for a series and, if requested, + * notifies all registered listeners. * * @param series the series index (zero-based). * @param visible the visible flag. @@ -841,8 +828,8 @@ void setSeriesItemLabelsVisible(int series, Boolean visible, boolean getDefaultItemLabelsVisible(); /** - * Sets the default flag that controls whether or not item labels are visible - * and sends a {@link RendererChangeEvent} to all registered listeners. + * Sets the default flag that controls whether or not item labels are + * visible and notifies all registered listeners. * * @param visible the flag. * @@ -851,8 +838,8 @@ void setSeriesItemLabelsVisible(int series, Boolean visible, void setDefaultItemLabelsVisible(boolean visible); /** - * Sets the default visibility for item labels and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default visibility for item labels and, if requested, notifies + * all registered listeners. * * @param visible the visibility flag. * @param notify a flag that controls whether or not listeners are @@ -887,8 +874,8 @@ CategoryItemLabelGenerator getItemLabelGenerator(int series, CategoryItemLabelGenerator getSeriesItemLabelGenerator(int series); /** - * Sets the item label generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label generator for a series and notifies all registered + * listeners. * * @param series the series index (zero-based). * @param generator the generator. @@ -899,8 +886,8 @@ void setSeriesItemLabelGenerator(int series, CategoryItemLabelGenerator generator); /** - * Sets the item label generator for a series and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label generator for a series and, if requested, notifies + * all registered listeners. * * @param series the series index (zero-based). * @param generator the generator. @@ -921,8 +908,8 @@ void setSeriesItemLabelGenerator(int series, CategoryItemLabelGenerator getDefaultItemLabelGenerator(); /** - * Sets the default item label generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item label generator and notifies all registered + * listeners. * * @param generator the generator ({@code null} permitted). * @@ -931,8 +918,8 @@ void setSeriesItemLabelGenerator(int series, void setDefaultItemLabelGenerator(CategoryItemLabelGenerator generator); /** - * Sets the default item label generator and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item label generator and, if requested, notifies all + * registered listeners. * * @param generator the generator ({@code null} permitted). * @param notify notify listeners? @@ -969,8 +956,7 @@ void setDefaultItemLabelGenerator(CategoryItemLabelGenerator generator, CategoryToolTipGenerator getSeriesToolTipGenerator(int series); /** - * Sets the tool tip generator for a series and sends a - * {@link org.jfree.chart.event.RendererChangeEvent} to all registered + * Sets the tool tip generator for a series and notifies all registered * listeners. * * @param series the series index (zero-based). @@ -982,8 +968,8 @@ void setSeriesToolTipGenerator(int series, CategoryToolTipGenerator generator); /** - * Sets the tool tip generator for a series and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the tool tip generator for a series and, if requested, notifies all + * registered listeners. * * @param series the series index (zero-based). * @param generator the generator ({@code null} permitted). @@ -1004,8 +990,7 @@ void setSeriesToolTipGenerator(int series, CategoryToolTipGenerator getDefaultToolTipGenerator(); /** - * Sets the default tool tip generator and sends a - * {@link org.jfree.chart.event.RendererChangeEvent} to all registered + * Sets the default tool tip generator and notifies all registered * listeners. * * @param generator the generator ({@code null} permitted). @@ -1015,8 +1000,8 @@ void setSeriesToolTipGenerator(int series, void setDefaultToolTipGenerator(CategoryToolTipGenerator generator); /** - * Sets the default tool tip generator and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default tool tip generator and, if requested, notifies all + * registered listeners. * * @param generator the generator ({@code null} permitted). * @param notify notify listeners? @@ -1050,8 +1035,8 @@ void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, Font getSeriesItemLabelFont(int series); /** - * Sets the item label font for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label font for a series and notifies all registered + * listeners. * * @param series the series index (zero-based). * @param font the font ({@code null} permitted). @@ -1061,8 +1046,8 @@ void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, void setSeriesItemLabelFont(int series, Font font); /** - * Sets the item label font for a series and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label font for a series and, if requested, notifies all + * registered listeners. * * @param series the series index (zero-based). * @param font the font ({@code null} permitted). @@ -1083,8 +1068,7 @@ void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, Font getDefaultItemLabelFont(); /** - * Sets the default item label font and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the default item label font and notifies all registered listeners. * * @param font the font ({@code null} not permitted). * @@ -1093,8 +1077,7 @@ void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, void setDefaultItemLabelFont(Font font); /** - * Sets the default item label font and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the default item label font and notifies all registered listeners. * * @param font the font ({@code null} not permitted). * @param notify notify listeners? @@ -1127,8 +1110,8 @@ void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, Paint getSeriesItemLabelPaint(int series); /** - * Sets the item label paint for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label paint for a series and notifies all registered + * listeners. * * @param series the series (zero based index). * @param paint the paint ({@code null} permitted). @@ -1138,8 +1121,8 @@ void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, void setSeriesItemLabelPaint(int series, Paint paint); /** - * Sets the item label paint for a series and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label paint for a series and, if requested, notifies all + * registered listeners. * * @param series the series (zero based index). * @param paint the paint ({@code null} permitted). @@ -1159,8 +1142,7 @@ void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, Paint getDefaultItemLabelPaint(); /** - * Sets the default item label paint and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the default item label paint and notifies all registered listeners. * * @param paint the paint ({@code null} not permitted). * @@ -1169,8 +1151,8 @@ void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, void setDefaultItemLabelPaint(Paint paint); /** - * Sets the default item label paint and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item label paint and, if requested, notifies all + * registered listeners. * * @param paint the paint ({@code null} not permitted). * @param notify notify listeners? @@ -1204,7 +1186,7 @@ void setDefaultToolTipGenerator(CategoryToolTipGenerator generator, /** * Sets the item label position for all positive values in a series and - * sends a {@link RendererChangeEvent} to all registered listeners. + * notifies all registered listeners. * * @param series the series index (zero-based). * @param position the position ({@code null} permitted). @@ -1216,8 +1198,7 @@ void setSeriesPositiveItemLabelPosition(int series, /** * Sets the item label position for all positive values in a series and (if - * requested) sends a {@link RendererChangeEvent} to all registered - * listeners. + * requested) notifies all registered listeners. * * @param series the series index (zero-based). * @param position the position ({@code null} permitted). @@ -1247,8 +1228,8 @@ void setSeriesPositiveItemLabelPosition(int series, void setDefaultPositiveItemLabelPosition(ItemLabelPosition position); /** - * Sets the default positive item label position and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default positive item label position and, if requested, notifies + * all registered listeners. * * @param position the position. * @param notify notify registered listeners? @@ -1285,8 +1266,8 @@ void setDefaultPositiveItemLabelPosition(ItemLabelPosition position, ItemLabelPosition getSeriesNegativeItemLabelPosition(int series); /** - * Sets the item label position for negative values in a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label position for negative values in a series and notifies + * all registered listeners. * * @param series the series index (zero-based). * @param position the position ({@code null} permitted). @@ -1298,8 +1279,7 @@ void setSeriesNegativeItemLabelPosition(int series, /** * Sets the item label position for negative values in a series and (if - * requested) sends a {@link RendererChangeEvent} to all registered - * listeners. + * requested) notifies all registered listeners. * * @param series the series index (zero-based). * @param position the position ({@code null} permitted). @@ -1320,8 +1300,8 @@ void setSeriesNegativeItemLabelPosition(int series, ItemLabelPosition getDefaultNegativeItemLabelPosition(); /** - * Sets the default item label position for negative values and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item label position for negative values and notifies all + * registered listeners. * * @param position the position. * @@ -1330,8 +1310,8 @@ void setSeriesNegativeItemLabelPosition(int series, void setDefaultNegativeItemLabelPosition(ItemLabelPosition position); /** - * Sets the default negative item label position and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default negative item label position and, if requested, notifies + * all registered listeners. * * @param position the position. * @param notify notify registered listeners? @@ -1367,10 +1347,10 @@ void setDefaultNegativeItemLabelPosition(ItemLabelPosition position, Boolean getSeriesCreateEntities(int series); /** - * Sets a flag that indicates whether or not entities should be created during - * rendering for the items in the specified series, and sends a - * {@link RendererChangeEvent} to all registered listeners. - * + * Sets a flag that indicates whether or not entities should be created + * during rendering for the items in the specified series, and notifies all + * registered listeners. + * * @param series the series index (zero-based). * @param create the new flag value ({@code null} permitted). */ @@ -1378,9 +1358,9 @@ void setDefaultNegativeItemLabelPosition(ItemLabelPosition position, /** * Sets a flag that indicates whether or not entities should be created during - * rendering for the items in the specified series, and sends a - * {@link RendererChangeEvent} to all registered listeners if requested. - * + * rendering for the items in the specified series, and notifies all + * registered listeners if requested. + * * @param series the series index (zero-based). * @param create the new flag value ({@code null} permitted). * @param notify notify listeners? @@ -1398,18 +1378,18 @@ void setSeriesCreateEntities(int series, Boolean create, /** * Sets the default value for the flag that controls whether or not an - * entity is created for an item during rendering and sends a - * {@link RendererChangeEvent} to all registered listeners. - * + * entity is created for an item during rendering and notifies all + * registered listeners. + * * @param create the new flag value. */ void setDefaultCreateEntities(boolean create); /** * Sets the default value for the flag that controls whether or not an - * entity is created for an item during rendering and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. - * + * entity is created for an item during rendering and, if requested, + * notifies all registered listeners. + * * @param create the new flag value. * @param notify notify listeners? */ @@ -1440,8 +1420,8 @@ void setSeriesCreateEntities(int series, Boolean create, CategoryURLGenerator getSeriesItemURLGenerator(int series); /** - * Sets the item URL generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item URL generator for a series and notifies all registered + * listeners. * * @param series the series index (zero-based). * @param generator the generator ({@code null} permitted). @@ -1452,8 +1432,8 @@ void setSeriesItemURLGenerator(int series, CategoryURLGenerator generator); /** - * Sets the item URL generator for a series and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item URL generator for a series and, if requested, notifies all + * registered listeners. * * @param series the series index (zero-based). * @param generator the generator ({@code null} permitted). @@ -1474,8 +1454,7 @@ void setSeriesItemURLGenerator(int series, CategoryURLGenerator getDefaultItemURLGenerator(); /** - * Sets the default item URL generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item URL generator and notifies all registered listeners. * * @param generator the item URL generator ({@code null} permitted). * @@ -1484,8 +1463,8 @@ void setSeriesItemURLGenerator(int series, void setDefaultItemURLGenerator(CategoryURLGenerator generator); /** - * Sets the default item URL generator and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item URL generator and, if requested, notifies all + * registered listeners. * * @param generator the item URL generator ({@code null} permitted). * @param notify notify listeners? diff --git a/src/main/java/org/jfree/chart/renderer/category/CategoryStepRenderer.java b/src/main/java/org/jfree/chart/renderer/category/CategoryStepRenderer.java index 80712db6e..37489ef1a 100644 --- a/src/main/java/org/jfree/chart/renderer/category/CategoryStepRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/CategoryStepRenderer.java @@ -48,17 +48,15 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.renderer.xy.XYStepRenderer; import org.jfree.chart.util.PublicCloneable; import org.jfree.data.category.CategoryDataset; /** - * A "step" renderer similar to {@link XYStepRenderer} but - * that can be used with the {@link CategoryPlot} class. The example shown + * A "step" renderer similar to {@link org.jfree.chart.renderer.xy.XYStepRenderer} + * but that can be used with the {@link CategoryPlot} class. The example shown * here is generated by the {@code CategoryStepChartDemo1.java} program * included in the JFreeChart Demo Collection: *

@@ -133,8 +131,7 @@ public boolean getStagger() { /** * Sets the flag that controls whether or not the series steps are - * staggered and sends a {@link RendererChangeEvent} to all registered - * listeners. + * staggered and calls {@link #fireChangeEvent()}. * * @param shouldStagger a boolean. */ diff --git a/src/main/java/org/jfree/chart/renderer/category/DefaultCategoryItemRenderer.java b/src/main/java/org/jfree/chart/renderer/category/DefaultCategoryItemRenderer.java index e827fb821..46cae12dc 100644 --- a/src/main/java/org/jfree/chart/renderer/category/DefaultCategoryItemRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/DefaultCategoryItemRenderer.java @@ -38,11 +38,9 @@ import java.io.Serializable; -import org.jfree.chart.plot.CategoryPlot; - /** - * A default renderer for the {@link CategoryPlot} class. This is simply an - * alias for the {@link LineAndShapeRenderer} class. + * A default renderer for the {@link org.jfree.chart.plot.CategoryPlot} class. + * This is simply an alias for the {@link LineAndShapeRenderer} class. */ public class DefaultCategoryItemRenderer extends LineAndShapeRenderer implements Serializable { diff --git a/src/main/java/org/jfree/chart/renderer/category/GanttRenderer.java b/src/main/java/org/jfree/chart/renderer/category/GanttRenderer.java index ab1be22f2..d73d44c2d 100644 --- a/src/main/java/org/jfree/chart/renderer/category/GanttRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/GanttRenderer.java @@ -49,7 +49,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.CategoryItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; @@ -114,8 +113,7 @@ public Paint getCompletePaint() { } /** - * Sets the paint used to show the percentage complete and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint used to show the percentage complete and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -139,8 +137,7 @@ public Paint getIncompletePaint() { } /** - * Sets the paint used to show the percentage incomplete and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint used to show the percentage incomplete and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -166,8 +163,7 @@ public double getStartPercent() { /** * Sets the position of the start of the progress indicator, as a - * percentage of the bar width, and sends a {@link RendererChangeEvent} to - * all registered listeners. + * percentage of the bar width, and calls {@link #fireChangeEvent()}. * * @param percent the percent. * @@ -192,8 +188,7 @@ public double getEndPercent() { /** * Sets the position of the end of the progress indicator, as a percentage - * of the bar width, and sends a {@link RendererChangeEvent} to all - * registered listeners. + * of the bar width, and calls {@link #fireChangeEvent()}. * * @param percent the percent. * diff --git a/src/main/java/org/jfree/chart/renderer/category/GroupedStackedBarRenderer.java b/src/main/java/org/jfree/chart/renderer/category/GroupedStackedBarRenderer.java index a6307a555..d706f36ed 100644 --- a/src/main/java/org/jfree/chart/renderer/category/GroupedStackedBarRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/GroupedStackedBarRenderer.java @@ -43,7 +43,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.CategoryItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; @@ -82,11 +81,11 @@ public GroupedStackedBarRenderer() { } /** - * Updates the map used to assign each series to a group, and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param map the map ({@code null} not permitted). - */ + * Updates the map used to assign each series to a group, and calls + * {@link #fireChangeEvent()}. + * + * @param map the map ({@code null} not permitted). + */ public void setSeriesToGroupMap(KeyToGroupMap map) { Args.nullNotPermitted(map, "map"); this.seriesToGroupMap = map; diff --git a/src/main/java/org/jfree/chart/renderer/category/LevelRenderer.java b/src/main/java/org/jfree/chart/renderer/category/LevelRenderer.java index c73eb9b3f..5f2f6e1d3 100644 --- a/src/main/java/org/jfree/chart/renderer/category/LevelRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/LevelRenderer.java @@ -48,7 +48,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.CategoryItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; @@ -107,15 +106,14 @@ public double getItemMargin() { } /** - * Sets the item margin and sends a {@link RendererChangeEvent} to all - * registered listeners. The value is expressed as a percentage of the - * available width for plotting all the bars, with the resulting amount to - * be distributed between all the bars evenly. - * - * @param percent the new margin. - * - * @see #getItemMargin() - */ + * Sets the item margin and calls {@link #fireChangeEvent()}. The value is + * expressed as a percentage of the available width for plotting all the bars, + * with the resulting amount to be distributed between all the bars evenly. + * + * @param percent the new margin. + * + * @see #getItemMargin() + */ public void setItemMargin(double percent) { this.itemMargin = percent; fireChangeEvent(); @@ -135,8 +133,7 @@ public double getMaximumItemWidth() { /** * Sets the maximum item width, which is specified as a percentage of the - * available space for all items, and sends a {@link RendererChangeEvent} - * to all registered listeners. + * available space for all items, and calls {@link #fireChangeEvent()}. * * @param percent the percent. * diff --git a/src/main/java/org/jfree/chart/renderer/category/LineAndShapeRenderer.java b/src/main/java/org/jfree/chart/renderer/category/LineAndShapeRenderer.java index cce456212..86588454f 100644 --- a/src/main/java/org/jfree/chart/renderer/category/LineAndShapeRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/LineAndShapeRenderer.java @@ -53,7 +53,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.util.BooleanList; @@ -195,28 +194,28 @@ public Boolean getSeriesLinesVisible(int series) { } /** - * Sets the 'lines visible' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param flag the flag ({@code null} permitted). - * - * @see #getSeriesLinesVisible(int) - */ + * Sets the 'lines visible' flag for a series and calls + * {@link #fireChangeEvent()}. + * + * @param series the series index (zero-based). + * @param flag the flag ({@code null} permitted). + * + * @see #getSeriesLinesVisible(int) + */ public void setSeriesLinesVisible(int series, Boolean flag) { this.seriesLinesVisible.setBoolean(series, flag); fireChangeEvent(); } /** - * Sets the 'lines visible' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param visible the flag. - * - * @see #getSeriesLinesVisible(int) - */ + * Sets the 'lines visible' flag for a series and calls + * {@link #fireChangeEvent()}. + * + * @param series the series index (zero-based). + * @param visible the flag. + * + * @see #getSeriesLinesVisible(int) + */ public void setSeriesLinesVisible(int series, boolean visible) { setSeriesLinesVisible(series, Boolean.valueOf(visible)); } @@ -233,8 +232,7 @@ public boolean getDefaultLinesVisible() { } /** - * Sets the default 'lines visible' flag and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default 'lines visible' flag and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -279,27 +277,27 @@ public Boolean getSeriesShapesVisible(int series) { } /** - * Sets the 'shapes visible' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param visible the flag. - * - * @see #getSeriesShapesVisible(int) - */ + * Sets the 'shapes visible' flag for a series and calls + * {@link #fireChangeEvent()}. + * + * @param series the series index (zero-based). + * @param visible the flag. + * + * @see #getSeriesShapesVisible(int) + */ public void setSeriesShapesVisible(int series, boolean visible) { setSeriesShapesVisible(series, Boolean.valueOf(visible)); } /** - * Sets the 'shapes visible' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param flag the flag. - * - * @see #getSeriesShapesVisible(int) - */ + * Sets the 'shapes visible' flag for a series and calls + * {@link #fireChangeEvent()}. + * + * @param series the series index (zero-based). + * @param flag the flag. + * + * @see #getSeriesShapesVisible(int) + */ public void setSeriesShapesVisible(int series, Boolean flag) { this.seriesShapesVisible.setBoolean(series, flag); fireChangeEvent(); @@ -317,8 +315,7 @@ public boolean getDefaultShapesVisible() { } /** - * Sets the default 'shapes visible' flag and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default 'shapes visible' flag and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -343,8 +340,7 @@ public boolean getDrawOutlines() { /** * Sets the flag that controls whether outlines are drawn for - * shapes, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * shapes, and calls {@link #fireChangeEvent()}. *

* In some cases, shapes look better if they do NOT have an outline, but * this flag allows you to set your own preference. @@ -372,8 +368,7 @@ public boolean getUseOutlinePaint() { /** * Sets the flag that controls whether the outline paint is used for shape - * outlines, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * outlines, and calls {@link #fireChangeEvent()}. * * @param use the flag. * @@ -418,8 +413,7 @@ public boolean getSeriesShapesFilled(int series) { } /** - * Sets the 'shapes filled' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'shapes filled' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param filled the flag. @@ -432,8 +426,7 @@ public void setSeriesShapesFilled(int series, Boolean filled) { } /** - * Sets the 'shapes filled' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'shapes filled' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param filled the flag. @@ -457,8 +450,7 @@ public boolean getDefaultShapesFilled() { } /** - * Sets the default 'shapes filled' flag and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default 'shapes filled' flag and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -484,8 +476,7 @@ public boolean getUseFillPaint() { /** * Sets the flag that controls whether the fill paint is used to fill - * shapes, and sends a {@link RendererChangeEvent} to all - * registered listeners. + * shapes, and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -511,7 +502,7 @@ public boolean getUseSeriesOffset() { /** * Sets the flag that controls whether or not the x-position for each * data item is offset within its category according to the series, and - * sends a {@link RendererChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param offset the offset. * @@ -538,15 +529,15 @@ public double getItemMargin() { } /** - * Sets the item margin, which is the gap between items within a category - * (expressed as a percentage of the overall category width), and sends - * a {@link RendererChangeEvent} to all registered listeners. - * - * @param margin the margin (0.0 <= margin < 1.0). - * - * @see #getItemMargin() - * @see #getUseSeriesOffset() - */ + * Sets the item margin, which is the gap between items within a category + * (expressed as a percentage of the overall category width), and calls + * {@link #fireChangeEvent()}. + * + * @param margin the margin (0.0 <= margin < 1.0). + * + * @see #getItemMargin() + * @see #getUseSeriesOffset() + */ public void setItemMargin(double margin) { if (margin < 0.0 || margin >= 1.0) { throw new IllegalArgumentException("Requires 0.0 <= margin < 1.0."); diff --git a/src/main/java/org/jfree/chart/renderer/category/MinMaxCategoryRenderer.java b/src/main/java/org/jfree/chart/renderer/category/MinMaxCategoryRenderer.java index 17f7eaf7b..e14087946 100644 --- a/src/main/java/org/jfree/chart/renderer/category/MinMaxCategoryRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/MinMaxCategoryRenderer.java @@ -60,7 +60,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.util.PaintUtils; @@ -139,8 +138,7 @@ public boolean isDrawLines() { /** * Sets the flag that controls whether or not lines are drawn to connect - * the items within a series and sends a {@link RendererChangeEvent} to - * all registered listeners. + * the items within a series and calls {@link #fireChangeEvent()}. * * @param draw the new value of the flag. * @@ -167,8 +165,7 @@ public Paint getGroupPaint() { /** * Sets the paint used to draw the line between the minimum and maximum - * value items in each category and sends a {@link RendererChangeEvent} to - * all registered listeners. + * value items in each category and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -194,8 +191,7 @@ public Stroke getGroupStroke() { /** * Sets the stroke of the line between the minimum value and the maximum - * value and sends a {@link RendererChangeEvent} to all registered - * listeners. + * value and calls {@link #fireChangeEvent()}. * * @param stroke the new stroke ({@code null} not permitted). */ @@ -217,8 +213,7 @@ public Icon getObjectIcon() { } /** - * Sets the icon drawn for each data item and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the icon drawn for each data item and calls {@link #fireChangeEvent()}. * * @param icon the icon. * @@ -244,8 +239,7 @@ public Icon getMaxIcon() { /** * Sets the icon displayed for the maximum value data item within each - * category and sends a {@link RendererChangeEvent} to all registered - * listeners. + * category and calls {@link #fireChangeEvent()}. * * @param icon the icon ({@code null} not permitted). * @@ -271,8 +265,7 @@ public Icon getMinIcon() { /** * Sets the icon displayed for the minimum value data item within each - * category and sends a {@link RendererChangeEvent} to all registered - * listeners. + * category and calls {@link #fireChangeEvent()}. * * @param icon the icon ({@code null} not permitted). * diff --git a/src/main/java/org/jfree/chart/renderer/category/ScatterRenderer.java b/src/main/java/org/jfree/chart/renderer/category/ScatterRenderer.java index f945bf1b5..e800d1595 100644 --- a/src/main/java/org/jfree/chart/renderer/category/ScatterRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/ScatterRenderer.java @@ -53,7 +53,6 @@ import org.jfree.chart.LegendItem; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.util.BooleanList; @@ -143,7 +142,7 @@ public boolean getUseSeriesOffset() { /** * Sets the flag that controls whether or not the x-position for each * data item is offset within its category according to the series, and - * sends a {@link RendererChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param offset the offset. * @@ -171,8 +170,8 @@ public double getItemMargin() { /** * Sets the item margin, which is the gap between items within a category - * (expressed as a percentage of the overall category width), and sends - * a {@link RendererChangeEvent} to all registered listeners. + * (expressed as a percentage of the overall category width), and + * calls {@link #fireChangeEvent()}. * * @param margin the margin (0.0 <= margin < 1.0). * @@ -201,8 +200,7 @@ public boolean getDrawOutlines() { /** * Sets the flag that controls whether outlines are drawn for - * shapes, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * shapes, and calls {@link #fireChangeEvent()}. *

In some cases, shapes look better if they do NOT have an outline, but * this flag allows you to set your own preference.

* @@ -229,8 +227,7 @@ public boolean getUseOutlinePaint() { /** * Sets the flag that controls whether the outline paint is used for shape - * outlines, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * outlines, and calls {@link #fireChangeEvent()}. * * @param use the flag. * @@ -276,8 +273,7 @@ public boolean getSeriesShapesFilled(int series) { } /** - * Sets the 'shapes filled' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'shapes filled' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param filled the flag. @@ -288,8 +284,7 @@ public void setSeriesShapesFilled(int series, Boolean filled) { } /** - * Sets the 'shapes filled' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'shapes filled' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param filled the flag. @@ -309,8 +304,7 @@ public boolean getBaseShapesFilled() { } /** - * Sets the base 'shapes filled' flag and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the base 'shapes filled' flag and calls {@link #fireChangeEvent()}. * * @param flag the flag. */ @@ -332,8 +326,7 @@ public boolean getUseFillPaint() { /** * Sets the flag that controls whether the fill paint is used to fill - * shapes, and sends a {@link RendererChangeEvent} to all - * registered listeners. + * shapes, and calls {@link #fireChangeEvent()}. * * @param flag the flag. */ diff --git a/src/main/java/org/jfree/chart/renderer/category/StackedAreaRenderer.java b/src/main/java/org/jfree/chart/renderer/category/StackedAreaRenderer.java index 3317fb73b..ecd0ea00f 100644 --- a/src/main/java/org/jfree/chart/renderer/category/StackedAreaRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/StackedAreaRenderer.java @@ -49,7 +49,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.ui.RectangleEdge; import org.jfree.chart.util.PublicCloneable; @@ -107,8 +106,8 @@ public boolean getRenderAsPercentages() { /** * Sets the flag that controls whether the renderer displays each item - * value as a percentage (so that the stacked areas add to 100%), and sends - * a {@link RendererChangeEvent} to all registered listeners. + * value as a percentage (so that the stacked areas add to 100%), and + * calls {@link #fireChangeEvent()}. * * @param asPercentages the flag. */ diff --git a/src/main/java/org/jfree/chart/renderer/category/StackedBarRenderer.java b/src/main/java/org/jfree/chart/renderer/category/StackedBarRenderer.java index c67d7a9bb..006a64a6f 100644 --- a/src/main/java/org/jfree/chart/renderer/category/StackedBarRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/StackedBarRenderer.java @@ -46,7 +46,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.CategoryItemLabelGenerator; import org.jfree.chart.labels.ItemLabelAnchor; import org.jfree.chart.labels.ItemLabelPosition; @@ -124,8 +123,8 @@ public boolean getRenderAsPercentages() { /** * Sets the flag that controls whether the renderer displays each item - * value as a percentage (so that the stacked bars add to 100%), and sends - * a {@link RendererChangeEvent} to all registered listeners. + * value as a percentage (so that the stacked bars add to 100%), and + * calls {@link #fireChangeEvent()}. * * @param asPercentages the flag. * diff --git a/src/main/java/org/jfree/chart/renderer/category/StatisticalBarRenderer.java b/src/main/java/org/jfree/chart/renderer/category/StatisticalBarRenderer.java index 64429de2b..2b3822490 100644 --- a/src/main/java/org/jfree/chart/renderer/category/StatisticalBarRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/StatisticalBarRenderer.java @@ -56,7 +56,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.CategoryItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; @@ -116,8 +115,7 @@ public Paint getErrorIndicatorPaint() { /** * Sets the paint used for the error indicators (if {@code null}, - * the item outline paint is used instead) and sends a - * {@link RendererChangeEvent} to all registered listeners. + * the item outline paint is used instead) and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} permitted). * @@ -141,15 +139,14 @@ public Stroke getErrorIndicatorStroke() { } /** - * Sets the stroke used to draw the error indicators, and sends a - * {@link RendererChangeEvent} to all registered listeners. If you set - * this to {@code null}, the renderer will use the item outline - * stroke. - * - * @param stroke the stroke ({@code null} permitted). - * - * @see #getErrorIndicatorStroke() - */ + * Sets the stroke used to draw the error indicators, and calls + * {@link #fireChangeEvent()}. If you set this to {@code null}, the renderer + * will use the item outline stroke. + * + * @param stroke the stroke ({@code null} permitted). + * + * @see #getErrorIndicatorStroke() + */ public void setErrorIndicatorStroke(Stroke stroke) { this.errorIndicatorStroke = stroke; fireChangeEvent(); diff --git a/src/main/java/org/jfree/chart/renderer/category/StatisticalLineAndShapeRenderer.java b/src/main/java/org/jfree/chart/renderer/category/StatisticalLineAndShapeRenderer.java index 30107abdd..c8085ed26 100644 --- a/src/main/java/org/jfree/chart/renderer/category/StatisticalLineAndShapeRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/StatisticalLineAndShapeRenderer.java @@ -53,7 +53,6 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.ui.RectangleEdge; @@ -124,8 +123,7 @@ public Paint getErrorIndicatorPaint() { /** * Sets the paint used for the error indicators (if {@code null}, - * the item paint is used instead) and sends a - * {@link RendererChangeEvent} to all registered listeners. + * the item paint is used instead) and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} permitted). * @@ -150,8 +148,7 @@ public Stroke getErrorIndicatorStroke() { /** * Sets the stroke used for the error indicators (if {@code null}, - * the item outline stroke is used instead) and sends a - * {@link RendererChangeEvent} to all registered listeners. + * the item outline stroke is used instead) and calls {@link #fireChangeEvent()}. * * @param stroke the stroke ({@code null} permitted). * diff --git a/src/main/java/org/jfree/chart/renderer/category/WaterfallBarRenderer.java b/src/main/java/org/jfree/chart/renderer/category/WaterfallBarRenderer.java index 7e49302a2..7ce410deb 100644 --- a/src/main/java/org/jfree/chart/renderer/category/WaterfallBarRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/category/WaterfallBarRenderer.java @@ -49,11 +49,9 @@ import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.CategoryItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.renderer.AbstractRenderer; import org.jfree.chart.ui.GradientPaintTransformType; import org.jfree.chart.ui.RectangleEdge; import org.jfree.chart.ui.StandardGradientPaintTransformer; @@ -72,7 +70,8 @@ * the final bar in the plot will be incorrectly plotted; *
  • the bar colors are defined using special methods in this class - the * inherited methods (for example, - * {@link AbstractRenderer#setSeriesPaint(int, Paint)}) are ignored;
  • + * {@link org.jfree.chart.renderer.AbstractRenderer#setSeriesPaint(int, Paint)}) + * are ignored; * * The example shown here is generated by the * {@code WaterfallChartDemo1.java} program included in the JFreeChart @@ -150,8 +149,8 @@ public Paint getFirstBarPaint() { } /** - * Sets the paint that will be used to draw the first bar and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint that will be used to draw the first bar and calls + * {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). */ @@ -171,8 +170,8 @@ public Paint getLastBarPaint() { } /** - * Sets the paint that will be used to draw the last bar and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint that will be used to draw the last bar and calls + * {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). */ @@ -213,7 +212,7 @@ public Paint getNegativeBarPaint() { /** * Sets the paint that will be used to draw bars having negative values, - * and sends a {@link RendererChangeEvent} to all registered listeners. + * and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). */ diff --git a/src/main/java/org/jfree/chart/renderer/xy/AbstractXYItemRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/AbstractXYItemRenderer.java index 925e846b1..001e96d0e 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/AbstractXYItemRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/AbstractXYItemRenderer.java @@ -1,1673 +1,1659 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------------------------- - * AbstractXYItemRenderer.java - * --------------------------- - * (C) Copyright 2002-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): Richard Atkinson; - * Focus Computer Services Limited; - * Tim Bardzil; - * Sergei Ivanov; - * Peter Kolb (patch 2809117); - * Martin Krauskopf; - */ - -package org.jfree.chart.renderer.xy; - -import java.awt.AlphaComposite; -import java.awt.Composite; -import java.awt.Font; -import java.awt.GradientPaint; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.RenderingHints; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Ellipse2D; -import java.awt.geom.GeneralPath; -import java.awt.geom.Line2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import org.jfree.chart.LegendItem; -import org.jfree.chart.LegendItemCollection; -import org.jfree.chart.annotations.Annotation; -import org.jfree.chart.annotations.XYAnnotation; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.entity.XYItemEntity; -import org.jfree.chart.event.AnnotationChangeEvent; -import org.jfree.chart.event.AnnotationChangeListener; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.labels.ItemLabelPosition; -import org.jfree.chart.labels.StandardXYSeriesLabelGenerator; -import org.jfree.chart.labels.XYItemLabelGenerator; -import org.jfree.chart.labels.XYSeriesLabelGenerator; -import org.jfree.chart.labels.XYToolTipGenerator; -import org.jfree.chart.plot.CrosshairState; -import org.jfree.chart.plot.DrawingSupplier; -import org.jfree.chart.plot.IntervalMarker; -import org.jfree.chart.plot.Marker; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.ValueMarker; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.renderer.AbstractRenderer; -import org.jfree.chart.text.TextUtils; -import org.jfree.chart.ui.GradientPaintTransformer; -import org.jfree.chart.ui.Layer; -import org.jfree.chart.ui.LengthAdjustmentType; -import org.jfree.chart.ui.RectangleAnchor; -import org.jfree.chart.ui.RectangleInsets; -import org.jfree.chart.urls.XYURLGenerator; -import org.jfree.chart.util.CloneUtils; -import org.jfree.chart.util.ObjectUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.data.Range; -import org.jfree.data.general.DatasetUtils; -import org.jfree.data.xy.XYDataset; -import org.jfree.data.xy.XYItemKey; - -/** - * A base class that can be used to create new {@link XYItemRenderer} - * implementations. - */ -public abstract class AbstractXYItemRenderer extends AbstractRenderer - implements XYItemRenderer, AnnotationChangeListener, - Cloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = 8019124836026607990L; - - /** The plot. */ - private XYPlot plot; - - /** A list of item label generators (one per series). */ - private Map itemLabelGeneratorMap; - - /** The default item label generator. */ - private XYItemLabelGenerator defaultItemLabelGenerator; - - /** A list of tool tip generators (one per series). */ - private Map toolTipGeneratorMap; - - /** The default tool tip generator. */ - private XYToolTipGenerator defaultToolTipGenerator; - - /** The URL text generator. */ - private XYURLGenerator urlGenerator; - - /** - * Annotations to be drawn in the background layer ('underneath' the data - * items). - */ - private List backgroundAnnotations; - - /** - * Annotations to be drawn in the foreground layer ('on top' of the data - * items). - */ - private List foregroundAnnotations; - - /** The legend item label generator. */ - private XYSeriesLabelGenerator legendItemLabelGenerator; - - /** The legend item tool tip generator. */ - private XYSeriesLabelGenerator legendItemToolTipGenerator; - - /** The legend item URL generator. */ - private XYSeriesLabelGenerator legendItemURLGenerator; - - /** - * Creates a renderer where the tooltip generator and the URL generator are - * both {@code null}. - */ - protected AbstractXYItemRenderer() { - super(); - this.itemLabelGeneratorMap = new HashMap<>(); - this.toolTipGeneratorMap = new HashMap<>(); - this.urlGenerator = null; - this.backgroundAnnotations = new java.util.ArrayList(); - this.foregroundAnnotations = new java.util.ArrayList(); - this.legendItemLabelGenerator = new StandardXYSeriesLabelGenerator( - "{0}"); - } - - /** - * Returns the number of passes through the data that the renderer requires - * in order to draw the chart. Most charts will require a single pass, but - * some require two passes. - * - * @return The pass count. - */ - @Override - public int getPassCount() { - return 1; - } - - /** - * Returns the plot that the renderer is assigned to. - * - * @return The plot (possibly {@code null}). - */ - @Override - public XYPlot getPlot() { - return this.plot; - } - - /** - * Sets the plot that the renderer is assigned to. - * - * @param plot the plot ({@code null} permitted). - */ - @Override - public void setPlot(XYPlot plot) { - this.plot = plot; - } - - /** - * Initialises the renderer and returns a state object that should be - * passed to all subsequent calls to the drawItem() method. - *

    - * This method will be called before the first item is rendered, giving the - * renderer an opportunity to initialise any state information it wants to - * maintain. The renderer can do nothing if it chooses. - * - * @param g2 the graphics device. - * @param dataArea the area inside the axes. - * @param plot the plot. - * @param dataset the dataset. - * @param info an optional info collection object to return data back to - * the caller. - * - * @return The renderer state (never {@code null}). - */ - @Override - public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, - XYPlot plot, XYDataset dataset, PlotRenderingInfo info) { - return new XYItemRendererState(info); - } - - /** - * Adds a {@code KEY_BEGIN_ELEMENT} hint to the graphics target. This - * hint is recognised by JFreeSVG (in theory it could be used by - * other {@code Graphics2D} implementations also). - * - * @param g2 the graphics target ({@code null} not permitted). - * @param seriesKey the series key that identifies the element - * ({@code null} not permitted). - * @param itemIndex the item index. - */ - protected void beginElementGroup(Graphics2D g2, Comparable seriesKey, - int itemIndex) { - beginElementGroup(g2, new XYItemKey(seriesKey, itemIndex)); - } - - // ITEM LABEL GENERATOR - - /** - * Returns the label generator for a data item. This implementation simply - * passes control to the {@link #getSeriesItemLabelGenerator(int)} method. - * If, for some reason, you want a different generator for individual - * items, you can override this method. - * - * @param series the series index (zero based). - * @param item the item index (zero based). - * - * @return The generator (possibly {@code null}). - */ - @Override - public XYItemLabelGenerator getItemLabelGenerator(int series, int item) { - - // otherwise look up the generator table - XYItemLabelGenerator generator = this.itemLabelGeneratorMap.get(series); - if (generator == null) { - generator = this.defaultItemLabelGenerator; - } - return generator; - } - - /** - * Returns the item label generator for a series. - * - * @param series the series index (zero based). - * - * @return The generator (possibly {@code null}). - */ - @Override - public XYItemLabelGenerator getSeriesItemLabelGenerator(int series) { - return this.itemLabelGeneratorMap.get(series); - } - - /** - * Sets the item label generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero based). - * @param generator the generator ({@code null} permitted). - */ - @Override - public void setSeriesItemLabelGenerator(int series, - XYItemLabelGenerator generator) { - this.itemLabelGeneratorMap.put(series, generator); - fireChangeEvent(); - } - - /** - * Returns the default item label generator. - * - * @return The generator (possibly {@code null}). - */ - @Override - public XYItemLabelGenerator getDefaultItemLabelGenerator() { - return this.defaultItemLabelGenerator; - } - - /** - * Sets the default item label generator and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - */ - @Override - public void setDefaultItemLabelGenerator(XYItemLabelGenerator generator) { - this.defaultItemLabelGenerator = generator; - fireChangeEvent(); - } - - // TOOL TIP GENERATOR - - /** - * Returns the tool tip generator for a data item. If, for some reason, - * you want a different generator for individual items, you can override - * this method. - * - * @param series the series index (zero based). - * @param item the item index (zero based). - * - * @return The generator (possibly {@code null}). - */ - @Override - public XYToolTipGenerator getToolTipGenerator(int series, int item) { - - // otherwise look up the generator table - XYToolTipGenerator generator = this.toolTipGeneratorMap.get(series); - if (generator == null) { - generator = this.defaultToolTipGenerator; - } - return generator; - } - - /** - * Returns the tool tip generator for a series. - * - * @param series the series index (zero based). - * - * @return The generator (possibly {@code null}). - */ - @Override - public XYToolTipGenerator getSeriesToolTipGenerator(int series) { - return this.toolTipGeneratorMap.get(series); - } - - /** - * Sets the tool tip generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero based). - * @param generator the generator ({@code null} permitted). - */ - @Override - public void setSeriesToolTipGenerator(int series, - XYToolTipGenerator generator) { - this.toolTipGeneratorMap.put(series, generator); - fireChangeEvent(); - } - - /** - * Returns the default tool tip generator. - * - * @return The generator (possibly {@code null}). - * - * @see #setDefaultToolTipGenerator(XYToolTipGenerator) - */ - @Override - public XYToolTipGenerator getDefaultToolTipGenerator() { - return this.defaultToolTipGenerator; - } - - /** - * Sets the default tool tip generator and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - * - * @see #getDefaultToolTipGenerator() - */ - @Override - public void setDefaultToolTipGenerator(XYToolTipGenerator generator) { - this.defaultToolTipGenerator = generator; - fireChangeEvent(); - } - - // URL GENERATOR - - /** - * Returns the URL generator for HTML image maps. - * - * @return The URL generator (possibly {@code null}). - */ - @Override - public XYURLGenerator getURLGenerator() { - return this.urlGenerator; - } - - /** - * Sets the URL generator for HTML image maps and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param urlGenerator the URL generator ({@code null} permitted). - */ - @Override - public void setURLGenerator(XYURLGenerator urlGenerator) { - this.urlGenerator = urlGenerator; - fireChangeEvent(); - } - - /** - * Adds an annotation and sends a {@link RendererChangeEvent} to all - * registered listeners. The annotation is added to the foreground - * layer. - * - * @param annotation the annotation ({@code null} not permitted). - */ - @Override - public void addAnnotation(XYAnnotation annotation) { - // defer argument checking - addAnnotation(annotation, Layer.FOREGROUND); - } - - /** - * Adds an annotation to the specified layer and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param annotation the annotation ({@code null} not permitted). - * @param layer the layer ({@code null} not permitted). - */ - @Override - public void addAnnotation(XYAnnotation annotation, Layer layer) { - Args.nullNotPermitted(annotation, "annotation"); - if (layer.equals(Layer.FOREGROUND)) { - this.foregroundAnnotations.add(annotation); - annotation.addChangeListener(this); - fireChangeEvent(); - } - else if (layer.equals(Layer.BACKGROUND)) { - this.backgroundAnnotations.add(annotation); - annotation.addChangeListener(this); - fireChangeEvent(); - } - else { - // should never get here - throw new RuntimeException("Unknown layer."); - } - } - /** - * Removes the specified annotation and sends a {@link RendererChangeEvent} - * to all registered listeners. - * - * @param annotation the annotation to remove ({@code null} not - * permitted). - * - * @return A boolean to indicate whether or not the annotation was - * successfully removed. - */ - @Override - public boolean removeAnnotation(XYAnnotation annotation) { - boolean removed = this.foregroundAnnotations.remove(annotation); - removed = removed & this.backgroundAnnotations.remove(annotation); - annotation.removeChangeListener(this); - fireChangeEvent(); - return removed; - } - - /** - * Removes all annotations and sends a {@link RendererChangeEvent} - * to all registered listeners. - */ - @Override - public void removeAnnotations() { - for(int i = 0; i < this.foregroundAnnotations.size(); i++){ - XYAnnotation annotation - = (XYAnnotation) this.foregroundAnnotations.get(i); - annotation.removeChangeListener(this); - } - for(int i = 0; i < this.backgroundAnnotations.size(); i++){ - XYAnnotation annotation - = (XYAnnotation) this.backgroundAnnotations.get(i); - annotation.removeChangeListener(this); - } - this.foregroundAnnotations.clear(); - this.backgroundAnnotations.clear(); - fireChangeEvent(); - } - - - /** - * Receives notification of a change to an {@link Annotation} added to - * this renderer. - * - * @param event information about the event (not used here). - */ - @Override - public void annotationChanged(AnnotationChangeEvent event) { - fireChangeEvent(); - } - - /** - * Returns a collection of the annotations that are assigned to the - * renderer. - * - * @return A collection of annotations (possibly empty but never - * {@code null}). - */ - public Collection getAnnotations() { - List result = new java.util.ArrayList(this.foregroundAnnotations); - result.addAll(this.backgroundAnnotations); - return result; - } - - /** - * Returns the legend item label generator. - * - * @return The label generator (never {@code null}). - * - * @see #setLegendItemLabelGenerator(XYSeriesLabelGenerator) - */ - @Override - public XYSeriesLabelGenerator getLegendItemLabelGenerator() { - return this.legendItemLabelGenerator; - } - - /** - * Sets the legend item label generator and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} not permitted). - * - * @see #getLegendItemLabelGenerator() - */ - @Override - public void setLegendItemLabelGenerator(XYSeriesLabelGenerator generator) { - Args.nullNotPermitted(generator, "generator"); - this.legendItemLabelGenerator = generator; - fireChangeEvent(); - } - - /** - * Returns the legend item tool tip generator. - * - * @return The tool tip generator (possibly {@code null}). - * - * @see #setLegendItemToolTipGenerator(XYSeriesLabelGenerator) - */ - public XYSeriesLabelGenerator getLegendItemToolTipGenerator() { - return this.legendItemToolTipGenerator; - } - - /** - * Sets the legend item tool tip generator and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - * - * @see #getLegendItemToolTipGenerator() - */ - public void setLegendItemToolTipGenerator( - XYSeriesLabelGenerator generator) { - this.legendItemToolTipGenerator = generator; - fireChangeEvent(); - } - - /** - * Returns the legend item URL generator. - * - * @return The URL generator (possibly {@code null}). - * - * @see #setLegendItemURLGenerator(XYSeriesLabelGenerator) - */ - public XYSeriesLabelGenerator getLegendItemURLGenerator() { - return this.legendItemURLGenerator; - } - - /** - * Sets the legend item URL generator and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param generator the generator ({@code null} permitted). - * - * @see #getLegendItemURLGenerator() - */ - public void setLegendItemURLGenerator(XYSeriesLabelGenerator generator) { - this.legendItemURLGenerator = generator; - fireChangeEvent(); - } - - /** - * Returns the lower and upper bounds (range) of the x-values in the - * specified dataset. - * - * @param dataset the dataset ({@code null} permitted). - * - * @return The range ({@code null} if the dataset is {@code null} - * or empty). - * - * @see #findRangeBounds(XYDataset) - */ - @Override - public Range findDomainBounds(XYDataset dataset) { - return findDomainBounds(dataset, false); - } - - /** - * Returns the lower and upper bounds (range) of the x-values in the - * specified dataset. - * - * @param dataset the dataset ({@code null} permitted). - * @param includeInterval include the interval (if any) for the dataset? - * - * @return The range ({@code null} if the dataset is {@code null} - * or empty). - */ - protected Range findDomainBounds(XYDataset dataset, - boolean includeInterval) { - if (dataset == null) { - return null; - } - if (getDataBoundsIncludesVisibleSeriesOnly()) { - List visibleSeriesKeys = new ArrayList(); - int seriesCount = dataset.getSeriesCount(); - for (int s = 0; s < seriesCount; s++) { - if (isSeriesVisible(s)) { - visibleSeriesKeys.add(dataset.getSeriesKey(s)); - } - } - return DatasetUtils.findDomainBounds(dataset, - visibleSeriesKeys, includeInterval); - } - return DatasetUtils.findDomainBounds(dataset, includeInterval); - } - - /** - * Returns the range of values the renderer requires to display all the - * items from the specified dataset. - * - * @param dataset the dataset ({@code null} permitted). - * - * @return The range ({@code null} if the dataset is {@code null} - * or empty). - * - * @see #findDomainBounds(XYDataset) - */ - @Override - public Range findRangeBounds(XYDataset dataset) { - return findRangeBounds(dataset, false); - } - - /** - * Returns the range of values the renderer requires to display all the - * items from the specified dataset. - * - * @param dataset the dataset ({@code null} permitted). - * @param includeInterval include the interval (if any) for the dataset? - * - * @return The range ({@code null} if the dataset is {@code null} - * or empty). - */ - protected Range findRangeBounds(XYDataset dataset, - boolean includeInterval) { - if (dataset == null) { - return null; - } - if (getDataBoundsIncludesVisibleSeriesOnly()) { - List visibleSeriesKeys = new ArrayList(); - int seriesCount = dataset.getSeriesCount(); - for (int s = 0; s < seriesCount; s++) { - if (isSeriesVisible(s)) { - visibleSeriesKeys.add(dataset.getSeriesKey(s)); - } - } - // the bounds should be calculated using just the items within - // the current range of the x-axis...if there is one - Range xRange = null; - XYPlot p = getPlot(); - if (p != null) { - ValueAxis xAxis = null; - int index = p.getIndexOf(this); - if (index >= 0) { - xAxis = this.plot.getDomainAxisForDataset(index); - } - if (xAxis != null) { - xRange = xAxis.getRange(); - } - } - if (xRange == null) { - xRange = new Range(Double.NEGATIVE_INFINITY, - Double.POSITIVE_INFINITY); - } - return DatasetUtils.findRangeBounds(dataset, - visibleSeriesKeys, xRange, includeInterval); - } - return DatasetUtils.findRangeBounds(dataset, includeInterval); - } - - /** - * Returns a (possibly empty) collection of legend items for the series - * that this renderer is responsible for drawing. - * - * @return The legend item collection (never {@code null}). - */ - @Override - public LegendItemCollection getLegendItems() { - if (this.plot == null) { - return new LegendItemCollection(); - } - LegendItemCollection result = new LegendItemCollection(); - int index = this.plot.getIndexOf(this); - XYDataset dataset = this.plot.getDataset(index); - if (dataset != null) { - int seriesCount = dataset.getSeriesCount(); - for (int i = 0; i < seriesCount; i++) { - if (isSeriesVisibleInLegend(i)) { - LegendItem item = getLegendItem(index, i); - if (item != null) { - result.add(item); - } - } - } - - } - return result; - } - - /** - * Returns a default legend item for the specified series. Subclasses - * should override this method to generate customised items. - * - * @param datasetIndex the dataset index (zero-based). - * @param series the series index (zero-based). - * - * @return A legend item for the series. - */ - @Override - public LegendItem getLegendItem(int datasetIndex, int series) { - XYPlot xyplot = getPlot(); - if (xyplot == null) { - return null; - } - XYDataset dataset = xyplot.getDataset(datasetIndex); - if (dataset == null) { - return null; - } - String label = this.legendItemLabelGenerator.generateLabel(dataset, - series); - String description = label; - String toolTipText = null; - if (getLegendItemToolTipGenerator() != null) { - toolTipText = getLegendItemToolTipGenerator().generateLabel( - dataset, series); - } - String urlText = null; - if (getLegendItemURLGenerator() != null) { - urlText = getLegendItemURLGenerator().generateLabel(dataset, - series); - } - Shape shape = lookupLegendShape(series); - Paint paint = lookupSeriesPaint(series); - LegendItem item = new LegendItem(label, paint); - item.setToolTipText(toolTipText); - item.setURLText(urlText); - item.setLabelFont(lookupLegendTextFont(series)); - Paint labelPaint = lookupLegendTextPaint(series); - if (labelPaint != null) { - item.setLabelPaint(labelPaint); - } - item.setSeriesKey(dataset.getSeriesKey(series)); - item.setSeriesIndex(series); - item.setDataset(dataset); - item.setDatasetIndex(datasetIndex); - - if (getTreatLegendShapeAsLine()) { - item.setLineVisible(true); - item.setLine(shape); - item.setLinePaint(paint); - item.setShapeVisible(false); - } else { - Paint outlinePaint = lookupSeriesOutlinePaint(series); - Stroke outlineStroke = lookupSeriesOutlineStroke(series); - item.setOutlinePaint(outlinePaint); - item.setOutlineStroke(outlineStroke); - } - return item; - } - - /** - * Fills a band between two values on the axis. This can be used to color - * bands between the grid lines. - * - * @param g2 the graphics device. - * @param plot the plot. - * @param axis the domain axis. - * @param dataArea the data area. - * @param start the start value. - * @param end the end value. - */ - @Override - public void fillDomainGridBand(Graphics2D g2, XYPlot plot, ValueAxis axis, - Rectangle2D dataArea, double start, double end) { - - double x1 = axis.valueToJava2D(start, dataArea, - plot.getDomainAxisEdge()); - double x2 = axis.valueToJava2D(end, dataArea, - plot.getDomainAxisEdge()); - Rectangle2D band; - if (plot.getOrientation() == PlotOrientation.VERTICAL) { - band = new Rectangle2D.Double(Math.min(x1, x2), dataArea.getMinY(), - Math.abs(x2 - x1), dataArea.getHeight()); - } - else { - band = new Rectangle2D.Double(dataArea.getMinX(), Math.min(x1, x2), - dataArea.getWidth(), Math.abs(x2 - x1)); - } - Paint paint = plot.getDomainTickBandPaint(); - - if (paint != null) { - g2.setPaint(paint); - g2.fill(band); - } - - } - - /** - * Fills a band between two values on the range axis. This can be used to - * color bands between the grid lines. - * - * @param g2 the graphics device. - * @param plot the plot. - * @param axis the range axis. - * @param dataArea the data area. - * @param start the start value. - * @param end the end value. - */ - @Override - public void fillRangeGridBand(Graphics2D g2, XYPlot plot, ValueAxis axis, - Rectangle2D dataArea, double start, double end) { - - double y1 = axis.valueToJava2D(start, dataArea, - plot.getRangeAxisEdge()); - double y2 = axis.valueToJava2D(end, dataArea, plot.getRangeAxisEdge()); - Rectangle2D band; - if (plot.getOrientation() == PlotOrientation.VERTICAL) { - band = new Rectangle2D.Double(dataArea.getMinX(), Math.min(y1, y2), - dataArea.getWidth(), Math.abs(y2 - y1)); - } - else { - band = new Rectangle2D.Double(Math.min(y1, y2), dataArea.getMinY(), - Math.abs(y2 - y1), dataArea.getHeight()); - } - Paint paint = plot.getRangeTickBandPaint(); - - if (paint != null) { - g2.setPaint(paint); - g2.fill(band); - } - - } - - /** - * Draws a line perpendicular to the domain axis. - * - * @param g2 the graphics device. - * @param plot the plot. - * @param axis the value axis. - * @param dataArea the area for plotting data. - * @param value the value at which the grid line should be drawn. - * @param paint the paint ({@code null} not permitted). - * @param stroke the stroke ({@code null} not permitted). - */ - @Override - public void drawDomainLine(Graphics2D g2, XYPlot plot, ValueAxis axis, - Rectangle2D dataArea, double value, Paint paint, Stroke stroke) { - - Range range = axis.getRange(); - if (!range.contains(value)) { - return; - } - - PlotOrientation orientation = plot.getOrientation(); - Line2D line = null; - double v = axis.valueToJava2D(value, dataArea, - plot.getDomainAxisEdge()); - if (orientation.isHorizontal()) { - line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), - v); - } else if (orientation.isVertical()) { - line = new Line2D.Double(v, dataArea.getMinY(), v, - dataArea.getMaxY()); - } - - g2.setPaint(paint); - g2.setStroke(stroke); - Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_NORMALIZE); - g2.draw(line); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); - } - - /** - * Draws a line perpendicular to the range axis. - * - * @param g2 the graphics device. - * @param plot the plot. - * @param axis the value axis. - * @param dataArea the area for plotting data. - * @param value the value at which the grid line should be drawn. - * @param paint the paint. - * @param stroke the stroke. - */ - @Override - public void drawRangeLine(Graphics2D g2, XYPlot plot, ValueAxis axis, - Rectangle2D dataArea, double value, Paint paint, Stroke stroke) { - - Range range = axis.getRange(); - if (!range.contains(value)) { - return; - } - - PlotOrientation orientation = plot.getOrientation(); - Line2D line = null; - double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge()); - if (orientation == PlotOrientation.HORIZONTAL) { - line = new Line2D.Double(v, dataArea.getMinY(), v, - dataArea.getMaxY()); - } else if (orientation == PlotOrientation.VERTICAL) { - line = new Line2D.Double(dataArea.getMinX(), v, - dataArea.getMaxX(), v); - } - - g2.setPaint(paint); - g2.setStroke(stroke); - Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_NORMALIZE); - g2.draw(line); - g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); - } - - /** - * Draws a line on the chart perpendicular to the x-axis to mark - * a value or range of values. - * - * @param g2 the graphics device. - * @param plot the plot. - * @param domainAxis the domain axis. - * @param marker the marker line. - * @param dataArea the axis data area. - */ - @Override - public void drawDomainMarker(Graphics2D g2, XYPlot plot, - ValueAxis domainAxis, Marker marker, Rectangle2D dataArea) { - - if (marker instanceof ValueMarker) { - ValueMarker vm = (ValueMarker) marker; - double value = vm.getValue(); - Range range = domainAxis.getRange(); - if (!range.contains(value)) { - return; - } - - double v = domainAxis.valueToJava2D(value, dataArea, - plot.getDomainAxisEdge()); - PlotOrientation orientation = plot.getOrientation(); - Line2D line = null; - if (orientation == PlotOrientation.HORIZONTAL) { - line = new Line2D.Double(dataArea.getMinX(), v, - dataArea.getMaxX(), v); - } else if (orientation == PlotOrientation.VERTICAL) { - line = new Line2D.Double(v, dataArea.getMinY(), v, - dataArea.getMaxY()); - } else { - throw new IllegalStateException("Unrecognised orientation."); - } - - final Composite originalComposite = g2.getComposite(); - g2.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, marker.getAlpha())); - g2.setPaint(marker.getPaint()); - g2.setStroke(marker.getStroke()); - g2.draw(line); - - String label = marker.getLabel(); - RectangleAnchor anchor = marker.getLabelAnchor(); - if (label != null) { - Font labelFont = marker.getLabelFont(); - g2.setFont(labelFont); - Point2D coords = calculateDomainMarkerTextAnchorPoint( - g2, orientation, dataArea, line.getBounds2D(), - marker.getLabelOffset(), - LengthAdjustmentType.EXPAND, anchor); - Rectangle2D r = TextUtils.calcAlignedStringBounds(label, - g2, (float) coords.getX(), (float) coords.getY(), - marker.getLabelTextAnchor()); - g2.setPaint(marker.getLabelBackgroundColor()); - g2.fill(r); - g2.setPaint(marker.getLabelPaint()); - TextUtils.drawAlignedString(label, g2, - (float) coords.getX(), (float) coords.getY(), - marker.getLabelTextAnchor()); - } - g2.setComposite(originalComposite); - } else if (marker instanceof IntervalMarker) { - IntervalMarker im = (IntervalMarker) marker; - double start = im.getStartValue(); - double end = im.getEndValue(); - Range range = domainAxis.getRange(); - if (!(range.intersects(start, end))) { - return; - } - - double start2d = domainAxis.valueToJava2D(start, dataArea, - plot.getDomainAxisEdge()); - double end2d = domainAxis.valueToJava2D(end, dataArea, - plot.getDomainAxisEdge()); - double low = Math.min(start2d, end2d); - double high = Math.max(start2d, end2d); - - PlotOrientation orientation = plot.getOrientation(); - Rectangle2D rect = null; - if (orientation == PlotOrientation.HORIZONTAL) { - // clip top and bottom bounds to data area - low = Math.max(low, dataArea.getMinY()); - high = Math.min(high, dataArea.getMaxY()); - rect = new Rectangle2D.Double(dataArea.getMinX(), - low, dataArea.getWidth(), - high - low); - } else if (orientation == PlotOrientation.VERTICAL) { - // clip left and right bounds to data area - low = Math.max(low, dataArea.getMinX()); - high = Math.min(high, dataArea.getMaxX()); - rect = new Rectangle2D.Double(low, - dataArea.getMinY(), high - low, - dataArea.getHeight()); - } - - final Composite originalComposite = g2.getComposite(); - g2.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, marker.getAlpha())); - Paint p = marker.getPaint(); - if (p instanceof GradientPaint) { - GradientPaint gp = (GradientPaint) p; - GradientPaintTransformer t = im.getGradientPaintTransformer(); - if (t != null) { - gp = t.transform(gp, rect); - } - g2.setPaint(gp); - } else { - g2.setPaint(p); - } - g2.fill(rect); - - // now draw the outlines, if visible... - if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) { - if (orientation == PlotOrientation.VERTICAL) { - Line2D line = new Line2D.Double(); - double y0 = dataArea.getMinY(); - double y1 = dataArea.getMaxY(); - g2.setPaint(im.getOutlinePaint()); - g2.setStroke(im.getOutlineStroke()); - if (range.contains(start)) { - line.setLine(start2d, y0, start2d, y1); - g2.draw(line); - } - if (range.contains(end)) { - line.setLine(end2d, y0, end2d, y1); - g2.draw(line); - } - } else { // PlotOrientation.HORIZONTAL - Line2D line = new Line2D.Double(); - double x0 = dataArea.getMinX(); - double x1 = dataArea.getMaxX(); - g2.setPaint(im.getOutlinePaint()); - g2.setStroke(im.getOutlineStroke()); - if (range.contains(start)) { - line.setLine(x0, start2d, x1, start2d); - g2.draw(line); - } - if (range.contains(end)) { - line.setLine(x0, end2d, x1, end2d); - g2.draw(line); - } - } - } - - String label = marker.getLabel(); - RectangleAnchor anchor = marker.getLabelAnchor(); - if (label != null) { - Font labelFont = marker.getLabelFont(); - g2.setFont(labelFont); - Point2D coords = calculateDomainMarkerTextAnchorPoint( - g2, orientation, dataArea, rect, - marker.getLabelOffset(), marker.getLabelOffsetType(), - anchor); - Rectangle2D r = TextUtils.calcAlignedStringBounds(label, - g2, (float) coords.getX(), (float) coords.getY(), - marker.getLabelTextAnchor()); - g2.setPaint(marker.getLabelBackgroundColor()); - g2.fill(r); - g2.setPaint(marker.getLabelPaint()); - TextUtils.drawAlignedString(label, g2, - (float) coords.getX(), (float) coords.getY(), - marker.getLabelTextAnchor()); - } - g2.setComposite(originalComposite); - } - } - - /** - * Calculates the {@code (x, y)} coordinates for drawing a marker label. - * - * @param g2 the graphics device. - * @param orientation the plot orientation. - * @param dataArea the data area. - * @param markerArea the rectangle surrounding the marker area. - * @param markerOffset the marker label offset. - * @param labelOffsetType the label offset type. - * @param anchor the label anchor. - * - * @return The coordinates for drawing the marker label. - */ - protected Point2D calculateDomainMarkerTextAnchorPoint(Graphics2D g2, - PlotOrientation orientation, Rectangle2D dataArea, - Rectangle2D markerArea, RectangleInsets markerOffset, - LengthAdjustmentType labelOffsetType, RectangleAnchor anchor) { - - Rectangle2D anchorRect = null; - if (orientation == PlotOrientation.HORIZONTAL) { - anchorRect = markerOffset.createAdjustedRectangle(markerArea, - LengthAdjustmentType.CONTRACT, labelOffsetType); - } - else if (orientation == PlotOrientation.VERTICAL) { - anchorRect = markerOffset.createAdjustedRectangle(markerArea, - labelOffsetType, LengthAdjustmentType.CONTRACT); - } - return anchor.getAnchorPoint(anchorRect); - - } - - /** - * Draws a line on the chart perpendicular to the y-axis to mark a value - * or range of values. - * - * @param g2 the graphics device. - * @param plot the plot. - * @param rangeAxis the range axis. - * @param marker the marker line. - * @param dataArea the axis data area. - */ - @Override - public void drawRangeMarker(Graphics2D g2, XYPlot plot, ValueAxis rangeAxis, - Marker marker, Rectangle2D dataArea) { - - if (marker instanceof ValueMarker) { - ValueMarker vm = (ValueMarker) marker; - double value = vm.getValue(); - Range range = rangeAxis.getRange(); - if (!range.contains(value)) { - return; - } - - double v = rangeAxis.valueToJava2D(value, dataArea, - plot.getRangeAxisEdge()); - PlotOrientation orientation = plot.getOrientation(); - Line2D line = null; - if (orientation == PlotOrientation.HORIZONTAL) { - line = new Line2D.Double(v, dataArea.getMinY(), v, - dataArea.getMaxY()); - } else if (orientation == PlotOrientation.VERTICAL) { - line = new Line2D.Double(dataArea.getMinX(), v, - dataArea.getMaxX(), v); - } else { - throw new IllegalStateException("Unrecognised orientation."); - } - - final Composite originalComposite = g2.getComposite(); - g2.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, marker.getAlpha())); - g2.setPaint(marker.getPaint()); - g2.setStroke(marker.getStroke()); - g2.draw(line); - - String label = marker.getLabel(); - RectangleAnchor anchor = marker.getLabelAnchor(); - if (label != null) { - Font labelFont = marker.getLabelFont(); - g2.setFont(labelFont); - Point2D coords = calculateRangeMarkerTextAnchorPoint( - g2, orientation, dataArea, line.getBounds2D(), - marker.getLabelOffset(), - LengthAdjustmentType.EXPAND, anchor); - Rectangle2D r = TextUtils.calcAlignedStringBounds(label, - g2, (float) coords.getX(), (float) coords.getY(), - marker.getLabelTextAnchor()); - g2.setPaint(marker.getLabelBackgroundColor()); - g2.fill(r); - g2.setPaint(marker.getLabelPaint()); - TextUtils.drawAlignedString(label, g2, - (float) coords.getX(), (float) coords.getY(), - marker.getLabelTextAnchor()); - } - g2.setComposite(originalComposite); - } else if (marker instanceof IntervalMarker) { - IntervalMarker im = (IntervalMarker) marker; - double start = im.getStartValue(); - double end = im.getEndValue(); - Range range = rangeAxis.getRange(); - if (!(range.intersects(start, end))) { - return; - } - - double start2d = rangeAxis.valueToJava2D(start, dataArea, - plot.getRangeAxisEdge()); - double end2d = rangeAxis.valueToJava2D(end, dataArea, - plot.getRangeAxisEdge()); - double low = Math.min(start2d, end2d); - double high = Math.max(start2d, end2d); - - PlotOrientation orientation = plot.getOrientation(); - Rectangle2D rect = null; - if (orientation == PlotOrientation.HORIZONTAL) { - // clip left and right bounds to data area - low = Math.max(low, dataArea.getMinX()); - high = Math.min(high, dataArea.getMaxX()); - rect = new Rectangle2D.Double(low, - dataArea.getMinY(), high - low, - dataArea.getHeight()); - } else if (orientation == PlotOrientation.VERTICAL) { - // clip top and bottom bounds to data area - low = Math.max(low, dataArea.getMinY()); - high = Math.min(high, dataArea.getMaxY()); - rect = new Rectangle2D.Double(dataArea.getMinX(), - low, dataArea.getWidth(), - high - low); - } - - final Composite originalComposite = g2.getComposite(); - g2.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, marker.getAlpha())); - Paint p = marker.getPaint(); - if (p instanceof GradientPaint) { - GradientPaint gp = (GradientPaint) p; - GradientPaintTransformer t = im.getGradientPaintTransformer(); - if (t != null) { - gp = t.transform(gp, rect); - } - g2.setPaint(gp); - } else { - g2.setPaint(p); - } - g2.fill(rect); - - // now draw the outlines, if visible... - if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) { - if (orientation == PlotOrientation.VERTICAL) { - Line2D line = new Line2D.Double(); - double x0 = dataArea.getMinX(); - double x1 = dataArea.getMaxX(); - g2.setPaint(im.getOutlinePaint()); - g2.setStroke(im.getOutlineStroke()); - if (range.contains(start)) { - line.setLine(x0, start2d, x1, start2d); - g2.draw(line); - } - if (range.contains(end)) { - line.setLine(x0, end2d, x1, end2d); - g2.draw(line); - } - } else { // PlotOrientation.HORIZONTAL - Line2D line = new Line2D.Double(); - double y0 = dataArea.getMinY(); - double y1 = dataArea.getMaxY(); - g2.setPaint(im.getOutlinePaint()); - g2.setStroke(im.getOutlineStroke()); - if (range.contains(start)) { - line.setLine(start2d, y0, start2d, y1); - g2.draw(line); - } - if (range.contains(end)) { - line.setLine(end2d, y0, end2d, y1); - g2.draw(line); - } - } - } - - String label = marker.getLabel(); - RectangleAnchor anchor = marker.getLabelAnchor(); - if (label != null) { - Font labelFont = marker.getLabelFont(); - g2.setFont(labelFont); - Point2D coords = calculateRangeMarkerTextAnchorPoint( - g2, orientation, dataArea, rect, - marker.getLabelOffset(), marker.getLabelOffsetType(), - anchor); - Rectangle2D r = TextUtils.calcAlignedStringBounds(label, - g2, (float) coords.getX(), (float) coords.getY(), - marker.getLabelTextAnchor()); - g2.setPaint(marker.getLabelBackgroundColor()); - g2.fill(r); - g2.setPaint(marker.getLabelPaint()); - TextUtils.drawAlignedString(label, g2, - (float) coords.getX(), (float) coords.getY(), - marker.getLabelTextAnchor()); - } - g2.setComposite(originalComposite); - } - } - - /** - * Calculates the (x, y) coordinates for drawing a marker label. - * - * @param g2 the graphics device. - * @param orientation the plot orientation. - * @param dataArea the data area. - * @param markerArea the marker area. - * @param markerOffset the marker offset. - * @param labelOffsetForRange ?? - * @param anchor the label anchor. - * - * @return The coordinates for drawing the marker label. - */ - private Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2, - PlotOrientation orientation, Rectangle2D dataArea, - Rectangle2D markerArea, RectangleInsets markerOffset, - LengthAdjustmentType labelOffsetForRange, RectangleAnchor anchor) { - - Rectangle2D anchorRect = null; - if (orientation == PlotOrientation.HORIZONTAL) { - anchorRect = markerOffset.createAdjustedRectangle(markerArea, - labelOffsetForRange, LengthAdjustmentType.CONTRACT); - } - else if (orientation == PlotOrientation.VERTICAL) { - anchorRect = markerOffset.createAdjustedRectangle(markerArea, - LengthAdjustmentType.CONTRACT, labelOffsetForRange); - } - return anchor.getAnchorPoint(anchorRect); - - } - - /** - * Returns a clone of the renderer. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the renderer does not support - * cloning. - */ - @Override - protected Object clone() throws CloneNotSupportedException { - AbstractXYItemRenderer clone = (AbstractXYItemRenderer) super.clone(); - // 'plot' : just retain reference, not a deep copy - - clone.itemLabelGeneratorMap = CloneUtils.cloneMapValues( - this.itemLabelGeneratorMap); - if (this.defaultItemLabelGenerator != null - && this.defaultItemLabelGenerator instanceof PublicCloneable) { - PublicCloneable pc = (PublicCloneable) this.defaultItemLabelGenerator; - clone.defaultItemLabelGenerator = (XYItemLabelGenerator) pc.clone(); - } - - clone.toolTipGeneratorMap = CloneUtils.cloneMapValues( - this.toolTipGeneratorMap); - if (this.defaultToolTipGenerator != null - && this.defaultToolTipGenerator instanceof PublicCloneable) { - PublicCloneable pc = (PublicCloneable) this.defaultToolTipGenerator; - clone.defaultToolTipGenerator = (XYToolTipGenerator) pc.clone(); - } - - if (this.legendItemLabelGenerator instanceof PublicCloneable) { - clone.legendItemLabelGenerator = (XYSeriesLabelGenerator) - ObjectUtils.clone(this.legendItemLabelGenerator); - } - if (this.legendItemToolTipGenerator instanceof PublicCloneable) { - clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator) - ObjectUtils.clone(this.legendItemToolTipGenerator); - } - if (this.legendItemURLGenerator instanceof PublicCloneable) { - clone.legendItemURLGenerator = (XYSeriesLabelGenerator) - ObjectUtils.clone(this.legendItemURLGenerator); - } - - clone.foregroundAnnotations = (List) ObjectUtils.deepClone( - this.foregroundAnnotations); - clone.backgroundAnnotations = (List) ObjectUtils.deepClone( - this.backgroundAnnotations); - - return clone; - } - - /** - * Tests this renderer for equality with another object. - * - * @param obj the object ({@code null} permitted). - * - * @return {@code true} or {@code false}. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof AbstractXYItemRenderer)) { - return false; - } - AbstractXYItemRenderer that = (AbstractXYItemRenderer) obj; - if (!this.itemLabelGeneratorMap.equals(that.itemLabelGeneratorMap)) { - return false; - } - if (!Objects.equals(this.defaultItemLabelGenerator, - that.defaultItemLabelGenerator)) { - return false; - } - if (!this.toolTipGeneratorMap.equals(that.toolTipGeneratorMap)) { - return false; - } - if (!Objects.equals(this.defaultToolTipGenerator, - that.defaultToolTipGenerator)) { - return false; - } - if (!Objects.equals(this.urlGenerator, that.urlGenerator)) { - return false; - } - if (!this.foregroundAnnotations.equals(that.foregroundAnnotations)) { - return false; - } - if (!this.backgroundAnnotations.equals(that.backgroundAnnotations)) { - return false; - } - if (!Objects.equals(this.legendItemLabelGenerator, - that.legendItemLabelGenerator)) { - return false; - } - if (!Objects.equals(this.legendItemToolTipGenerator, - that.legendItemToolTipGenerator)) { - return false; - } - if (!Objects.equals(this.legendItemURLGenerator, - that.legendItemURLGenerator)) { - return false; - } - return super.equals(obj); - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + itemLabelGeneratorMap.hashCode(); - result = 31 * result + (defaultItemLabelGenerator != null ? defaultItemLabelGenerator.hashCode() : 0); - result = 31 * result + toolTipGeneratorMap.hashCode(); - result = 31 * result + (defaultToolTipGenerator != null ? defaultToolTipGenerator.hashCode() : 0); - result = 31 * result + (urlGenerator != null ? urlGenerator.hashCode() : 0); - result = 31 * result + (backgroundAnnotations != null ? backgroundAnnotations.hashCode() : 0); - result = 31 * result + (foregroundAnnotations != null ? foregroundAnnotations.hashCode() : 0); - result = 31 * result + (legendItemLabelGenerator != null ? legendItemLabelGenerator.hashCode() : 0); - result = 31 * result + (legendItemToolTipGenerator != null ? legendItemToolTipGenerator.hashCode() : 0); - result = 31 * result + (legendItemURLGenerator != null ? legendItemURLGenerator.hashCode() : 0); - return result; - } - - /** - * Returns the drawing supplier from the plot. - * - * @return The drawing supplier (possibly {@code null}). - */ - @Override - public DrawingSupplier getDrawingSupplier() { - DrawingSupplier result = null; - XYPlot p = getPlot(); - if (p != null) { - result = p.getDrawingSupplier(); - } - return result; - } - - /** - * Considers the current (x, y) coordinate and updates the crosshair point - * if it meets the criteria (usually means the (x, y) coordinate is the - * closest to the anchor point so far). - * - * @param crosshairState the crosshair state ({@code null} permitted, - * but the method does nothing in that case). - * @param x the x-value (in data space). - * @param y the y-value (in data space). - * @param datasetIndex the index of the dataset for the point. - * @param transX the x-value translated to Java2D space. - * @param transY the y-value translated to Java2D space. - * @param orientation the plot orientation ({@code null} not - * permitted). - */ - protected void updateCrosshairValues(CrosshairState crosshairState, - double x, double y, int datasetIndex, - double transX, double transY, PlotOrientation orientation) { - - Args.nullNotPermitted(orientation, "orientation"); - if (crosshairState != null) { - // do we need to update the crosshair values? - if (this.plot.isDomainCrosshairLockedOnData()) { - if (this.plot.isRangeCrosshairLockedOnData()) { - // both axes - crosshairState.updateCrosshairPoint(x, y, datasetIndex, - transX, transY, orientation); - } - else { - // just the domain axis... - crosshairState.updateCrosshairX(x, transX, datasetIndex); - } - } - else { - if (this.plot.isRangeCrosshairLockedOnData()) { - // just the range axis... - crosshairState.updateCrosshairY(y, transY, datasetIndex); - } - } - } - - } - - /** - * Draws an item label. - * - * @param g2 the graphics device. - * @param orientation the orientation. - * @param dataset the dataset. - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * @param x the x coordinate (in Java2D space). - * @param y the y coordinate (in Java2D space). - * @param negative indicates a negative value (which affects the item - * label position). - */ - protected void drawItemLabel(Graphics2D g2, PlotOrientation orientation, - XYDataset dataset, int series, int item, double x, double y, - boolean negative) { - - XYItemLabelGenerator generator = getItemLabelGenerator(series, item); - if (generator != null) { - Font labelFont = getItemLabelFont(series, item); - Paint paint = getItemLabelPaint(series, item); - g2.setFont(labelFont); - g2.setPaint(paint); - String label = generator.generateLabel(dataset, series, item); - - // get the label position.. - ItemLabelPosition position; - if (!negative) { - position = getPositiveItemLabelPosition(series, item); - } - else { - position = getNegativeItemLabelPosition(series, item); - } - - // work out the label anchor point... - Point2D anchorPoint = calculateLabelAnchorPoint( - position.getItemLabelAnchor(), x, y, orientation); - TextUtils.drawRotatedString(label, g2, - (float) anchorPoint.getX(), (float) anchorPoint.getY(), - position.getTextAnchor(), position.getAngle(), - position.getRotationAnchor()); - } - - } - - /** - * Draws all the annotations for the specified layer. - * - * @param g2 the graphics device. - * @param dataArea the data area. - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - * @param layer the layer ({@code null} not permitted). - * @param info the plot rendering info. - */ - @Override - public void drawAnnotations(Graphics2D g2, Rectangle2D dataArea, - ValueAxis domainAxis, ValueAxis rangeAxis, Layer layer, - PlotRenderingInfo info) { - - Iterator iterator = null; - if (layer.equals(Layer.FOREGROUND)) { - iterator = this.foregroundAnnotations.iterator(); - } - else if (layer.equals(Layer.BACKGROUND)) { - iterator = this.backgroundAnnotations.iterator(); - } - else { - // should not get here - throw new RuntimeException("Unknown layer."); - } - while (iterator.hasNext()) { - XYAnnotation annotation = (XYAnnotation) iterator.next(); - int index = this.plot.getIndexOf(this); - annotation.draw(g2, this.plot, dataArea, domainAxis, rangeAxis, - index, info); - } - - } - - /** - * Adds an entity to the collection. Note the the {@code entityX} and - * {@code entityY} coordinates are in Java2D space, should already be - * adjusted for the plot orientation, and will only be used if - * {@code hotspot} is {@code null}. - * - * @param entities the entity collection being populated. - * @param hotspot the entity area (if {@code null} a default will be - * used). - * @param dataset the dataset. - * @param series the series. - * @param item the item. - * @param entityX the entity x-coordinate (in Java2D space, only used if - * {@code hotspot} is {@code null}). - * @param entityY the entity y-coordinate (in Java2D space, only used if - * {@code hotspot} is {@code null}). - */ - protected void addEntity(EntityCollection entities, Shape hotspot, - XYDataset dataset, int series, int item, double entityX, - double entityY) { - - if (!getItemCreateEntity(series, item)) { - return; - } - - // if not hotspot is provided, we create a default based on the - // provided data coordinates (which are already in Java2D space) - if (hotspot == null) { - double r = getDefaultEntityRadius(); - double w = r * 2; - hotspot = new Ellipse2D.Double(entityX - r, entityY - r, w, w); - } - String tip = null; - XYToolTipGenerator generator = getToolTipGenerator(series, item); - if (generator != null) { - tip = generator.generateToolTip(dataset, series, item); - } - String url = null; - if (getURLGenerator() != null) { - url = getURLGenerator().generateURL(dataset, series, item); - } - XYItemEntity entity = new XYItemEntity(hotspot, dataset, series, item, - tip, url); - entities.add(entity); - } - - /** - * Utility method delegating to {@link GeneralPath#moveTo} taking double as - * parameters. - * - * @param hotspot the region under construction ({@code null} not - * permitted); - * @param x the x coordinate; - * @param y the y coordinate; - */ - protected static void moveTo(GeneralPath hotspot, double x, double y) { - hotspot.moveTo((float) x, (float) y); - } - - /** - * Utility method delegating to {@link GeneralPath#lineTo} taking double as - * parameters. - * - * @param hotspot the region under construction ({@code null} not - * permitted); - * @param x the x coordinate; - * @param y the y coordinate; - */ - protected static void lineTo(GeneralPath hotspot, double x, double y) { - hotspot.lineTo((float) x, (float) y); - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------------------------- + * AbstractXYItemRenderer.java + * --------------------------- + * (C) Copyright 2002-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): Richard Atkinson; + * Focus Computer Services Limited; + * Tim Bardzil; + * Sergei Ivanov; + * Peter Kolb (patch 2809117); + * Martin Krauskopf; + */ + +package org.jfree.chart.renderer.xy; + +import java.awt.AlphaComposite; +import java.awt.Composite; +import java.awt.Font; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.jfree.chart.LegendItem; +import org.jfree.chart.LegendItemCollection; +import org.jfree.chart.annotations.XYAnnotation; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.entity.EntityCollection; +import org.jfree.chart.entity.XYItemEntity; +import org.jfree.chart.event.AnnotationChangeEvent; +import org.jfree.chart.event.AnnotationChangeListener; +import org.jfree.chart.labels.ItemLabelPosition; +import org.jfree.chart.labels.StandardXYSeriesLabelGenerator; +import org.jfree.chart.labels.XYItemLabelGenerator; +import org.jfree.chart.labels.XYSeriesLabelGenerator; +import org.jfree.chart.labels.XYToolTipGenerator; +import org.jfree.chart.plot.CrosshairState; +import org.jfree.chart.plot.DrawingSupplier; +import org.jfree.chart.plot.IntervalMarker; +import org.jfree.chart.plot.Marker; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.ValueMarker; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.AbstractRenderer; +import org.jfree.chart.text.TextUtils; +import org.jfree.chart.ui.GradientPaintTransformer; +import org.jfree.chart.ui.Layer; +import org.jfree.chart.ui.LengthAdjustmentType; +import org.jfree.chart.ui.RectangleAnchor; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.chart.urls.XYURLGenerator; +import org.jfree.chart.util.CloneUtils; +import org.jfree.chart.util.ObjectUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.data.Range; +import org.jfree.data.general.DatasetUtils; +import org.jfree.data.xy.XYDataset; +import org.jfree.data.xy.XYItemKey; + +/** + * A base class that can be used to create new {@link XYItemRenderer} + * implementations. + */ +public abstract class AbstractXYItemRenderer extends AbstractRenderer + implements XYItemRenderer, AnnotationChangeListener, + Cloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = 8019124836026607990L; + + /** The plot. */ + private XYPlot plot; + + /** A list of item label generators (one per series). */ + private Map itemLabelGeneratorMap; + + /** The default item label generator. */ + private XYItemLabelGenerator defaultItemLabelGenerator; + + /** A list of tool tip generators (one per series). */ + private Map toolTipGeneratorMap; + + /** The default tool tip generator. */ + private XYToolTipGenerator defaultToolTipGenerator; + + /** The URL text generator. */ + private XYURLGenerator urlGenerator; + + /** + * Annotations to be drawn in the background layer ('underneath' the data + * items). + */ + private List backgroundAnnotations; + + /** + * Annotations to be drawn in the foreground layer ('on top' of the data + * items). + */ + private List foregroundAnnotations; + + /** The legend item label generator. */ + private XYSeriesLabelGenerator legendItemLabelGenerator; + + /** The legend item tool tip generator. */ + private XYSeriesLabelGenerator legendItemToolTipGenerator; + + /** The legend item URL generator. */ + private XYSeriesLabelGenerator legendItemURLGenerator; + + /** + * Creates a renderer where the tooltip generator and the URL generator are + * both {@code null}. + */ + protected AbstractXYItemRenderer() { + super(); + this.itemLabelGeneratorMap = new HashMap<>(); + this.toolTipGeneratorMap = new HashMap<>(); + this.urlGenerator = null; + this.backgroundAnnotations = new java.util.ArrayList(); + this.foregroundAnnotations = new java.util.ArrayList(); + this.legendItemLabelGenerator = new StandardXYSeriesLabelGenerator( + "{0}"); + } + + /** + * Returns the number of passes through the data that the renderer requires + * in order to draw the chart. Most charts will require a single pass, but + * some require two passes. + * + * @return The pass count. + */ + @Override + public int getPassCount() { + return 1; + } + + /** + * Returns the plot that the renderer is assigned to. + * + * @return The plot (possibly {@code null}). + */ + @Override + public XYPlot getPlot() { + return this.plot; + } + + /** + * Sets the plot that the renderer is assigned to. + * + * @param plot the plot ({@code null} permitted). + */ + @Override + public void setPlot(XYPlot plot) { + this.plot = plot; + } + + /** + * Initialises the renderer and returns a state object that should be + * passed to all subsequent calls to the drawItem() method. + *

    + * This method will be called before the first item is rendered, giving the + * renderer an opportunity to initialise any state information it wants to + * maintain. The renderer can do nothing if it chooses. + * + * @param g2 the graphics device. + * @param dataArea the area inside the axes. + * @param plot the plot. + * @param dataset the dataset. + * @param info an optional info collection object to return data back to + * the caller. + * + * @return The renderer state (never {@code null}). + */ + @Override + public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, + XYPlot plot, XYDataset dataset, PlotRenderingInfo info) { + return new XYItemRendererState(info); + } + + /** + * Adds a {@code KEY_BEGIN_ELEMENT} hint to the graphics target. This + * hint is recognised by JFreeSVG (in theory it could be used by + * other {@code Graphics2D} implementations also). + * + * @param g2 the graphics target ({@code null} not permitted). + * @param seriesKey the series key that identifies the element + * ({@code null} not permitted). + * @param itemIndex the item index. + */ + protected void beginElementGroup(Graphics2D g2, Comparable seriesKey, + int itemIndex) { + beginElementGroup(g2, new XYItemKey(seriesKey, itemIndex)); + } + + // ITEM LABEL GENERATOR + + /** + * Returns the label generator for a data item. This implementation simply + * passes control to the {@link #getSeriesItemLabelGenerator(int)} method. + * If, for some reason, you want a different generator for individual + * items, you can override this method. + * + * @param series the series index (zero based). + * @param item the item index (zero based). + * + * @return The generator (possibly {@code null}). + */ + @Override + public XYItemLabelGenerator getItemLabelGenerator(int series, int item) { + + // otherwise look up the generator table + XYItemLabelGenerator generator = this.itemLabelGeneratorMap.get(series); + if (generator == null) { + generator = this.defaultItemLabelGenerator; + } + return generator; + } + + /** + * Returns the item label generator for a series. + * + * @param series the series index (zero based). + * + * @return The generator (possibly {@code null}). + */ + @Override + public XYItemLabelGenerator getSeriesItemLabelGenerator(int series) { + return this.itemLabelGeneratorMap.get(series); + } + + /** + * Sets the item label generator for a series and calls + * {@link #fireChangeEvent()}. + * + * @param series the series index (zero based). + * @param generator the generator ({@code null} permitted). + */ + @Override + public void setSeriesItemLabelGenerator(int series, + XYItemLabelGenerator generator) { + this.itemLabelGeneratorMap.put(series, generator); + fireChangeEvent(); + } + + /** + * Returns the default item label generator. + * + * @return The generator (possibly {@code null}). + */ + @Override + public XYItemLabelGenerator getDefaultItemLabelGenerator() { + return this.defaultItemLabelGenerator; + } + + /** + * Sets the default item label generator and calls {@link #fireChangeEvent()}. + * + * @param generator the generator ({@code null} permitted). + */ + @Override + public void setDefaultItemLabelGenerator(XYItemLabelGenerator generator) { + this.defaultItemLabelGenerator = generator; + fireChangeEvent(); + } + + // TOOL TIP GENERATOR + + /** + * Returns the tool tip generator for a data item. If, for some reason, + * you want a different generator for individual items, you can override + * this method. + * + * @param series the series index (zero based). + * @param item the item index (zero based). + * + * @return The generator (possibly {@code null}). + */ + @Override + public XYToolTipGenerator getToolTipGenerator(int series, int item) { + + // otherwise look up the generator table + XYToolTipGenerator generator = this.toolTipGeneratorMap.get(series); + if (generator == null) { + generator = this.defaultToolTipGenerator; + } + return generator; + } + + /** + * Returns the tool tip generator for a series. + * + * @param series the series index (zero based). + * + * @return The generator (possibly {@code null}). + */ + @Override + public XYToolTipGenerator getSeriesToolTipGenerator(int series) { + return this.toolTipGeneratorMap.get(series); + } + + /** + * Sets the tool tip generator for a series and calls {@link #fireChangeEvent()}. + * + * @param series the series index (zero based). + * @param generator the generator ({@code null} permitted). + */ + @Override + public void setSeriesToolTipGenerator(int series, + XYToolTipGenerator generator) { + this.toolTipGeneratorMap.put(series, generator); + fireChangeEvent(); + } + + /** + * Returns the default tool tip generator. + * + * @return The generator (possibly {@code null}). + * + * @see #setDefaultToolTipGenerator(XYToolTipGenerator) + */ + @Override + public XYToolTipGenerator getDefaultToolTipGenerator() { + return this.defaultToolTipGenerator; + } + + /** + * Sets the default tool tip generator and calls {@link #fireChangeEvent()}. + * + * @param generator the generator ({@code null} permitted). + * + * @see #getDefaultToolTipGenerator() + */ + @Override + public void setDefaultToolTipGenerator(XYToolTipGenerator generator) { + this.defaultToolTipGenerator = generator; + fireChangeEvent(); + } + + // URL GENERATOR + + /** + * Returns the URL generator for HTML image maps. + * + * @return The URL generator (possibly {@code null}). + */ + @Override + public XYURLGenerator getURLGenerator() { + return this.urlGenerator; + } + + /** + * Sets the URL generator for HTML image maps and calls {@link #fireChangeEvent()}. + * + * @param urlGenerator the URL generator ({@code null} permitted). + */ + @Override + public void setURLGenerator(XYURLGenerator urlGenerator) { + this.urlGenerator = urlGenerator; + fireChangeEvent(); + } + + /** + * Adds an annotation to the foreground layer and calls {@link #fireChangeEvent()}. + * + * @param annotation the annotation ({@code null} not permitted). + */ + @Override + public void addAnnotation(XYAnnotation annotation) { + // defer argument checking + addAnnotation(annotation, Layer.FOREGROUND); + } + + /** + * Adds an annotation to the specified layer and calls {@link #fireChangeEvent()}. + * + * @param annotation the annotation ({@code null} not permitted). + * @param layer the layer ({@code null} not permitted). + */ + @Override + public void addAnnotation(XYAnnotation annotation, Layer layer) { + Args.nullNotPermitted(annotation, "annotation"); + if (layer.equals(Layer.FOREGROUND)) { + this.foregroundAnnotations.add(annotation); + annotation.addChangeListener(this); + fireChangeEvent(); + } + else if (layer.equals(Layer.BACKGROUND)) { + this.backgroundAnnotations.add(annotation); + annotation.addChangeListener(this); + fireChangeEvent(); + } + else { + // should never get here + throw new RuntimeException("Unknown layer."); + } + } + /** + * Removes the specified annotation and calls {@link #fireChangeEvent()}. + * + * @param annotation the annotation to remove ({@code null} not + * permitted). + * + * @return A boolean to indicate whether or not the annotation was + * successfully removed. + */ + @Override + public boolean removeAnnotation(XYAnnotation annotation) { + boolean removed = this.foregroundAnnotations.remove(annotation); + removed = removed & this.backgroundAnnotations.remove(annotation); + annotation.removeChangeListener(this); + fireChangeEvent(); + return removed; + } + + /** + * Removes all annotations and calls {@link #fireChangeEvent()}. + */ + @Override + public void removeAnnotations() { + for(int i = 0; i < this.foregroundAnnotations.size(); i++){ + XYAnnotation annotation + = (XYAnnotation) this.foregroundAnnotations.get(i); + annotation.removeChangeListener(this); + } + for(int i = 0; i < this.backgroundAnnotations.size(); i++){ + XYAnnotation annotation + = (XYAnnotation) this.backgroundAnnotations.get(i); + annotation.removeChangeListener(this); + } + this.foregroundAnnotations.clear(); + this.backgroundAnnotations.clear(); + fireChangeEvent(); + } + + + /** + * Receives notification of a change to an {@link org.jfree.chart.annotations.Annotation} added to + * this renderer. + * + * @param event information about the event (not used here). + */ + @Override + public void annotationChanged(AnnotationChangeEvent event) { + fireChangeEvent(); + } + + /** + * Returns a collection of the annotations that are assigned to the + * renderer. + * + * @return A collection of annotations (possibly empty but never + * {@code null}). + */ + public Collection getAnnotations() { + List result = new java.util.ArrayList(this.foregroundAnnotations); + result.addAll(this.backgroundAnnotations); + return result; + } + + /** + * Returns the legend item label generator. + * + * @return The label generator (never {@code null}). + * + * @see #setLegendItemLabelGenerator(XYSeriesLabelGenerator) + */ + @Override + public XYSeriesLabelGenerator getLegendItemLabelGenerator() { + return this.legendItemLabelGenerator; + } + + /** + * Sets the legend item label generator and calls {@link #fireChangeEvent()}. + * + * @param generator the generator ({@code null} not permitted). + * + * @see #getLegendItemLabelGenerator() + */ + @Override + public void setLegendItemLabelGenerator(XYSeriesLabelGenerator generator) { + Args.nullNotPermitted(generator, "generator"); + this.legendItemLabelGenerator = generator; + fireChangeEvent(); + } + + /** + * Returns the legend item tool tip generator. + * + * @return The tool tip generator (possibly {@code null}). + * + * @see #setLegendItemToolTipGenerator(XYSeriesLabelGenerator) + */ + public XYSeriesLabelGenerator getLegendItemToolTipGenerator() { + return this.legendItemToolTipGenerator; + } + + /** + * Sets the legend item tool tip generator and calls {@link #fireChangeEvent()}. + * + * @param generator the generator ({@code null} permitted). + * + * @see #getLegendItemToolTipGenerator() + */ + public void setLegendItemToolTipGenerator( + XYSeriesLabelGenerator generator) { + this.legendItemToolTipGenerator = generator; + fireChangeEvent(); + } + + /** + * Returns the legend item URL generator. + * + * @return The URL generator (possibly {@code null}). + * + * @see #setLegendItemURLGenerator(XYSeriesLabelGenerator) + */ + public XYSeriesLabelGenerator getLegendItemURLGenerator() { + return this.legendItemURLGenerator; + } + + /** + * Sets the legend item URL generator and calls {@link #fireChangeEvent()}. + * + * @param generator the generator ({@code null} permitted). + * + * @see #getLegendItemURLGenerator() + */ + public void setLegendItemURLGenerator(XYSeriesLabelGenerator generator) { + this.legendItemURLGenerator = generator; + fireChangeEvent(); + } + + /** + * Returns the lower and upper bounds (range) of the x-values in the + * specified dataset. + * + * @param dataset the dataset ({@code null} permitted). + * + * @return The range ({@code null} if the dataset is {@code null} + * or empty). + * + * @see #findRangeBounds(XYDataset) + */ + @Override + public Range findDomainBounds(XYDataset dataset) { + return findDomainBounds(dataset, false); + } + + /** + * Returns the lower and upper bounds (range) of the x-values in the + * specified dataset. + * + * @param dataset the dataset ({@code null} permitted). + * @param includeInterval include the interval (if any) for the dataset? + * + * @return The range ({@code null} if the dataset is {@code null} + * or empty). + */ + protected Range findDomainBounds(XYDataset dataset, + boolean includeInterval) { + if (dataset == null) { + return null; + } + if (getDataBoundsIncludesVisibleSeriesOnly()) { + List visibleSeriesKeys = new ArrayList(); + int seriesCount = dataset.getSeriesCount(); + for (int s = 0; s < seriesCount; s++) { + if (isSeriesVisible(s)) { + visibleSeriesKeys.add(dataset.getSeriesKey(s)); + } + } + return DatasetUtils.findDomainBounds(dataset, + visibleSeriesKeys, includeInterval); + } + return DatasetUtils.findDomainBounds(dataset, includeInterval); + } + + /** + * Returns the range of values the renderer requires to display all the + * items from the specified dataset. + * + * @param dataset the dataset ({@code null} permitted). + * + * @return The range ({@code null} if the dataset is {@code null} + * or empty). + * + * @see #findDomainBounds(XYDataset) + */ + @Override + public Range findRangeBounds(XYDataset dataset) { + return findRangeBounds(dataset, false); + } + + /** + * Returns the range of values the renderer requires to display all the + * items from the specified dataset. + * + * @param dataset the dataset ({@code null} permitted). + * @param includeInterval include the interval (if any) for the dataset? + * + * @return The range ({@code null} if the dataset is {@code null} + * or empty). + */ + protected Range findRangeBounds(XYDataset dataset, + boolean includeInterval) { + if (dataset == null) { + return null; + } + if (getDataBoundsIncludesVisibleSeriesOnly()) { + List visibleSeriesKeys = new ArrayList(); + int seriesCount = dataset.getSeriesCount(); + for (int s = 0; s < seriesCount; s++) { + if (isSeriesVisible(s)) { + visibleSeriesKeys.add(dataset.getSeriesKey(s)); + } + } + // the bounds should be calculated using just the items within + // the current range of the x-axis...if there is one + Range xRange = null; + XYPlot p = getPlot(); + if (p != null) { + ValueAxis xAxis = null; + int index = p.getIndexOf(this); + if (index >= 0) { + xAxis = this.plot.getDomainAxisForDataset(index); + } + if (xAxis != null) { + xRange = xAxis.getRange(); + } + } + if (xRange == null) { + xRange = new Range(Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY); + } + return DatasetUtils.findRangeBounds(dataset, + visibleSeriesKeys, xRange, includeInterval); + } + return DatasetUtils.findRangeBounds(dataset, includeInterval); + } + + /** + * Returns a (possibly empty) collection of legend items for the series + * that this renderer is responsible for drawing. + * + * @return The legend item collection (never {@code null}). + */ + @Override + public LegendItemCollection getLegendItems() { + if (this.plot == null) { + return new LegendItemCollection(); + } + LegendItemCollection result = new LegendItemCollection(); + int index = this.plot.getIndexOf(this); + XYDataset dataset = this.plot.getDataset(index); + if (dataset != null) { + int seriesCount = dataset.getSeriesCount(); + for (int i = 0; i < seriesCount; i++) { + if (isSeriesVisibleInLegend(i)) { + LegendItem item = getLegendItem(index, i); + if (item != null) { + result.add(item); + } + } + } + + } + return result; + } + + /** + * Returns a default legend item for the specified series. Subclasses + * should override this method to generate customised items. + * + * @param datasetIndex the dataset index (zero-based). + * @param series the series index (zero-based). + * + * @return A legend item for the series. + */ + @Override + public LegendItem getLegendItem(int datasetIndex, int series) { + XYPlot xyplot = getPlot(); + if (xyplot == null) { + return null; + } + XYDataset dataset = xyplot.getDataset(datasetIndex); + if (dataset == null) { + return null; + } + String label = this.legendItemLabelGenerator.generateLabel(dataset, + series); + String description = label; + String toolTipText = null; + if (getLegendItemToolTipGenerator() != null) { + toolTipText = getLegendItemToolTipGenerator().generateLabel( + dataset, series); + } + String urlText = null; + if (getLegendItemURLGenerator() != null) { + urlText = getLegendItemURLGenerator().generateLabel(dataset, + series); + } + Shape shape = lookupLegendShape(series); + Paint paint = lookupSeriesPaint(series); + LegendItem item = new LegendItem(label, paint); + item.setToolTipText(toolTipText); + item.setURLText(urlText); + item.setLabelFont(lookupLegendTextFont(series)); + Paint labelPaint = lookupLegendTextPaint(series); + if (labelPaint != null) { + item.setLabelPaint(labelPaint); + } + item.setSeriesKey(dataset.getSeriesKey(series)); + item.setSeriesIndex(series); + item.setDataset(dataset); + item.setDatasetIndex(datasetIndex); + + if (getTreatLegendShapeAsLine()) { + item.setLineVisible(true); + item.setLine(shape); + item.setLinePaint(paint); + item.setShapeVisible(false); + } else { + Paint outlinePaint = lookupSeriesOutlinePaint(series); + Stroke outlineStroke = lookupSeriesOutlineStroke(series); + item.setOutlinePaint(outlinePaint); + item.setOutlineStroke(outlineStroke); + } + return item; + } + + /** + * Fills a band between two values on the axis. This can be used to color + * bands between the grid lines. + * + * @param g2 the graphics device. + * @param plot the plot. + * @param axis the domain axis. + * @param dataArea the data area. + * @param start the start value. + * @param end the end value. + */ + @Override + public void fillDomainGridBand(Graphics2D g2, XYPlot plot, ValueAxis axis, + Rectangle2D dataArea, double start, double end) { + + double x1 = axis.valueToJava2D(start, dataArea, + plot.getDomainAxisEdge()); + double x2 = axis.valueToJava2D(end, dataArea, + plot.getDomainAxisEdge()); + Rectangle2D band; + if (plot.getOrientation() == PlotOrientation.VERTICAL) { + band = new Rectangle2D.Double(Math.min(x1, x2), dataArea.getMinY(), + Math.abs(x2 - x1), dataArea.getHeight()); + } + else { + band = new Rectangle2D.Double(dataArea.getMinX(), Math.min(x1, x2), + dataArea.getWidth(), Math.abs(x2 - x1)); + } + Paint paint = plot.getDomainTickBandPaint(); + + if (paint != null) { + g2.setPaint(paint); + g2.fill(band); + } + + } + + /** + * Fills a band between two values on the range axis. This can be used to + * color bands between the grid lines. + * + * @param g2 the graphics device. + * @param plot the plot. + * @param axis the range axis. + * @param dataArea the data area. + * @param start the start value. + * @param end the end value. + */ + @Override + public void fillRangeGridBand(Graphics2D g2, XYPlot plot, ValueAxis axis, + Rectangle2D dataArea, double start, double end) { + + double y1 = axis.valueToJava2D(start, dataArea, + plot.getRangeAxisEdge()); + double y2 = axis.valueToJava2D(end, dataArea, plot.getRangeAxisEdge()); + Rectangle2D band; + if (plot.getOrientation() == PlotOrientation.VERTICAL) { + band = new Rectangle2D.Double(dataArea.getMinX(), Math.min(y1, y2), + dataArea.getWidth(), Math.abs(y2 - y1)); + } + else { + band = new Rectangle2D.Double(Math.min(y1, y2), dataArea.getMinY(), + Math.abs(y2 - y1), dataArea.getHeight()); + } + Paint paint = plot.getRangeTickBandPaint(); + + if (paint != null) { + g2.setPaint(paint); + g2.fill(band); + } + + } + + /** + * Draws a line perpendicular to the domain axis. + * + * @param g2 the graphics device. + * @param plot the plot. + * @param axis the value axis. + * @param dataArea the area for plotting data. + * @param value the value at which the grid line should be drawn. + * @param paint the paint ({@code null} not permitted). + * @param stroke the stroke ({@code null} not permitted). + */ + @Override + public void drawDomainLine(Graphics2D g2, XYPlot plot, ValueAxis axis, + Rectangle2D dataArea, double value, Paint paint, Stroke stroke) { + + Range range = axis.getRange(); + if (!range.contains(value)) { + return; + } + + PlotOrientation orientation = plot.getOrientation(); + Line2D line = null; + double v = axis.valueToJava2D(value, dataArea, + plot.getDomainAxisEdge()); + if (orientation.isHorizontal()) { + line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), + v); + } else if (orientation.isVertical()) { + line = new Line2D.Double(v, dataArea.getMinY(), v, + dataArea.getMaxY()); + } + + g2.setPaint(paint); + g2.setStroke(stroke); + Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_NORMALIZE); + g2.draw(line); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); + } + + /** + * Draws a line perpendicular to the range axis. + * + * @param g2 the graphics device. + * @param plot the plot. + * @param axis the value axis. + * @param dataArea the area for plotting data. + * @param value the value at which the grid line should be drawn. + * @param paint the paint. + * @param stroke the stroke. + */ + @Override + public void drawRangeLine(Graphics2D g2, XYPlot plot, ValueAxis axis, + Rectangle2D dataArea, double value, Paint paint, Stroke stroke) { + + Range range = axis.getRange(); + if (!range.contains(value)) { + return; + } + + PlotOrientation orientation = plot.getOrientation(); + Line2D line = null; + double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge()); + if (orientation == PlotOrientation.HORIZONTAL) { + line = new Line2D.Double(v, dataArea.getMinY(), v, + dataArea.getMaxY()); + } else if (orientation == PlotOrientation.VERTICAL) { + line = new Line2D.Double(dataArea.getMinX(), v, + dataArea.getMaxX(), v); + } + + g2.setPaint(paint); + g2.setStroke(stroke); + Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_NORMALIZE); + g2.draw(line); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); + } + + /** + * Draws a line on the chart perpendicular to the x-axis to mark + * a value or range of values. + * + * @param g2 the graphics device. + * @param plot the plot. + * @param domainAxis the domain axis. + * @param marker the marker line. + * @param dataArea the axis data area. + */ + @Override + public void drawDomainMarker(Graphics2D g2, XYPlot plot, + ValueAxis domainAxis, Marker marker, Rectangle2D dataArea) { + + if (marker instanceof ValueMarker) { + ValueMarker vm = (ValueMarker) marker; + double value = vm.getValue(); + Range range = domainAxis.getRange(); + if (!range.contains(value)) { + return; + } + + double v = domainAxis.valueToJava2D(value, dataArea, + plot.getDomainAxisEdge()); + PlotOrientation orientation = plot.getOrientation(); + Line2D line = null; + if (orientation == PlotOrientation.HORIZONTAL) { + line = new Line2D.Double(dataArea.getMinX(), v, + dataArea.getMaxX(), v); + } else if (orientation == PlotOrientation.VERTICAL) { + line = new Line2D.Double(v, dataArea.getMinY(), v, + dataArea.getMaxY()); + } else { + throw new IllegalStateException("Unrecognised orientation."); + } + + final Composite originalComposite = g2.getComposite(); + g2.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, marker.getAlpha())); + g2.setPaint(marker.getPaint()); + g2.setStroke(marker.getStroke()); + g2.draw(line); + + String label = marker.getLabel(); + RectangleAnchor anchor = marker.getLabelAnchor(); + if (label != null) { + Font labelFont = marker.getLabelFont(); + g2.setFont(labelFont); + Point2D coords = calculateDomainMarkerTextAnchorPoint( + g2, orientation, dataArea, line.getBounds2D(), + marker.getLabelOffset(), + LengthAdjustmentType.EXPAND, anchor); + Rectangle2D r = TextUtils.calcAlignedStringBounds(label, + g2, (float) coords.getX(), (float) coords.getY(), + marker.getLabelTextAnchor()); + g2.setPaint(marker.getLabelBackgroundColor()); + g2.fill(r); + g2.setPaint(marker.getLabelPaint()); + TextUtils.drawAlignedString(label, g2, + (float) coords.getX(), (float) coords.getY(), + marker.getLabelTextAnchor()); + } + g2.setComposite(originalComposite); + } else if (marker instanceof IntervalMarker) { + IntervalMarker im = (IntervalMarker) marker; + double start = im.getStartValue(); + double end = im.getEndValue(); + Range range = domainAxis.getRange(); + if (!(range.intersects(start, end))) { + return; + } + + double start2d = domainAxis.valueToJava2D(start, dataArea, + plot.getDomainAxisEdge()); + double end2d = domainAxis.valueToJava2D(end, dataArea, + plot.getDomainAxisEdge()); + double low = Math.min(start2d, end2d); + double high = Math.max(start2d, end2d); + + PlotOrientation orientation = plot.getOrientation(); + Rectangle2D rect = null; + if (orientation == PlotOrientation.HORIZONTAL) { + // clip top and bottom bounds to data area + low = Math.max(low, dataArea.getMinY()); + high = Math.min(high, dataArea.getMaxY()); + rect = new Rectangle2D.Double(dataArea.getMinX(), + low, dataArea.getWidth(), + high - low); + } else if (orientation == PlotOrientation.VERTICAL) { + // clip left and right bounds to data area + low = Math.max(low, dataArea.getMinX()); + high = Math.min(high, dataArea.getMaxX()); + rect = new Rectangle2D.Double(low, + dataArea.getMinY(), high - low, + dataArea.getHeight()); + } + + final Composite originalComposite = g2.getComposite(); + g2.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, marker.getAlpha())); + Paint p = marker.getPaint(); + if (p instanceof GradientPaint) { + GradientPaint gp = (GradientPaint) p; + GradientPaintTransformer t = im.getGradientPaintTransformer(); + if (t != null) { + gp = t.transform(gp, rect); + } + g2.setPaint(gp); + } else { + g2.setPaint(p); + } + g2.fill(rect); + + // now draw the outlines, if visible... + if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) { + if (orientation == PlotOrientation.VERTICAL) { + Line2D line = new Line2D.Double(); + double y0 = dataArea.getMinY(); + double y1 = dataArea.getMaxY(); + g2.setPaint(im.getOutlinePaint()); + g2.setStroke(im.getOutlineStroke()); + if (range.contains(start)) { + line.setLine(start2d, y0, start2d, y1); + g2.draw(line); + } + if (range.contains(end)) { + line.setLine(end2d, y0, end2d, y1); + g2.draw(line); + } + } else { // PlotOrientation.HORIZONTAL + Line2D line = new Line2D.Double(); + double x0 = dataArea.getMinX(); + double x1 = dataArea.getMaxX(); + g2.setPaint(im.getOutlinePaint()); + g2.setStroke(im.getOutlineStroke()); + if (range.contains(start)) { + line.setLine(x0, start2d, x1, start2d); + g2.draw(line); + } + if (range.contains(end)) { + line.setLine(x0, end2d, x1, end2d); + g2.draw(line); + } + } + } + + String label = marker.getLabel(); + RectangleAnchor anchor = marker.getLabelAnchor(); + if (label != null) { + Font labelFont = marker.getLabelFont(); + g2.setFont(labelFont); + Point2D coords = calculateDomainMarkerTextAnchorPoint( + g2, orientation, dataArea, rect, + marker.getLabelOffset(), marker.getLabelOffsetType(), + anchor); + Rectangle2D r = TextUtils.calcAlignedStringBounds(label, + g2, (float) coords.getX(), (float) coords.getY(), + marker.getLabelTextAnchor()); + g2.setPaint(marker.getLabelBackgroundColor()); + g2.fill(r); + g2.setPaint(marker.getLabelPaint()); + TextUtils.drawAlignedString(label, g2, + (float) coords.getX(), (float) coords.getY(), + marker.getLabelTextAnchor()); + } + g2.setComposite(originalComposite); + } + } + + /** + * Calculates the {@code (x, y)} coordinates for drawing a marker label. + * + * @param g2 the graphics device. + * @param orientation the plot orientation. + * @param dataArea the data area. + * @param markerArea the rectangle surrounding the marker area. + * @param markerOffset the marker label offset. + * @param labelOffsetType the label offset type. + * @param anchor the label anchor. + * + * @return The coordinates for drawing the marker label. + */ + protected Point2D calculateDomainMarkerTextAnchorPoint(Graphics2D g2, + PlotOrientation orientation, Rectangle2D dataArea, + Rectangle2D markerArea, RectangleInsets markerOffset, + LengthAdjustmentType labelOffsetType, RectangleAnchor anchor) { + + Rectangle2D anchorRect = null; + if (orientation == PlotOrientation.HORIZONTAL) { + anchorRect = markerOffset.createAdjustedRectangle(markerArea, + LengthAdjustmentType.CONTRACT, labelOffsetType); + } + else if (orientation == PlotOrientation.VERTICAL) { + anchorRect = markerOffset.createAdjustedRectangle(markerArea, + labelOffsetType, LengthAdjustmentType.CONTRACT); + } + return anchor.getAnchorPoint(anchorRect); + + } + + /** + * Draws a line on the chart perpendicular to the y-axis to mark a value + * or range of values. + * + * @param g2 the graphics device. + * @param plot the plot. + * @param rangeAxis the range axis. + * @param marker the marker line. + * @param dataArea the axis data area. + */ + @Override + public void drawRangeMarker(Graphics2D g2, XYPlot plot, ValueAxis rangeAxis, + Marker marker, Rectangle2D dataArea) { + + if (marker instanceof ValueMarker) { + ValueMarker vm = (ValueMarker) marker; + double value = vm.getValue(); + Range range = rangeAxis.getRange(); + if (!range.contains(value)) { + return; + } + + double v = rangeAxis.valueToJava2D(value, dataArea, + plot.getRangeAxisEdge()); + PlotOrientation orientation = plot.getOrientation(); + Line2D line = null; + if (orientation == PlotOrientation.HORIZONTAL) { + line = new Line2D.Double(v, dataArea.getMinY(), v, + dataArea.getMaxY()); + } else if (orientation == PlotOrientation.VERTICAL) { + line = new Line2D.Double(dataArea.getMinX(), v, + dataArea.getMaxX(), v); + } else { + throw new IllegalStateException("Unrecognised orientation."); + } + + final Composite originalComposite = g2.getComposite(); + g2.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, marker.getAlpha())); + g2.setPaint(marker.getPaint()); + g2.setStroke(marker.getStroke()); + g2.draw(line); + + String label = marker.getLabel(); + RectangleAnchor anchor = marker.getLabelAnchor(); + if (label != null) { + Font labelFont = marker.getLabelFont(); + g2.setFont(labelFont); + Point2D coords = calculateRangeMarkerTextAnchorPoint( + g2, orientation, dataArea, line.getBounds2D(), + marker.getLabelOffset(), + LengthAdjustmentType.EXPAND, anchor); + Rectangle2D r = TextUtils.calcAlignedStringBounds(label, + g2, (float) coords.getX(), (float) coords.getY(), + marker.getLabelTextAnchor()); + g2.setPaint(marker.getLabelBackgroundColor()); + g2.fill(r); + g2.setPaint(marker.getLabelPaint()); + TextUtils.drawAlignedString(label, g2, + (float) coords.getX(), (float) coords.getY(), + marker.getLabelTextAnchor()); + } + g2.setComposite(originalComposite); + } else if (marker instanceof IntervalMarker) { + IntervalMarker im = (IntervalMarker) marker; + double start = im.getStartValue(); + double end = im.getEndValue(); + Range range = rangeAxis.getRange(); + if (!(range.intersects(start, end))) { + return; + } + + double start2d = rangeAxis.valueToJava2D(start, dataArea, + plot.getRangeAxisEdge()); + double end2d = rangeAxis.valueToJava2D(end, dataArea, + plot.getRangeAxisEdge()); + double low = Math.min(start2d, end2d); + double high = Math.max(start2d, end2d); + + PlotOrientation orientation = plot.getOrientation(); + Rectangle2D rect = null; + if (orientation == PlotOrientation.HORIZONTAL) { + // clip left and right bounds to data area + low = Math.max(low, dataArea.getMinX()); + high = Math.min(high, dataArea.getMaxX()); + rect = new Rectangle2D.Double(low, + dataArea.getMinY(), high - low, + dataArea.getHeight()); + } else if (orientation == PlotOrientation.VERTICAL) { + // clip top and bottom bounds to data area + low = Math.max(low, dataArea.getMinY()); + high = Math.min(high, dataArea.getMaxY()); + rect = new Rectangle2D.Double(dataArea.getMinX(), + low, dataArea.getWidth(), + high - low); + } + + final Composite originalComposite = g2.getComposite(); + g2.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, marker.getAlpha())); + Paint p = marker.getPaint(); + if (p instanceof GradientPaint) { + GradientPaint gp = (GradientPaint) p; + GradientPaintTransformer t = im.getGradientPaintTransformer(); + if (t != null) { + gp = t.transform(gp, rect); + } + g2.setPaint(gp); + } else { + g2.setPaint(p); + } + g2.fill(rect); + + // now draw the outlines, if visible... + if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) { + if (orientation == PlotOrientation.VERTICAL) { + Line2D line = new Line2D.Double(); + double x0 = dataArea.getMinX(); + double x1 = dataArea.getMaxX(); + g2.setPaint(im.getOutlinePaint()); + g2.setStroke(im.getOutlineStroke()); + if (range.contains(start)) { + line.setLine(x0, start2d, x1, start2d); + g2.draw(line); + } + if (range.contains(end)) { + line.setLine(x0, end2d, x1, end2d); + g2.draw(line); + } + } else { // PlotOrientation.HORIZONTAL + Line2D line = new Line2D.Double(); + double y0 = dataArea.getMinY(); + double y1 = dataArea.getMaxY(); + g2.setPaint(im.getOutlinePaint()); + g2.setStroke(im.getOutlineStroke()); + if (range.contains(start)) { + line.setLine(start2d, y0, start2d, y1); + g2.draw(line); + } + if (range.contains(end)) { + line.setLine(end2d, y0, end2d, y1); + g2.draw(line); + } + } + } + + String label = marker.getLabel(); + RectangleAnchor anchor = marker.getLabelAnchor(); + if (label != null) { + Font labelFont = marker.getLabelFont(); + g2.setFont(labelFont); + Point2D coords = calculateRangeMarkerTextAnchorPoint( + g2, orientation, dataArea, rect, + marker.getLabelOffset(), marker.getLabelOffsetType(), + anchor); + Rectangle2D r = TextUtils.calcAlignedStringBounds(label, + g2, (float) coords.getX(), (float) coords.getY(), + marker.getLabelTextAnchor()); + g2.setPaint(marker.getLabelBackgroundColor()); + g2.fill(r); + g2.setPaint(marker.getLabelPaint()); + TextUtils.drawAlignedString(label, g2, + (float) coords.getX(), (float) coords.getY(), + marker.getLabelTextAnchor()); + } + g2.setComposite(originalComposite); + } + } + + /** + * Calculates the (x, y) coordinates for drawing a marker label. + * + * @param g2 the graphics device. + * @param orientation the plot orientation. + * @param dataArea the data area. + * @param markerArea the marker area. + * @param markerOffset the marker offset. + * @param labelOffsetForRange ?? + * @param anchor the label anchor. + * + * @return The coordinates for drawing the marker label. + */ + private Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2, + PlotOrientation orientation, Rectangle2D dataArea, + Rectangle2D markerArea, RectangleInsets markerOffset, + LengthAdjustmentType labelOffsetForRange, RectangleAnchor anchor) { + + Rectangle2D anchorRect = null; + if (orientation == PlotOrientation.HORIZONTAL) { + anchorRect = markerOffset.createAdjustedRectangle(markerArea, + labelOffsetForRange, LengthAdjustmentType.CONTRACT); + } + else if (orientation == PlotOrientation.VERTICAL) { + anchorRect = markerOffset.createAdjustedRectangle(markerArea, + LengthAdjustmentType.CONTRACT, labelOffsetForRange); + } + return anchor.getAnchorPoint(anchorRect); + + } + + /** + * Returns a clone of the renderer. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the renderer does not support + * cloning. + */ + @Override + protected Object clone() throws CloneNotSupportedException { + AbstractXYItemRenderer clone = (AbstractXYItemRenderer) super.clone(); + // 'plot' : just retain reference, not a deep copy + + clone.itemLabelGeneratorMap = CloneUtils.cloneMapValues( + this.itemLabelGeneratorMap); + if (this.defaultItemLabelGenerator != null + && this.defaultItemLabelGenerator instanceof PublicCloneable) { + PublicCloneable pc = (PublicCloneable) this.defaultItemLabelGenerator; + clone.defaultItemLabelGenerator = (XYItemLabelGenerator) pc.clone(); + } + + clone.toolTipGeneratorMap = CloneUtils.cloneMapValues( + this.toolTipGeneratorMap); + if (this.defaultToolTipGenerator != null + && this.defaultToolTipGenerator instanceof PublicCloneable) { + PublicCloneable pc = (PublicCloneable) this.defaultToolTipGenerator; + clone.defaultToolTipGenerator = (XYToolTipGenerator) pc.clone(); + } + + if (this.legendItemLabelGenerator instanceof PublicCloneable) { + clone.legendItemLabelGenerator = (XYSeriesLabelGenerator) + ObjectUtils.clone(this.legendItemLabelGenerator); + } + if (this.legendItemToolTipGenerator instanceof PublicCloneable) { + clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator) + ObjectUtils.clone(this.legendItemToolTipGenerator); + } + if (this.legendItemURLGenerator instanceof PublicCloneable) { + clone.legendItemURLGenerator = (XYSeriesLabelGenerator) + ObjectUtils.clone(this.legendItemURLGenerator); + } + + clone.foregroundAnnotations = (List) ObjectUtils.deepClone( + this.foregroundAnnotations); + clone.backgroundAnnotations = (List) ObjectUtils.deepClone( + this.backgroundAnnotations); + + return clone; + } + + /** + * Tests this renderer for equality with another object. + * + * @param obj the object ({@code null} permitted). + * + * @return {@code true} or {@code false}. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof AbstractXYItemRenderer)) { + return false; + } + AbstractXYItemRenderer that = (AbstractXYItemRenderer) obj; + if (!this.itemLabelGeneratorMap.equals(that.itemLabelGeneratorMap)) { + return false; + } + if (!Objects.equals(this.defaultItemLabelGenerator, + that.defaultItemLabelGenerator)) { + return false; + } + if (!this.toolTipGeneratorMap.equals(that.toolTipGeneratorMap)) { + return false; + } + if (!Objects.equals(this.defaultToolTipGenerator, + that.defaultToolTipGenerator)) { + return false; + } + if (!Objects.equals(this.urlGenerator, that.urlGenerator)) { + return false; + } + if (!this.foregroundAnnotations.equals(that.foregroundAnnotations)) { + return false; + } + if (!this.backgroundAnnotations.equals(that.backgroundAnnotations)) { + return false; + } + if (!Objects.equals(this.legendItemLabelGenerator, + that.legendItemLabelGenerator)) { + return false; + } + if (!Objects.equals(this.legendItemToolTipGenerator, + that.legendItemToolTipGenerator)) { + return false; + } + if (!Objects.equals(this.legendItemURLGenerator, + that.legendItemURLGenerator)) { + return false; + } + return super.equals(obj); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + itemLabelGeneratorMap.hashCode(); + result = 31 * result + (defaultItemLabelGenerator != null ? defaultItemLabelGenerator.hashCode() : 0); + result = 31 * result + toolTipGeneratorMap.hashCode(); + result = 31 * result + (defaultToolTipGenerator != null ? defaultToolTipGenerator.hashCode() : 0); + result = 31 * result + (urlGenerator != null ? urlGenerator.hashCode() : 0); + result = 31 * result + (backgroundAnnotations != null ? backgroundAnnotations.hashCode() : 0); + result = 31 * result + (foregroundAnnotations != null ? foregroundAnnotations.hashCode() : 0); + result = 31 * result + (legendItemLabelGenerator != null ? legendItemLabelGenerator.hashCode() : 0); + result = 31 * result + (legendItemToolTipGenerator != null ? legendItemToolTipGenerator.hashCode() : 0); + result = 31 * result + (legendItemURLGenerator != null ? legendItemURLGenerator.hashCode() : 0); + return result; + } + + /** + * Returns the drawing supplier from the plot. + * + * @return The drawing supplier (possibly {@code null}). + */ + @Override + public DrawingSupplier getDrawingSupplier() { + DrawingSupplier result = null; + XYPlot p = getPlot(); + if (p != null) { + result = p.getDrawingSupplier(); + } + return result; + } + + /** + * Considers the current (x, y) coordinate and updates the crosshair point + * if it meets the criteria (usually means the (x, y) coordinate is the + * closest to the anchor point so far). + * + * @param crosshairState the crosshair state ({@code null} permitted, + * but the method does nothing in that case). + * @param x the x-value (in data space). + * @param y the y-value (in data space). + * @param datasetIndex the index of the dataset for the point. + * @param transX the x-value translated to Java2D space. + * @param transY the y-value translated to Java2D space. + * @param orientation the plot orientation ({@code null} not + * permitted). + */ + protected void updateCrosshairValues(CrosshairState crosshairState, + double x, double y, int datasetIndex, + double transX, double transY, PlotOrientation orientation) { + + Args.nullNotPermitted(orientation, "orientation"); + if (crosshairState != null) { + // do we need to update the crosshair values? + if (this.plot.isDomainCrosshairLockedOnData()) { + if (this.plot.isRangeCrosshairLockedOnData()) { + // both axes + crosshairState.updateCrosshairPoint(x, y, datasetIndex, + transX, transY, orientation); + } + else { + // just the domain axis... + crosshairState.updateCrosshairX(x, transX, datasetIndex); + } + } + else { + if (this.plot.isRangeCrosshairLockedOnData()) { + // just the range axis... + crosshairState.updateCrosshairY(y, transY, datasetIndex); + } + } + } + + } + + /** + * Draws an item label. + * + * @param g2 the graphics device. + * @param orientation the orientation. + * @param dataset the dataset. + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * @param x the x coordinate (in Java2D space). + * @param y the y coordinate (in Java2D space). + * @param negative indicates a negative value (which affects the item + * label position). + */ + protected void drawItemLabel(Graphics2D g2, PlotOrientation orientation, + XYDataset dataset, int series, int item, double x, double y, + boolean negative) { + + XYItemLabelGenerator generator = getItemLabelGenerator(series, item); + if (generator != null) { + Font labelFont = getItemLabelFont(series, item); + Paint paint = getItemLabelPaint(series, item); + g2.setFont(labelFont); + g2.setPaint(paint); + String label = generator.generateLabel(dataset, series, item); + + // get the label position.. + ItemLabelPosition position; + if (!negative) { + position = getPositiveItemLabelPosition(series, item); + } + else { + position = getNegativeItemLabelPosition(series, item); + } + + // work out the label anchor point... + Point2D anchorPoint = calculateLabelAnchorPoint( + position.getItemLabelAnchor(), x, y, orientation); + TextUtils.drawRotatedString(label, g2, + (float) anchorPoint.getX(), (float) anchorPoint.getY(), + position.getTextAnchor(), position.getAngle(), + position.getRotationAnchor()); + } + + } + + /** + * Draws all the annotations for the specified layer. + * + * @param g2 the graphics device. + * @param dataArea the data area. + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + * @param layer the layer ({@code null} not permitted). + * @param info the plot rendering info. + */ + @Override + public void drawAnnotations(Graphics2D g2, Rectangle2D dataArea, + ValueAxis domainAxis, ValueAxis rangeAxis, Layer layer, + PlotRenderingInfo info) { + + Iterator iterator = null; + if (layer.equals(Layer.FOREGROUND)) { + iterator = this.foregroundAnnotations.iterator(); + } + else if (layer.equals(Layer.BACKGROUND)) { + iterator = this.backgroundAnnotations.iterator(); + } + else { + // should not get here + throw new RuntimeException("Unknown layer."); + } + while (iterator.hasNext()) { + XYAnnotation annotation = (XYAnnotation) iterator.next(); + int index = this.plot.getIndexOf(this); + annotation.draw(g2, this.plot, dataArea, domainAxis, rangeAxis, + index, info); + } + + } + + /** + * Adds an entity to the collection. Note the the {@code entityX} and + * {@code entityY} coordinates are in Java2D space, should already be + * adjusted for the plot orientation, and will only be used if + * {@code hotspot} is {@code null}. + * + * @param entities the entity collection being populated. + * @param hotspot the entity area (if {@code null} a default will be + * used). + * @param dataset the dataset. + * @param series the series. + * @param item the item. + * @param entityX the entity x-coordinate (in Java2D space, only used if + * {@code hotspot} is {@code null}). + * @param entityY the entity y-coordinate (in Java2D space, only used if + * {@code hotspot} is {@code null}). + */ + protected void addEntity(EntityCollection entities, Shape hotspot, + XYDataset dataset, int series, int item, double entityX, + double entityY) { + + if (!getItemCreateEntity(series, item)) { + return; + } + + // if not hotspot is provided, we create a default based on the + // provided data coordinates (which are already in Java2D space) + if (hotspot == null) { + double r = getDefaultEntityRadius(); + double w = r * 2; + hotspot = new Ellipse2D.Double(entityX - r, entityY - r, w, w); + } + String tip = null; + XYToolTipGenerator generator = getToolTipGenerator(series, item); + if (generator != null) { + tip = generator.generateToolTip(dataset, series, item); + } + String url = null; + if (getURLGenerator() != null) { + url = getURLGenerator().generateURL(dataset, series, item); + } + XYItemEntity entity = new XYItemEntity(hotspot, dataset, series, item, + tip, url); + entities.add(entity); + } + + /** + * Utility method delegating to {@link GeneralPath#moveTo} taking double as + * parameters. + * + * @param hotspot the region under construction ({@code null} not + * permitted); + * @param x the x coordinate; + * @param y the y coordinate; + */ + protected static void moveTo(GeneralPath hotspot, double x, double y) { + hotspot.moveTo((float) x, (float) y); + } + + /** + * Utility method delegating to {@link GeneralPath#lineTo} taking double as + * parameters. + * + * @param hotspot the region under construction ({@code null} not + * permitted); + * @param x the x coordinate; + * @param y the y coordinate; + */ + protected static void lineTo(GeneralPath hotspot, double x, double y) { + hotspot.lineTo((float) x, (float) y); + } + +} diff --git a/src/main/java/org/jfree/chart/renderer/xy/CandlestickRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/CandlestickRenderer.java index a483053b5..492c18b9f 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/CandlestickRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/CandlestickRenderer.java @@ -54,7 +54,6 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.HighLowItemLabelGenerator; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.CrosshairState; @@ -207,8 +206,7 @@ public double getCandleWidth() { } /** - * Sets the candle width and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the candle width and calls {@link #fireChangeEvent()}. *

    * If you set the width to a negative value, the renderer will calculate * the candle width automatically based on the space available on the chart. @@ -238,8 +236,7 @@ public double getMaxCandleWidthInMilliseconds() { } /** - * Sets the maximum candle width (in milliseconds) and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the maximum candle width (in milliseconds) and calls {@link #fireChangeEvent()}. * * @param millis The maximum width. * @@ -267,7 +264,7 @@ public int getAutoWidthMethod() { /** * Sets the method of automatically calculating the candle width and - * sends a {@link RendererChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. *

    * {@code WIDTHMETHOD_AVERAGE}: Divides the entire display (ignoring * scale factor) by the number of items, and uses this as the available @@ -344,8 +341,7 @@ public double getAutoWidthGap() { /** * Sets the amount of space to leave on the left and right of each candle - * when automatically calculating widths and sends a - * {@link RendererChangeEvent} to all registered listeners. + * when automatically calculating widths and calls {@link #fireChangeEvent()}. * * @param autoWidthGap The gap. * @@ -376,8 +372,7 @@ public Paint getUpPaint() { /** * Sets the paint used to fill candles when the price moves up from open - * to close and sends a {@link RendererChangeEvent} to all registered - * listeners. + * to close and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} permitted). * @@ -402,8 +397,7 @@ public Paint getDownPaint() { /** * Sets the paint used to fill candles when the price moves down from open - * to close and sends a {@link RendererChangeEvent} to all registered - * listeners. + * to close and calls {@link #fireChangeEvent()}. * * @param paint The paint ({@code null} permitted). */ @@ -426,8 +420,7 @@ public boolean getDrawVolume() { /** * Sets a flag that controls whether or not volume bars are drawn in the - * background and sends a {@link RendererChangeEvent} to all registered - * listeners. + * background and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -453,8 +446,7 @@ public Paint getVolumePaint() { } /** - * Sets the paint used to fill the volume bars, and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint used to fill the volume bars, and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * @@ -481,9 +473,9 @@ public boolean getUseOutlinePaint() { } /** - * Sets the flag that controls whether or not the renderer's outline - * paint is used to draw the candlestick outline, and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the flag that controls whether or not the renderer's outline paint + * is used to draw the candlestick outline, and calls + * {@link #fireChangeEvent()}. * * @param use the new flag value. * diff --git a/src/main/java/org/jfree/chart/renderer/xy/DeviationRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/DeviationRenderer.java index a9343666b..f733490d8 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/DeviationRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/DeviationRenderer.java @@ -45,7 +45,6 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; @@ -132,8 +131,8 @@ public float getAlpha() { } /** - * Sets the alpha transparency for the background shading, and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the alpha transparency for the background shading, and calls + * {@link #fireChangeEvent()}. * * @param alpha the alpha (in the range 0.0f to 1.0f). * diff --git a/src/main/java/org/jfree/chart/renderer/xy/HighLowRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/HighLowRenderer.java index 2a29d40eb..3c3ffec7e 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/HighLowRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/HighLowRenderer.java @@ -50,7 +50,6 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; @@ -126,8 +125,8 @@ public boolean getDrawOpenTicks() { } /** - * Sets the flag that controls whether open ticks are drawn, and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the flag that controls whether open ticks are drawn, and calls + * {@link #fireChangeEvent()}. * * @param draw the flag. * @@ -151,8 +150,8 @@ public boolean getDrawCloseTicks() { } /** - * Sets the flag that controls whether close ticks are drawn, and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the flag that controls whether close ticks are drawn, and calls + * {@link #fireChangeEvent()}. * * @param draw the flag. * @@ -176,10 +175,9 @@ public Paint getOpenTickPaint() { } /** - * Sets the paint used to draw the ticks for the open values and sends a - * {@link RendererChangeEvent} to all registered listeners. If you set - * this to {@code null} (the default), the series paint is used - * instead. + * Sets the paint used to draw the ticks for the open values and calls + * {@link #fireChangeEvent()}. If you set this to {@code null} (the + * default), the series paint is used instead. * * @param paint the paint ({@code null} permitted). * @@ -203,10 +201,9 @@ public Paint getCloseTickPaint() { } /** - * Sets the paint used to draw the ticks for the close values and sends a - * {@link RendererChangeEvent} to all registered listeners. If you set - * this to {@code null} (the default), the series paint is used - * instead. + * Sets the paint used to draw the ticks for the close values and calls + * {@link #fireChangeEvent()}. If you set this to {@code null} (the + * default), the series paint is used instead. * * @param paint the paint ({@code null} permitted). * @@ -229,8 +226,7 @@ public double getTickLength() { } /** - * Sets the tick length (in Java2D units) and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the tick length (in Java2D units) and calls {@link #fireChangeEvent()}. * * @param length the length. * diff --git a/src/main/java/org/jfree/chart/renderer/xy/StackedXYAreaRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/StackedXYAreaRenderer.java index 8cbab0d62..bc81122a9 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/StackedXYAreaRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/StackedXYAreaRenderer.java @@ -57,7 +57,6 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; import org.jfree.chart.entity.XYItemEntity; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotOrientation; @@ -242,8 +241,7 @@ public Paint getShapePaint() { } /** - * Sets the paint for rendering shapes and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint for rendering shapes and calls {@link #fireChangeEvent()}. * * @param shapePaint the paint ({@code null} permitted). * @@ -267,8 +265,7 @@ public Stroke getShapeStroke() { } /** - * Sets the stroke for rendering shapes and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the stroke for rendering shapes and calls {@link #fireChangeEvent()}. * * @param shapeStroke the stroke ({@code null} permitted). * diff --git a/src/main/java/org/jfree/chart/renderer/xy/StackedXYAreaRenderer2.java b/src/main/java/org/jfree/chart/renderer/xy/StackedXYAreaRenderer2.java index 7a28194b0..f4d30de05 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/StackedXYAreaRenderer2.java +++ b/src/main/java/org/jfree/chart/renderer/xy/StackedXYAreaRenderer2.java @@ -46,7 +46,6 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotOrientation; @@ -117,8 +116,7 @@ public boolean getRoundXCoordinates() { /** * Sets the flag that controls whether or not the x-coordinates (in - * Java2D space) are rounded to integer values, and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Java2D space) are rounded to integer values, and calls {@link #fireChangeEvent()}. * * @param round the new flag value. * diff --git a/src/main/java/org/jfree/chart/renderer/xy/StackedXYBarRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/StackedXYBarRenderer.java index 01c78b6d0..3ac7934ae 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/StackedXYBarRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/StackedXYBarRenderer.java @@ -1,418 +1,417 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------------- - * StackedXYBarRenderer.java - * ------------------------- - * (C) Copyright 2004-present, by Andreas Schroeder and Contributors. - * - * Original Author: Andreas Schroeder; - * Contributor(s): David Gilbert; - */ - -package org.jfree.chart.renderer.xy; - -import java.awt.Graphics2D; -import java.awt.geom.Rectangle2D; - -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.labels.ItemLabelAnchor; -import org.jfree.chart.labels.ItemLabelPosition; -import org.jfree.chart.labels.XYItemLabelGenerator; -import org.jfree.chart.plot.CrosshairState; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.TextAnchor; -import org.jfree.data.Range; -import org.jfree.data.general.DatasetUtils; -import org.jfree.data.xy.IntervalXYDataset; -import org.jfree.data.xy.TableXYDataset; -import org.jfree.data.xy.XYDataset; - -/** - * A bar renderer that displays the series items stacked. - * The dataset used together with this renderer must be a - * {@link org.jfree.data.xy.IntervalXYDataset} and a - * {@link org.jfree.data.xy.TableXYDataset}. For example, the - * dataset class {@link org.jfree.data.xy.CategoryTableXYDataset} - * implements both interfaces. - * - * The example shown here is generated by the - * {@code StackedXYBarChartDemo2.java} program included in the - * JFreeChart demo collection: - *

    - * StackedXYBarRendererSample.png - - */ -public class StackedXYBarRenderer extends XYBarRenderer { - - /** For serialization. */ - private static final long serialVersionUID = -7049101055533436444L; - - /** A flag that controls whether the bars display values or percentages. */ - private boolean renderAsPercentages; - - /** - * Creates a new renderer. - */ - public StackedXYBarRenderer() { - this(0.0); - } - - /** - * Creates a new renderer. - * - * @param margin the percentual amount of the bars that are cut away. - */ - public StackedXYBarRenderer(double margin) { - super(margin); - this.renderAsPercentages = false; - - // set the default item label positions, which will only be used if - // the user requests visible item labels... - ItemLabelPosition p = new ItemLabelPosition(ItemLabelAnchor.CENTER, - TextAnchor.CENTER); - setDefaultPositiveItemLabelPosition(p); - setDefaultNegativeItemLabelPosition(p); - setPositiveItemLabelPositionFallback(null); - setNegativeItemLabelPositionFallback(null); - } - - /** - * Returns {@code true} if the renderer displays each item value as - * a percentage (so that the stacked bars add to 100%), and - * {@code false} otherwise. - * - * @return A boolean. - * - * @see #setRenderAsPercentages(boolean) - */ - public boolean getRenderAsPercentages() { - return this.renderAsPercentages; - } - - /** - * Sets the flag that controls whether the renderer displays each item - * value as a percentage (so that the stacked bars add to 100%), and sends - * a {@link RendererChangeEvent} to all registered listeners. - * - * @param asPercentages the flag. - * - * @see #getRenderAsPercentages() - */ - public void setRenderAsPercentages(boolean asPercentages) { - this.renderAsPercentages = asPercentages; - fireChangeEvent(); - } - - /** - * Returns {@code 3} to indicate that this renderer requires three - * passes for drawing (shadows are drawn in the first pass, the bars in the - * second, and item labels are drawn in the third pass so that - * they always appear in front of all the bars). - * - * @return {@code 2}. - */ - @Override - public int getPassCount() { - return 3; - } - - /** - * Initialises the renderer and returns a state object that should be - * passed to all subsequent calls to the drawItem() method. Here there is - * nothing to do. - * - * @param g2 the graphics device. - * @param dataArea the area inside the axes. - * @param plot the plot. - * @param data the data. - * @param info an optional info collection object to return data back to - * the caller. - * - * @return A state object. - */ - @Override - public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, - XYPlot plot, XYDataset data, PlotRenderingInfo info) { - return new XYBarRendererState(info); - } - - /** - * Returns the range of values the renderer requires to display all the - * items from the specified dataset. - * - * @param dataset the dataset ({@code null} permitted). - * - * @return The range ({@code null} if the dataset is {@code null} - * or empty). - */ - @Override - public Range findRangeBounds(XYDataset dataset) { - if (dataset != null) { - if (this.renderAsPercentages) { - return new Range(0.0, 1.0); - } - else { - return DatasetUtils.findStackedRangeBounds( - (TableXYDataset) dataset); - } - } - else { - return null; - } - } - - /** - * Draws the visual representation of a single data item. - * - * @param g2 the graphics device. - * @param state the renderer state. - * @param dataArea the area within which the plot is being drawn. - * @param info collects information about the drawing. - * @param plot the plot (can be used to obtain standard color information - * etc). - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - * @param dataset the dataset. - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * @param crosshairState crosshair information for the plot - * ({@code null} permitted). - * @param pass the pass index. - */ - @Override - public void drawItem(Graphics2D g2, XYItemRendererState state, - Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, - ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, - int series, int item, CrosshairState crosshairState, int pass) { - - if (!getItemVisible(series, item)) { - return; - } - - if (!(dataset instanceof IntervalXYDataset - && dataset instanceof TableXYDataset)) { - String message = "dataset (type " + dataset.getClass().getName() - + ") has wrong type:"; - boolean and = false; - if (!IntervalXYDataset.class.isAssignableFrom(dataset.getClass())) { - message += " it is no IntervalXYDataset"; - and = true; - } - if (!TableXYDataset.class.isAssignableFrom(dataset.getClass())) { - if (and) { - message += " and"; - } - message += " it is no TableXYDataset"; - } - - throw new IllegalArgumentException(message); - } - - IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; - double value = intervalDataset.getYValue(series, item); - if (Double.isNaN(value)) { - return; - } - - // if we are rendering the values as percentages, we need to calculate - // the total for the current item. Unfortunately here we end up - // repeating the calculation more times than is strictly necessary - - // hopefully I'll come back to this and find a way to add the - // total(s) to the renderer state. The other problem is we implicitly - // assume the dataset has no negative values...perhaps that can be - // fixed too. - double total = 0.0; - if (this.renderAsPercentages) { - total = DatasetUtils.calculateStackTotal( - (TableXYDataset) dataset, item); - value = value / total; - } - - double positiveBase = 0.0; - double negativeBase = 0.0; - - for (int i = 0; i < series; i++) { - double v = dataset.getYValue(i, item); - if (!Double.isNaN(v) && isSeriesVisible(i)) { - if (this.renderAsPercentages) { - v = v / total; - } - if (v > 0) { - positiveBase = positiveBase + v; - } - else { - negativeBase = negativeBase + v; - } - } - } - - double translatedBase; - double translatedValue; - RectangleEdge edgeR = plot.getRangeAxisEdge(); - if (value > 0.0) { - translatedBase = rangeAxis.valueToJava2D(positiveBase, dataArea, - edgeR); - translatedValue = rangeAxis.valueToJava2D(positiveBase + value, - dataArea, edgeR); - } - else { - translatedBase = rangeAxis.valueToJava2D(negativeBase, dataArea, - edgeR); - translatedValue = rangeAxis.valueToJava2D(negativeBase + value, - dataArea, edgeR); - } - - RectangleEdge edgeD = plot.getDomainAxisEdge(); - double startX = intervalDataset.getStartXValue(series, item); - if (Double.isNaN(startX)) { - return; - } - double translatedStartX = domainAxis.valueToJava2D(startX, dataArea, - edgeD); - - double endX = intervalDataset.getEndXValue(series, item); - if (Double.isNaN(endX)) { - return; - } - double translatedEndX = domainAxis.valueToJava2D(endX, dataArea, edgeD); - - double translatedWidth = Math.max(1, Math.abs(translatedEndX - - translatedStartX)); - double translatedHeight = Math.abs(translatedValue - translatedBase); - if (getMargin() > 0.0) { - double cut = translatedWidth * getMargin(); - translatedWidth = translatedWidth - cut; - translatedStartX = translatedStartX + cut / 2; - } - - Rectangle2D bar = null; - PlotOrientation orientation = plot.getOrientation(); - if (orientation == PlotOrientation.HORIZONTAL) { - bar = new Rectangle2D.Double(Math.min(translatedBase, - translatedValue), Math.min(translatedEndX, - translatedStartX), translatedHeight, translatedWidth); - } - else if (orientation == PlotOrientation.VERTICAL) { - bar = new Rectangle2D.Double(Math.min(translatedStartX, - translatedEndX), Math.min(translatedBase, translatedValue), - translatedWidth, translatedHeight); - } else { - throw new IllegalStateException(); - } - boolean positive = (value > 0.0); - boolean inverted = rangeAxis.isInverted(); - RectangleEdge barBase; - if (orientation == PlotOrientation.HORIZONTAL) { - if (positive && inverted || !positive && !inverted) { - barBase = RectangleEdge.RIGHT; - } - else { - barBase = RectangleEdge.LEFT; - } - } - else { - if (positive && !inverted || !positive && inverted) { - barBase = RectangleEdge.BOTTOM; - } - else { - barBase = RectangleEdge.TOP; - } - } - - if (pass == 0) { - if (getShadowsVisible()) { - getBarPainter().paintBarShadow(g2, this, series, item, bar, - barBase, false); - } - } - else if (pass == 1) { - getBarPainter().paintBar(g2, this, series, item, bar, barBase); - - // add an entity for the item... - if (info != null) { - EntityCollection entities = info.getOwner() - .getEntityCollection(); - if (entities != null) { - addEntity(entities, bar, dataset, series, item, - bar.getCenterX(), bar.getCenterY()); - } - } - } - else if (pass == 2) { - // handle item label drawing, now that we know all the bars have - // been drawn... - if (isItemLabelVisible(series, item)) { - XYItemLabelGenerator generator = getItemLabelGenerator(series, - item); - drawItemLabel(g2, dataset, series, item, plot, generator, bar, - value < 0.0); - } - } - - } - - /** - * Tests this renderer for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof StackedXYBarRenderer)) { - return false; - } - StackedXYBarRenderer that = (StackedXYBarRenderer) obj; - if (this.renderAsPercentages != that.renderAsPercentages) { - return false; - } - return super.equals(obj); - } - - /** - * Returns a hash code for this instance. - * - * @return A hash code. - */ - @Override - public int hashCode() { - int result = super.hashCode(); - result = result * 37 + (this.renderAsPercentages ? 1 : 0); - return result; - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------------- + * StackedXYBarRenderer.java + * ------------------------- + * (C) Copyright 2004-present, by Andreas Schroeder and Contributors. + * + * Original Author: Andreas Schroeder; + * Contributor(s): David Gilbert; + */ + +package org.jfree.chart.renderer.xy; + +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; + +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.entity.EntityCollection; +import org.jfree.chart.labels.ItemLabelAnchor; +import org.jfree.chart.labels.ItemLabelPosition; +import org.jfree.chart.labels.XYItemLabelGenerator; +import org.jfree.chart.plot.CrosshairState; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.TextAnchor; +import org.jfree.data.Range; +import org.jfree.data.general.DatasetUtils; +import org.jfree.data.xy.IntervalXYDataset; +import org.jfree.data.xy.TableXYDataset; +import org.jfree.data.xy.XYDataset; + +/** + * A bar renderer that displays the series items stacked. + * The dataset used together with this renderer must be a + * {@link org.jfree.data.xy.IntervalXYDataset} and a + * {@link org.jfree.data.xy.TableXYDataset}. For example, the + * dataset class {@link org.jfree.data.xy.CategoryTableXYDataset} + * implements both interfaces. + * + * The example shown here is generated by the + * {@code StackedXYBarChartDemo2.java} program included in the + * JFreeChart demo collection: + *

    + * StackedXYBarRendererSample.png + + */ +public class StackedXYBarRenderer extends XYBarRenderer { + + /** For serialization. */ + private static final long serialVersionUID = -7049101055533436444L; + + /** A flag that controls whether the bars display values or percentages. */ + private boolean renderAsPercentages; + + /** + * Creates a new renderer. + */ + public StackedXYBarRenderer() { + this(0.0); + } + + /** + * Creates a new renderer. + * + * @param margin the percentual amount of the bars that are cut away. + */ + public StackedXYBarRenderer(double margin) { + super(margin); + this.renderAsPercentages = false; + + // set the default item label positions, which will only be used if + // the user requests visible item labels... + ItemLabelPosition p = new ItemLabelPosition(ItemLabelAnchor.CENTER, + TextAnchor.CENTER); + setDefaultPositiveItemLabelPosition(p); + setDefaultNegativeItemLabelPosition(p); + setPositiveItemLabelPositionFallback(null); + setNegativeItemLabelPositionFallback(null); + } + + /** + * Returns {@code true} if the renderer displays each item value as + * a percentage (so that the stacked bars add to 100%), and + * {@code false} otherwise. + * + * @return A boolean. + * + * @see #setRenderAsPercentages(boolean) + */ + public boolean getRenderAsPercentages() { + return this.renderAsPercentages; + } + + /** + * Sets the flag that controls whether the renderer displays each item + * value as a percentage (so that the stacked bars add to 100%), and calls + * {@link #fireChangeEvent()}. + * + * @param asPercentages the flag. + * + * @see #getRenderAsPercentages() + */ + public void setRenderAsPercentages(boolean asPercentages) { + this.renderAsPercentages = asPercentages; + fireChangeEvent(); + } + + /** + * Returns {@code 3} to indicate that this renderer requires three + * passes for drawing (shadows are drawn in the first pass, the bars in the + * second, and item labels are drawn in the third pass so that + * they always appear in front of all the bars). + * + * @return {@code 2}. + */ + @Override + public int getPassCount() { + return 3; + } + + /** + * Initialises the renderer and returns a state object that should be + * passed to all subsequent calls to the drawItem() method. Here there is + * nothing to do. + * + * @param g2 the graphics device. + * @param dataArea the area inside the axes. + * @param plot the plot. + * @param data the data. + * @param info an optional info collection object to return data back to + * the caller. + * + * @return A state object. + */ + @Override + public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, + XYPlot plot, XYDataset data, PlotRenderingInfo info) { + return new XYBarRendererState(info); + } + + /** + * Returns the range of values the renderer requires to display all the + * items from the specified dataset. + * + * @param dataset the dataset ({@code null} permitted). + * + * @return The range ({@code null} if the dataset is {@code null} + * or empty). + */ + @Override + public Range findRangeBounds(XYDataset dataset) { + if (dataset != null) { + if (this.renderAsPercentages) { + return new Range(0.0, 1.0); + } + else { + return DatasetUtils.findStackedRangeBounds( + (TableXYDataset) dataset); + } + } + else { + return null; + } + } + + /** + * Draws the visual representation of a single data item. + * + * @param g2 the graphics device. + * @param state the renderer state. + * @param dataArea the area within which the plot is being drawn. + * @param info collects information about the drawing. + * @param plot the plot (can be used to obtain standard color information + * etc). + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + * @param dataset the dataset. + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * @param crosshairState crosshair information for the plot + * ({@code null} permitted). + * @param pass the pass index. + */ + @Override + public void drawItem(Graphics2D g2, XYItemRendererState state, + Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, + ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, + int series, int item, CrosshairState crosshairState, int pass) { + + if (!getItemVisible(series, item)) { + return; + } + + if (!(dataset instanceof IntervalXYDataset + && dataset instanceof TableXYDataset)) { + String message = "dataset (type " + dataset.getClass().getName() + + ") has wrong type:"; + boolean and = false; + if (!IntervalXYDataset.class.isAssignableFrom(dataset.getClass())) { + message += " it is no IntervalXYDataset"; + and = true; + } + if (!TableXYDataset.class.isAssignableFrom(dataset.getClass())) { + if (and) { + message += " and"; + } + message += " it is no TableXYDataset"; + } + + throw new IllegalArgumentException(message); + } + + IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; + double value = intervalDataset.getYValue(series, item); + if (Double.isNaN(value)) { + return; + } + + // if we are rendering the values as percentages, we need to calculate + // the total for the current item. Unfortunately here we end up + // repeating the calculation more times than is strictly necessary - + // hopefully I'll come back to this and find a way to add the + // total(s) to the renderer state. The other problem is we implicitly + // assume the dataset has no negative values...perhaps that can be + // fixed too. + double total = 0.0; + if (this.renderAsPercentages) { + total = DatasetUtils.calculateStackTotal( + (TableXYDataset) dataset, item); + value = value / total; + } + + double positiveBase = 0.0; + double negativeBase = 0.0; + + for (int i = 0; i < series; i++) { + double v = dataset.getYValue(i, item); + if (!Double.isNaN(v) && isSeriesVisible(i)) { + if (this.renderAsPercentages) { + v = v / total; + } + if (v > 0) { + positiveBase = positiveBase + v; + } + else { + negativeBase = negativeBase + v; + } + } + } + + double translatedBase; + double translatedValue; + RectangleEdge edgeR = plot.getRangeAxisEdge(); + if (value > 0.0) { + translatedBase = rangeAxis.valueToJava2D(positiveBase, dataArea, + edgeR); + translatedValue = rangeAxis.valueToJava2D(positiveBase + value, + dataArea, edgeR); + } + else { + translatedBase = rangeAxis.valueToJava2D(negativeBase, dataArea, + edgeR); + translatedValue = rangeAxis.valueToJava2D(negativeBase + value, + dataArea, edgeR); + } + + RectangleEdge edgeD = plot.getDomainAxisEdge(); + double startX = intervalDataset.getStartXValue(series, item); + if (Double.isNaN(startX)) { + return; + } + double translatedStartX = domainAxis.valueToJava2D(startX, dataArea, + edgeD); + + double endX = intervalDataset.getEndXValue(series, item); + if (Double.isNaN(endX)) { + return; + } + double translatedEndX = domainAxis.valueToJava2D(endX, dataArea, edgeD); + + double translatedWidth = Math.max(1, Math.abs(translatedEndX + - translatedStartX)); + double translatedHeight = Math.abs(translatedValue - translatedBase); + if (getMargin() > 0.0) { + double cut = translatedWidth * getMargin(); + translatedWidth = translatedWidth - cut; + translatedStartX = translatedStartX + cut / 2; + } + + Rectangle2D bar = null; + PlotOrientation orientation = plot.getOrientation(); + if (orientation == PlotOrientation.HORIZONTAL) { + bar = new Rectangle2D.Double(Math.min(translatedBase, + translatedValue), Math.min(translatedEndX, + translatedStartX), translatedHeight, translatedWidth); + } + else if (orientation == PlotOrientation.VERTICAL) { + bar = new Rectangle2D.Double(Math.min(translatedStartX, + translatedEndX), Math.min(translatedBase, translatedValue), + translatedWidth, translatedHeight); + } else { + throw new IllegalStateException(); + } + boolean positive = (value > 0.0); + boolean inverted = rangeAxis.isInverted(); + RectangleEdge barBase; + if (orientation == PlotOrientation.HORIZONTAL) { + if (positive && inverted || !positive && !inverted) { + barBase = RectangleEdge.RIGHT; + } + else { + barBase = RectangleEdge.LEFT; + } + } + else { + if (positive && !inverted || !positive && inverted) { + barBase = RectangleEdge.BOTTOM; + } + else { + barBase = RectangleEdge.TOP; + } + } + + if (pass == 0) { + if (getShadowsVisible()) { + getBarPainter().paintBarShadow(g2, this, series, item, bar, + barBase, false); + } + } + else if (pass == 1) { + getBarPainter().paintBar(g2, this, series, item, bar, barBase); + + // add an entity for the item... + if (info != null) { + EntityCollection entities = info.getOwner() + .getEntityCollection(); + if (entities != null) { + addEntity(entities, bar, dataset, series, item, + bar.getCenterX(), bar.getCenterY()); + } + } + } + else if (pass == 2) { + // handle item label drawing, now that we know all the bars have + // been drawn... + if (isItemLabelVisible(series, item)) { + XYItemLabelGenerator generator = getItemLabelGenerator(series, + item); + drawItemLabel(g2, dataset, series, item, plot, generator, bar, + value < 0.0); + } + } + + } + + /** + * Tests this renderer for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof StackedXYBarRenderer)) { + return false; + } + StackedXYBarRenderer that = (StackedXYBarRenderer) obj; + if (this.renderAsPercentages != that.renderAsPercentages) { + return false; + } + return super.equals(obj); + } + + /** + * Returns a hash code for this instance. + * + * @return A hash code. + */ + @Override + public int hashCode() { + int result = super.hashCode(); + result = result * 37 + (this.renderAsPercentages ? 1 : 0); + return result; + } + +} diff --git a/src/main/java/org/jfree/chart/renderer/xy/StandardXYItemRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/StandardXYItemRenderer.java index b22058cb9..ca095cdec 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/StandardXYItemRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/StandardXYItemRenderer.java @@ -60,7 +60,6 @@ import org.jfree.chart.LegendItem; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.Plot; @@ -283,8 +282,7 @@ public Boolean getSeriesShapesFilled(int series) { } /** - * Sets the 'shapes filled' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'shapes filled' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param flag the flag. @@ -308,8 +306,7 @@ public boolean getBaseShapesFilled() { } /** - * Sets the base 'shapes filled' flag and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the base 'shapes filled' flag and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -332,8 +329,7 @@ public boolean getPlotLines() { /** * Sets the flag that controls whether or not a line is plotted between - * each data point and sends a {@link RendererChangeEvent} to all - * registered listeners. + * each data point and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -358,8 +354,7 @@ public UnitType getGapThresholdType() { } /** - * Sets the gap threshold type and sends a {@link RendererChangeEvent} to - * all registered listeners. + * Sets the gap threshold type and calls {@link #fireChangeEvent()}. * * @param thresholdType the type ({@code null} not permitted). * @@ -383,8 +378,7 @@ public double getGapThreshold() { } /** - * Sets the gap threshold for discontinuous lines and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the gap threshold for discontinuous lines and calls {@link #fireChangeEvent()}. * * @param t the threshold. * @@ -408,8 +402,7 @@ public boolean getPlotImages() { /** * Sets the flag that controls whether or not an image is drawn at each - * data point and sends a {@link RendererChangeEvent} to all registered - * listeners. + * data point and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -434,8 +427,7 @@ public boolean getPlotDiscontinuous() { /** * Sets the flag that controls whether or not the renderer shows - * discontinuous lines, and sends a {@link RendererChangeEvent} to all - * registered listeners. + * discontinuous lines, and calls {@link #fireChangeEvent()}. * * @param flag the new flag value. */ @@ -482,8 +474,7 @@ public Shape getLegendLine() { } /** - * Sets the shape used as a line in each legend item and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the shape used as a line in each legend item and calls {@link #fireChangeEvent()}. * * @param line the line ({@code null} not permitted). * diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYAreaRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYAreaRenderer.java index 482dd607f..29b2e2037 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYAreaRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYAreaRenderer.java @@ -1,715 +1,711 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------- - * XYAreaRenderer.java - * ------------------- - * (C) Copyright 2002-present, by Hari and Contributors. - * - * Original Author: Hari (ourhari@hotmail.com); - * Contributor(s): David Gilbert; - * Richard Atkinson; - * Christian W. Zuckschwerdt; - * Martin Krauskopf; - * Ulrich Voigt (patch #312); - */ - -package org.jfree.chart.renderer.xy; - -import java.awt.BasicStroke; -import java.awt.GradientPaint; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Area; -import java.awt.geom.GeneralPath; -import java.awt.geom.Line2D; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; - -import org.jfree.chart.HashUtils; -import org.jfree.chart.LegendItem; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.labels.XYSeriesLabelGenerator; -import org.jfree.chart.labels.XYToolTipGenerator; -import org.jfree.chart.plot.CrosshairState; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.ui.GradientPaintTransformer; -import org.jfree.chart.ui.StandardGradientPaintTransformer; -import org.jfree.chart.urls.XYURLGenerator; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.SerialUtils; -import org.jfree.chart.util.ShapeUtils; -import org.jfree.data.xy.XYDataset; - -/** - * Area item renderer for an {@link XYPlot}. This class can draw (a) shapes at - * each point, or (b) lines between points, or (c) both shapes and lines, - * or (d) filled areas, or (e) filled areas and shapes. The example shown here - * is generated by the {@code XYAreaRendererDemo1.java} program included - * in the JFreeChart demo collection: - *

    - * XYAreaRendererSample.png - */ -public class XYAreaRenderer extends AbstractXYItemRenderer - implements XYItemRenderer, PublicCloneable { - - /** For serialization. */ - private static final long serialVersionUID = -4481971353973876747L; - - /** - * A state object used by this renderer. - */ - static class XYAreaRendererState extends XYItemRendererState { - - /** Working storage for the area under one series. */ - public GeneralPath area; - - /** Working line that can be recycled. */ - public Line2D line; - - /** - * Creates a new state. - * - * @param info the plot rendering info. - */ - public XYAreaRendererState(PlotRenderingInfo info) { - super(info); - this.area = new GeneralPath(); - this.line = new Line2D.Double(); - } - - } - - /** Useful constant for specifying the type of rendering (shapes only). */ - public static final int SHAPES = 1; - - /** Useful constant for specifying the type of rendering (lines only). */ - public static final int LINES = 2; - - /** - * Useful constant for specifying the type of rendering (shapes and lines). - */ - public static final int SHAPES_AND_LINES = 3; - - /** Useful constant for specifying the type of rendering (area only). */ - public static final int AREA = 4; - - /** - * Useful constant for specifying the type of rendering (area and shapes). - */ - public static final int AREA_AND_SHAPES = 5; - - /** A flag indicating whether or not shapes are drawn at each XY point. */ - private boolean plotShapes; - - /** A flag indicating whether or not lines are drawn between XY points. */ - private boolean plotLines; - - /** A flag indicating whether or not Area are drawn at each XY point. */ - private boolean plotArea; - - /** A flag that controls whether or not the outline is shown. */ - private boolean showOutline; - - /** - * The shape used to represent an area in each legend item (this should - * never be {@code null}). - */ - private transient Shape legendArea; - - /** - * A flag that can be set to specify that the fill paint should be used - * to fill the area under the renderer. - */ - private boolean useFillPaint; - - /** - * A transformer that is applied to the paint used to fill under the - * area *if* it is an instance of GradientPaint. - */ - private GradientPaintTransformer gradientTransformer; - - /** - * Constructs a new renderer. - */ - public XYAreaRenderer() { - this(AREA); - } - - /** - * Constructs a new renderer. - * - * @param type the type of the renderer. - */ - public XYAreaRenderer(int type) { - this(type, null, null); - } - - /** - * Constructs a new renderer. To specify the type of renderer, use one of - * the constants: {@code SHAPES}, {@code LINES}, {@code SHAPES_AND_LINES}, - * {@code AREA} or {@code AREA_AND_SHAPES}. - * - * @param type the type of renderer. - * @param toolTipGenerator the tool tip generator ({@code null} permitted). - * @param urlGenerator the URL generator ({@code null} permitted). - */ - public XYAreaRenderer(int type, XYToolTipGenerator toolTipGenerator, - XYURLGenerator urlGenerator) { - - super(); - setDefaultToolTipGenerator(toolTipGenerator); - setURLGenerator(urlGenerator); - - if (type == SHAPES) { - this.plotShapes = true; - } - if (type == LINES) { - this.plotLines = true; - } - if (type == SHAPES_AND_LINES) { - this.plotShapes = true; - this.plotLines = true; - } - if (type == AREA) { - this.plotArea = true; - } - if (type == AREA_AND_SHAPES) { - this.plotArea = true; - this.plotShapes = true; - } - this.showOutline = false; - GeneralPath area = new GeneralPath(); - area.moveTo(0.0f, -4.0f); - area.lineTo(3.0f, -2.0f); - area.lineTo(4.0f, 4.0f); - area.lineTo(-4.0f, 4.0f); - area.lineTo(-3.0f, -2.0f); - area.closePath(); - this.legendArea = area; - this.useFillPaint = false; - this.gradientTransformer = new StandardGradientPaintTransformer(); - } - - /** - * Returns true if shapes are being plotted by the renderer. - * - * @return {@code true} if shapes are being plotted by the renderer. - */ - public boolean getPlotShapes() { - return this.plotShapes; - } - - /** - * Returns true if lines are being plotted by the renderer. - * - * @return {@code true} if lines are being plotted by the renderer. - */ - public boolean getPlotLines() { - return this.plotLines; - } - - /** - * Returns true if Area is being plotted by the renderer. - * - * @return {@code true} if Area is being plotted by the renderer. - */ - public boolean getPlotArea() { - return this.plotArea; - } - - /** - * Returns a flag that controls whether or not outlines of the areas are - * drawn. - * - * @return The flag. - * - * @see #setOutline(boolean) - */ - public boolean isOutline() { - return this.showOutline; - } - - /** - * Sets a flag that controls whether or not outlines of the areas are drawn - * and sends a {@link RendererChangeEvent} to all registered listeners. - * - * @param show the flag. - * - * @see #isOutline() - */ - public void setOutline(boolean show) { - this.showOutline = show; - fireChangeEvent(); - } - - /** - * Returns the shape used to represent an area in the legend. - * - * @return The legend area (never {@code null}). - */ - public Shape getLegendArea() { - return this.legendArea; - } - - /** - * Sets the shape used as an area in each legend item and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param area the area ({@code null} not permitted). - */ - public void setLegendArea(Shape area) { - Args.nullNotPermitted(area, "area"); - this.legendArea = area; - fireChangeEvent(); - } - - /** - * Returns the flag that controls whether the series fill paint is used to - * fill the area under the line. - * - * @return A boolean. - */ - public boolean getUseFillPaint() { - return this.useFillPaint; - } - - /** - * Sets the flag that controls whether or not the series fill paint is - * used to fill the area under the line and sends a - * {@link RendererChangeEvent} to all listeners. - * - * @param use the new flag value. - */ - public void setUseFillPaint(boolean use) { - this.useFillPaint = use; - fireChangeEvent(); - } - - /** - * Returns the gradient paint transformer. - * - * @return The gradient paint transformer (never {@code null}). - */ - public GradientPaintTransformer getGradientTransformer() { - return this.gradientTransformer; - } - - /** - * Sets the gradient paint transformer and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param transformer the transformer ({@code null} not permitted). - */ - public void setGradientTransformer(GradientPaintTransformer transformer) { - Args.nullNotPermitted(transformer, "transformer"); - this.gradientTransformer = transformer; - fireChangeEvent(); - } - - /** - * Initialises the renderer and returns a state object that should be - * passed to all subsequent calls to the drawItem() method. - * - * @param g2 the graphics device. - * @param dataArea the area inside the axes. - * @param plot the plot. - * @param data the data. - * @param info an optional info collection object to return data back to - * the caller. - * - * @return A state object for use by the renderer. - */ - @Override - public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, - XYPlot plot, XYDataset data, PlotRenderingInfo info) { - XYAreaRendererState state = new XYAreaRendererState(info); - - // in the rendering process, there is special handling for item - // zero, so we can't support processing of visible data items only - state.setProcessVisibleItemsOnly(false); - return state; - } - - /** - * Returns a default legend item for the specified series. Subclasses - * should override this method to generate customised items. - * - * @param datasetIndex the dataset index (zero-based). - * @param series the series index (zero-based). - * - * @return A legend item for the series. - */ - @Override - public LegendItem getLegendItem(int datasetIndex, int series) { - LegendItem result = null; - XYPlot xyplot = getPlot(); - if (xyplot != null) { - XYDataset dataset = xyplot.getDataset(datasetIndex); - if (dataset != null) { - XYSeriesLabelGenerator lg = getLegendItemLabelGenerator(); - String label = lg.generateLabel(dataset, series); - String description = label; - String toolTipText = null; - if (getLegendItemToolTipGenerator() != null) { - toolTipText = getLegendItemToolTipGenerator().generateLabel( - dataset, series); - } - String urlText = null; - if (getLegendItemURLGenerator() != null) { - urlText = getLegendItemURLGenerator().generateLabel( - dataset, series); - } - Paint paint = lookupSeriesPaint(series); - result = new LegendItem(label, description, toolTipText, - urlText, this.legendArea, paint); - result.setLabelFont(lookupLegendTextFont(series)); - Paint labelPaint = lookupLegendTextPaint(series); - if (labelPaint != null) { - result.setLabelPaint(labelPaint); - } - result.setDataset(dataset); - result.setDatasetIndex(datasetIndex); - result.setSeriesKey(dataset.getSeriesKey(series)); - result.setSeriesIndex(series); - } - } - return result; - } - - /** - * Draws the visual representation of a single data item. - * - * @param g2 the graphics device. - * @param state the renderer state. - * @param dataArea the area within which the data is being drawn. - * @param info collects information about the drawing. - * @param plot the plot (can be used to obtain standard color information - * etc). - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - * @param dataset the dataset. - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * @param crosshairState crosshair information for the plot - * ({@code null} permitted). - * @param pass the pass index. - */ - @Override - public void drawItem(Graphics2D g2, XYItemRendererState state, - Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, - ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, - int series, int item, CrosshairState crosshairState, int pass) { - - if (!getItemVisible(series, item)) { - return; - } - XYAreaRendererState areaState = (XYAreaRendererState) state; - - // get the data point... - double x1 = dataset.getXValue(series, item); - double y1 = dataset.getYValue(series, item); - if (Double.isNaN(y1)) { - y1 = 0.0; - } - double transX1 = domainAxis.valueToJava2D(x1, dataArea, - plot.getDomainAxisEdge()); - double transY1 = rangeAxis.valueToJava2D(y1, dataArea, - plot.getRangeAxisEdge()); - - // get the previous point and the next point so we can calculate a - // "hot spot" for the area (used by the chart entity)... - int itemCount = dataset.getItemCount(series); - double x0 = dataset.getXValue(series, Math.max(item - 1, 0)); - double y0 = dataset.getYValue(series, Math.max(item - 1, 0)); - if (Double.isNaN(y0)) { - y0 = 0.0; - } - double transX0 = domainAxis.valueToJava2D(x0, dataArea, - plot.getDomainAxisEdge()); - double transY0 = rangeAxis.valueToJava2D(y0, dataArea, - plot.getRangeAxisEdge()); - - double x2 = dataset.getXValue(series, Math.min(item + 1, - itemCount - 1)); - double y2 = dataset.getYValue(series, Math.min(item + 1, - itemCount - 1)); - if (Double.isNaN(y2)) { - y2 = 0.0; - } - double transX2 = domainAxis.valueToJava2D(x2, dataArea, - plot.getDomainAxisEdge()); - double transY2 = rangeAxis.valueToJava2D(y2, dataArea, - plot.getRangeAxisEdge()); - - double transZero = rangeAxis.valueToJava2D(0.0, dataArea, - plot.getRangeAxisEdge()); - - if (item == 0) { // create a new area polygon for the series - areaState.area = new GeneralPath(); - // the first point is (x, 0) - double zero = rangeAxis.valueToJava2D(0.0, dataArea, - plot.getRangeAxisEdge()); - if (plot.getOrientation().isVertical()) { - moveTo(areaState.area, transX1, zero); - } else if (plot.getOrientation().isHorizontal()) { - moveTo(areaState.area, zero, transX1); - } - } - - // Add each point to Area (x, y) - if (plot.getOrientation().isVertical()) { - lineTo(areaState.area, transX1, transY1); - } else if (plot.getOrientation().isHorizontal()) { - lineTo(areaState.area, transY1, transX1); - } - - PlotOrientation orientation = plot.getOrientation(); - Paint paint = getItemPaint(series, item); - Stroke stroke = getItemStroke(series, item); - g2.setPaint(paint); - g2.setStroke(stroke); - - Shape shape; - if (getPlotShapes()) { - shape = getItemShape(series, item); - if (orientation == PlotOrientation.VERTICAL) { - shape = ShapeUtils.createTranslatedShape(shape, transX1, - transY1); - } else if (orientation == PlotOrientation.HORIZONTAL) { - shape = ShapeUtils.createTranslatedShape(shape, transY1, - transX1); - } - g2.draw(shape); - } - - if (getPlotLines()) { - if (item > 0) { - if (plot.getOrientation() == PlotOrientation.VERTICAL) { - areaState.line.setLine(transX0, transY0, transX1, transY1); - } else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { - areaState.line.setLine(transY0, transX0, transY1, transX1); - } - g2.draw(areaState.line); - } - } - - // Check if the item is the last item for the series. - // and number of items > 0. We can't draw an area for a single point. - if (getPlotArea() && item > 0 && item == (itemCount - 1)) { - - if (orientation == PlotOrientation.VERTICAL) { - // Add the last point (x,0) - lineTo(areaState.area, transX1, transZero); - areaState.area.closePath(); - } else if (orientation == PlotOrientation.HORIZONTAL) { - // Add the last point (x,0) - lineTo(areaState.area, transZero, transX1); - areaState.area.closePath(); - } - - if (this.useFillPaint) { - paint = lookupSeriesFillPaint(series); - } - if (paint instanceof GradientPaint) { - GradientPaint gp = (GradientPaint) paint; - GradientPaint adjGP = this.gradientTransformer.transform(gp, - dataArea); - g2.setPaint(adjGP); - } - g2.fill(areaState.area); - - // draw an outline around the Area. - if (isOutline()) { - Shape area = areaState.area; - - // Java2D has some issues drawing dashed lines around "large" - // geometrical shapes - for example, see bug 6620013 in the - // Java bug database. So, we'll check if the outline is - // dashed and, if it is, do our own clipping before drawing - // the outline... - Stroke outlineStroke = lookupSeriesOutlineStroke(series); - if (outlineStroke instanceof BasicStroke) { - BasicStroke bs = (BasicStroke) outlineStroke; - if (bs.getDashArray() != null) { - Area poly = new Area(areaState.area); - // we make the clip region slightly larger than the - // dataArea so that the clipped edges don't show lines - // on the chart - Area clip = new Area(new Rectangle2D.Double( - dataArea.getX() - 5.0, dataArea.getY() - 5.0, - dataArea.getWidth() + 10.0, - dataArea.getHeight() + 10.0)); - poly.intersect(clip); - area = poly; - } - } // end of workaround - - g2.setStroke(outlineStroke); - g2.setPaint(lookupSeriesOutlinePaint(series)); - g2.draw(area); - } - } - - int datasetIndex = plot.indexOf(dataset); - updateCrosshairValues(crosshairState, x1, y1, datasetIndex, - transX1, transY1, orientation); - - // collect entity and tool tip information... - EntityCollection entities = state.getEntityCollection(); - if (entities != null) { - GeneralPath hotspot = new GeneralPath(); - if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { - moveTo(hotspot, transZero, ((transX0 + transX1) / 2.0)); - lineTo(hotspot, ((transY0 + transY1) / 2.0), ((transX0 + transX1) / 2.0)); - lineTo(hotspot, transY1, transX1); - lineTo(hotspot, ((transY1 + transY2) / 2.0), ((transX1 + transX2) / 2.0)); - lineTo(hotspot, transZero, ((transX1 + transX2) / 2.0)); - } else { // vertical orientation - moveTo(hotspot, ((transX0 + transX1) / 2.0), transZero); - lineTo(hotspot, ((transX0 + transX1) / 2.0), ((transY0 + transY1) / 2.0)); - lineTo(hotspot, transX1, transY1); - lineTo(hotspot, ((transX1 + transX2) / 2.0), ((transY1 + transY2) / 2.0)); - lineTo(hotspot, ((transX1 + transX2) / 2.0), transZero); - } - hotspot.closePath(); - - // limit the entity hotspot area to the data area - Area dataAreaHotspot = new Area(hotspot); - dataAreaHotspot.intersect(new Area(dataArea)); - - if (!dataAreaHotspot.isEmpty()) { - addEntity(entities, dataAreaHotspot, dataset, series, item, - 0.0, 0.0); - } - } - - } - - /** - * Returns a clone of the renderer. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the renderer cannot be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - XYAreaRenderer clone = (XYAreaRenderer) super.clone(); - clone.legendArea = ShapeUtils.clone(this.legendArea); - return clone; - } - - /** - * Tests this renderer for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof XYAreaRenderer)) { - return false; - } - XYAreaRenderer that = (XYAreaRenderer) obj; - if (this.plotArea != that.plotArea) { - return false; - } - if (this.plotLines != that.plotLines) { - return false; - } - if (this.plotShapes != that.plotShapes) { - return false; - } - if (this.showOutline != that.showOutline) { - return false; - } - if (this.useFillPaint != that.useFillPaint) { - return false; - } - if (!this.gradientTransformer.equals(that.gradientTransformer)) { - return false; - } - if (!ShapeUtils.equal(this.legendArea, that.legendArea)) { - return false; - } - return true; - } - - /** - * Returns a hash code for this instance. - * - * @return A hash code. - */ - @Override - public int hashCode() { - int result = super.hashCode(); - result = HashUtils.hashCode(result, this.plotArea); - result = HashUtils.hashCode(result, this.plotLines); - result = HashUtils.hashCode(result, this.plotShapes); - result = HashUtils.hashCode(result, this.useFillPaint); - return result; - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - this.legendArea = SerialUtils.readShape(stream); - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writeShape(this.legendArea, stream); - } -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------- + * XYAreaRenderer.java + * ------------------- + * (C) Copyright 2002-present, by Hari and Contributors. + * + * Original Author: Hari (ourhari@hotmail.com); + * Contributor(s): David Gilbert; + * Richard Atkinson; + * Christian W. Zuckschwerdt; + * Martin Krauskopf; + * Ulrich Voigt (patch #312); + */ + +package org.jfree.chart.renderer.xy; + +import java.awt.BasicStroke; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.jfree.chart.HashUtils; +import org.jfree.chart.LegendItem; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.entity.EntityCollection; +import org.jfree.chart.labels.XYSeriesLabelGenerator; +import org.jfree.chart.labels.XYToolTipGenerator; +import org.jfree.chart.plot.CrosshairState; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.ui.GradientPaintTransformer; +import org.jfree.chart.ui.StandardGradientPaintTransformer; +import org.jfree.chart.urls.XYURLGenerator; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.SerialUtils; +import org.jfree.chart.util.ShapeUtils; +import org.jfree.data.xy.XYDataset; + +/** + * Area item renderer for an {@link XYPlot}. This class can draw (a) shapes at + * each point, or (b) lines between points, or (c) both shapes and lines, + * or (d) filled areas, or (e) filled areas and shapes. The example shown here + * is generated by the {@code XYAreaRendererDemo1.java} program included + * in the JFreeChart demo collection: + *

    + * XYAreaRendererSample.png + */ +public class XYAreaRenderer extends AbstractXYItemRenderer + implements XYItemRenderer, PublicCloneable { + + /** For serialization. */ + private static final long serialVersionUID = -4481971353973876747L; + + /** + * A state object used by this renderer. + */ + static class XYAreaRendererState extends XYItemRendererState { + + /** Working storage for the area under one series. */ + public GeneralPath area; + + /** Working line that can be recycled. */ + public Line2D line; + + /** + * Creates a new state. + * + * @param info the plot rendering info. + */ + public XYAreaRendererState(PlotRenderingInfo info) { + super(info); + this.area = new GeneralPath(); + this.line = new Line2D.Double(); + } + + } + + /** Useful constant for specifying the type of rendering (shapes only). */ + public static final int SHAPES = 1; + + /** Useful constant for specifying the type of rendering (lines only). */ + public static final int LINES = 2; + + /** + * Useful constant for specifying the type of rendering (shapes and lines). + */ + public static final int SHAPES_AND_LINES = 3; + + /** Useful constant for specifying the type of rendering (area only). */ + public static final int AREA = 4; + + /** + * Useful constant for specifying the type of rendering (area and shapes). + */ + public static final int AREA_AND_SHAPES = 5; + + /** A flag indicating whether or not shapes are drawn at each XY point. */ + private boolean plotShapes; + + /** A flag indicating whether or not lines are drawn between XY points. */ + private boolean plotLines; + + /** A flag indicating whether or not Area are drawn at each XY point. */ + private boolean plotArea; + + /** A flag that controls whether or not the outline is shown. */ + private boolean showOutline; + + /** + * The shape used to represent an area in each legend item (this should + * never be {@code null}). + */ + private transient Shape legendArea; + + /** + * A flag that can be set to specify that the fill paint should be used + * to fill the area under the renderer. + */ + private boolean useFillPaint; + + /** + * A transformer that is applied to the paint used to fill under the + * area *if* it is an instance of GradientPaint. + */ + private GradientPaintTransformer gradientTransformer; + + /** + * Constructs a new renderer. + */ + public XYAreaRenderer() { + this(AREA); + } + + /** + * Constructs a new renderer. + * + * @param type the type of the renderer. + */ + public XYAreaRenderer(int type) { + this(type, null, null); + } + + /** + * Constructs a new renderer. To specify the type of renderer, use one of + * the constants: {@code SHAPES}, {@code LINES}, {@code SHAPES_AND_LINES}, + * {@code AREA} or {@code AREA_AND_SHAPES}. + * + * @param type the type of renderer. + * @param toolTipGenerator the tool tip generator ({@code null} permitted). + * @param urlGenerator the URL generator ({@code null} permitted). + */ + public XYAreaRenderer(int type, XYToolTipGenerator toolTipGenerator, + XYURLGenerator urlGenerator) { + + super(); + setDefaultToolTipGenerator(toolTipGenerator); + setURLGenerator(urlGenerator); + + if (type == SHAPES) { + this.plotShapes = true; + } + if (type == LINES) { + this.plotLines = true; + } + if (type == SHAPES_AND_LINES) { + this.plotShapes = true; + this.plotLines = true; + } + if (type == AREA) { + this.plotArea = true; + } + if (type == AREA_AND_SHAPES) { + this.plotArea = true; + this.plotShapes = true; + } + this.showOutline = false; + GeneralPath area = new GeneralPath(); + area.moveTo(0.0f, -4.0f); + area.lineTo(3.0f, -2.0f); + area.lineTo(4.0f, 4.0f); + area.lineTo(-4.0f, 4.0f); + area.lineTo(-3.0f, -2.0f); + area.closePath(); + this.legendArea = area; + this.useFillPaint = false; + this.gradientTransformer = new StandardGradientPaintTransformer(); + } + + /** + * Returns true if shapes are being plotted by the renderer. + * + * @return {@code true} if shapes are being plotted by the renderer. + */ + public boolean getPlotShapes() { + return this.plotShapes; + } + + /** + * Returns true if lines are being plotted by the renderer. + * + * @return {@code true} if lines are being plotted by the renderer. + */ + public boolean getPlotLines() { + return this.plotLines; + } + + /** + * Returns true if Area is being plotted by the renderer. + * + * @return {@code true} if Area is being plotted by the renderer. + */ + public boolean getPlotArea() { + return this.plotArea; + } + + /** + * Returns a flag that controls whether or not outlines of the areas are + * drawn. + * + * @return The flag. + * + * @see #setOutline(boolean) + */ + public boolean isOutline() { + return this.showOutline; + } + + /** + * Sets a flag that controls whether or not outlines of the areas are drawn + * and calls {@link #fireChangeEvent()}. + * + * @param show the flag. + * + * @see #isOutline() + */ + public void setOutline(boolean show) { + this.showOutline = show; + fireChangeEvent(); + } + + /** + * Returns the shape used to represent an area in the legend. + * + * @return The legend area (never {@code null}). + */ + public Shape getLegendArea() { + return this.legendArea; + } + + /** + * Sets the shape used as an area in each legend item and calls {@link #fireChangeEvent()}. + * + * @param area the area ({@code null} not permitted). + */ + public void setLegendArea(Shape area) { + Args.nullNotPermitted(area, "area"); + this.legendArea = area; + fireChangeEvent(); + } + + /** + * Returns the flag that controls whether the series fill paint is used to + * fill the area under the line. + * + * @return A boolean. + */ + public boolean getUseFillPaint() { + return this.useFillPaint; + } + + /** + * Sets the flag that controls whether or not the series fill paint is + * used to fill the area under the line and calls {@link #fireChangeEvent()}. + * + * @param use the new flag value. + */ + public void setUseFillPaint(boolean use) { + this.useFillPaint = use; + fireChangeEvent(); + } + + /** + * Returns the gradient paint transformer. + * + * @return The gradient paint transformer (never {@code null}). + */ + public GradientPaintTransformer getGradientTransformer() { + return this.gradientTransformer; + } + + /** + * Sets the gradient paint transformer and calls {@link #fireChangeEvent()}. + * + * @param transformer the transformer ({@code null} not permitted). + */ + public void setGradientTransformer(GradientPaintTransformer transformer) { + Args.nullNotPermitted(transformer, "transformer"); + this.gradientTransformer = transformer; + fireChangeEvent(); + } + + /** + * Initialises the renderer and returns a state object that should be + * passed to all subsequent calls to the drawItem() method. + * + * @param g2 the graphics device. + * @param dataArea the area inside the axes. + * @param plot the plot. + * @param data the data. + * @param info an optional info collection object to return data back to + * the caller. + * + * @return A state object for use by the renderer. + */ + @Override + public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, + XYPlot plot, XYDataset data, PlotRenderingInfo info) { + XYAreaRendererState state = new XYAreaRendererState(info); + + // in the rendering process, there is special handling for item + // zero, so we can't support processing of visible data items only + state.setProcessVisibleItemsOnly(false); + return state; + } + + /** + * Returns a default legend item for the specified series. Subclasses + * should override this method to generate customised items. + * + * @param datasetIndex the dataset index (zero-based). + * @param series the series index (zero-based). + * + * @return A legend item for the series. + */ + @Override + public LegendItem getLegendItem(int datasetIndex, int series) { + LegendItem result = null; + XYPlot xyplot = getPlot(); + if (xyplot != null) { + XYDataset dataset = xyplot.getDataset(datasetIndex); + if (dataset != null) { + XYSeriesLabelGenerator lg = getLegendItemLabelGenerator(); + String label = lg.generateLabel(dataset, series); + String description = label; + String toolTipText = null; + if (getLegendItemToolTipGenerator() != null) { + toolTipText = getLegendItemToolTipGenerator().generateLabel( + dataset, series); + } + String urlText = null; + if (getLegendItemURLGenerator() != null) { + urlText = getLegendItemURLGenerator().generateLabel( + dataset, series); + } + Paint paint = lookupSeriesPaint(series); + result = new LegendItem(label, description, toolTipText, + urlText, this.legendArea, paint); + result.setLabelFont(lookupLegendTextFont(series)); + Paint labelPaint = lookupLegendTextPaint(series); + if (labelPaint != null) { + result.setLabelPaint(labelPaint); + } + result.setDataset(dataset); + result.setDatasetIndex(datasetIndex); + result.setSeriesKey(dataset.getSeriesKey(series)); + result.setSeriesIndex(series); + } + } + return result; + } + + /** + * Draws the visual representation of a single data item. + * + * @param g2 the graphics device. + * @param state the renderer state. + * @param dataArea the area within which the data is being drawn. + * @param info collects information about the drawing. + * @param plot the plot (can be used to obtain standard color information + * etc). + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + * @param dataset the dataset. + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * @param crosshairState crosshair information for the plot + * ({@code null} permitted). + * @param pass the pass index. + */ + @Override + public void drawItem(Graphics2D g2, XYItemRendererState state, + Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, + ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, + int series, int item, CrosshairState crosshairState, int pass) { + + if (!getItemVisible(series, item)) { + return; + } + XYAreaRendererState areaState = (XYAreaRendererState) state; + + // get the data point... + double x1 = dataset.getXValue(series, item); + double y1 = dataset.getYValue(series, item); + if (Double.isNaN(y1)) { + y1 = 0.0; + } + double transX1 = domainAxis.valueToJava2D(x1, dataArea, + plot.getDomainAxisEdge()); + double transY1 = rangeAxis.valueToJava2D(y1, dataArea, + plot.getRangeAxisEdge()); + + // get the previous point and the next point so we can calculate a + // "hot spot" for the area (used by the chart entity)... + int itemCount = dataset.getItemCount(series); + double x0 = dataset.getXValue(series, Math.max(item - 1, 0)); + double y0 = dataset.getYValue(series, Math.max(item - 1, 0)); + if (Double.isNaN(y0)) { + y0 = 0.0; + } + double transX0 = domainAxis.valueToJava2D(x0, dataArea, + plot.getDomainAxisEdge()); + double transY0 = rangeAxis.valueToJava2D(y0, dataArea, + plot.getRangeAxisEdge()); + + double x2 = dataset.getXValue(series, Math.min(item + 1, + itemCount - 1)); + double y2 = dataset.getYValue(series, Math.min(item + 1, + itemCount - 1)); + if (Double.isNaN(y2)) { + y2 = 0.0; + } + double transX2 = domainAxis.valueToJava2D(x2, dataArea, + plot.getDomainAxisEdge()); + double transY2 = rangeAxis.valueToJava2D(y2, dataArea, + plot.getRangeAxisEdge()); + + double transZero = rangeAxis.valueToJava2D(0.0, dataArea, + plot.getRangeAxisEdge()); + + if (item == 0) { // create a new area polygon for the series + areaState.area = new GeneralPath(); + // the first point is (x, 0) + double zero = rangeAxis.valueToJava2D(0.0, dataArea, + plot.getRangeAxisEdge()); + if (plot.getOrientation().isVertical()) { + moveTo(areaState.area, transX1, zero); + } else if (plot.getOrientation().isHorizontal()) { + moveTo(areaState.area, zero, transX1); + } + } + + // Add each point to Area (x, y) + if (plot.getOrientation().isVertical()) { + lineTo(areaState.area, transX1, transY1); + } else if (plot.getOrientation().isHorizontal()) { + lineTo(areaState.area, transY1, transX1); + } + + PlotOrientation orientation = plot.getOrientation(); + Paint paint = getItemPaint(series, item); + Stroke stroke = getItemStroke(series, item); + g2.setPaint(paint); + g2.setStroke(stroke); + + Shape shape; + if (getPlotShapes()) { + shape = getItemShape(series, item); + if (orientation == PlotOrientation.VERTICAL) { + shape = ShapeUtils.createTranslatedShape(shape, transX1, + transY1); + } else if (orientation == PlotOrientation.HORIZONTAL) { + shape = ShapeUtils.createTranslatedShape(shape, transY1, + transX1); + } + g2.draw(shape); + } + + if (getPlotLines()) { + if (item > 0) { + if (plot.getOrientation() == PlotOrientation.VERTICAL) { + areaState.line.setLine(transX0, transY0, transX1, transY1); + } else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { + areaState.line.setLine(transY0, transX0, transY1, transX1); + } + g2.draw(areaState.line); + } + } + + // Check if the item is the last item for the series. + // and number of items > 0. We can't draw an area for a single point. + if (getPlotArea() && item > 0 && item == (itemCount - 1)) { + + if (orientation == PlotOrientation.VERTICAL) { + // Add the last point (x,0) + lineTo(areaState.area, transX1, transZero); + areaState.area.closePath(); + } else if (orientation == PlotOrientation.HORIZONTAL) { + // Add the last point (x,0) + lineTo(areaState.area, transZero, transX1); + areaState.area.closePath(); + } + + if (this.useFillPaint) { + paint = lookupSeriesFillPaint(series); + } + if (paint instanceof GradientPaint) { + GradientPaint gp = (GradientPaint) paint; + GradientPaint adjGP = this.gradientTransformer.transform(gp, + dataArea); + g2.setPaint(adjGP); + } + g2.fill(areaState.area); + + // draw an outline around the Area. + if (isOutline()) { + Shape area = areaState.area; + + // Java2D has some issues drawing dashed lines around "large" + // geometrical shapes - for example, see bug 6620013 in the + // Java bug database. So, we'll check if the outline is + // dashed and, if it is, do our own clipping before drawing + // the outline... + Stroke outlineStroke = lookupSeriesOutlineStroke(series); + if (outlineStroke instanceof BasicStroke) { + BasicStroke bs = (BasicStroke) outlineStroke; + if (bs.getDashArray() != null) { + Area poly = new Area(areaState.area); + // we make the clip region slightly larger than the + // dataArea so that the clipped edges don't show lines + // on the chart + Area clip = new Area(new Rectangle2D.Double( + dataArea.getX() - 5.0, dataArea.getY() - 5.0, + dataArea.getWidth() + 10.0, + dataArea.getHeight() + 10.0)); + poly.intersect(clip); + area = poly; + } + } // end of workaround + + g2.setStroke(outlineStroke); + g2.setPaint(lookupSeriesOutlinePaint(series)); + g2.draw(area); + } + } + + int datasetIndex = plot.indexOf(dataset); + updateCrosshairValues(crosshairState, x1, y1, datasetIndex, + transX1, transY1, orientation); + + // collect entity and tool tip information... + EntityCollection entities = state.getEntityCollection(); + if (entities != null) { + GeneralPath hotspot = new GeneralPath(); + if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { + moveTo(hotspot, transZero, ((transX0 + transX1) / 2.0)); + lineTo(hotspot, ((transY0 + transY1) / 2.0), ((transX0 + transX1) / 2.0)); + lineTo(hotspot, transY1, transX1); + lineTo(hotspot, ((transY1 + transY2) / 2.0), ((transX1 + transX2) / 2.0)); + lineTo(hotspot, transZero, ((transX1 + transX2) / 2.0)); + } else { // vertical orientation + moveTo(hotspot, ((transX0 + transX1) / 2.0), transZero); + lineTo(hotspot, ((transX0 + transX1) / 2.0), ((transY0 + transY1) / 2.0)); + lineTo(hotspot, transX1, transY1); + lineTo(hotspot, ((transX1 + transX2) / 2.0), ((transY1 + transY2) / 2.0)); + lineTo(hotspot, ((transX1 + transX2) / 2.0), transZero); + } + hotspot.closePath(); + + // limit the entity hotspot area to the data area + Area dataAreaHotspot = new Area(hotspot); + dataAreaHotspot.intersect(new Area(dataArea)); + + if (!dataAreaHotspot.isEmpty()) { + addEntity(entities, dataAreaHotspot, dataset, series, item, + 0.0, 0.0); + } + } + + } + + /** + * Returns a clone of the renderer. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the renderer cannot be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + XYAreaRenderer clone = (XYAreaRenderer) super.clone(); + clone.legendArea = ShapeUtils.clone(this.legendArea); + return clone; + } + + /** + * Tests this renderer for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof XYAreaRenderer)) { + return false; + } + XYAreaRenderer that = (XYAreaRenderer) obj; + if (this.plotArea != that.plotArea) { + return false; + } + if (this.plotLines != that.plotLines) { + return false; + } + if (this.plotShapes != that.plotShapes) { + return false; + } + if (this.showOutline != that.showOutline) { + return false; + } + if (this.useFillPaint != that.useFillPaint) { + return false; + } + if (!this.gradientTransformer.equals(that.gradientTransformer)) { + return false; + } + if (!ShapeUtils.equal(this.legendArea, that.legendArea)) { + return false; + } + return true; + } + + /** + * Returns a hash code for this instance. + * + * @return A hash code. + */ + @Override + public int hashCode() { + int result = super.hashCode(); + result = HashUtils.hashCode(result, this.plotArea); + result = HashUtils.hashCode(result, this.plotLines); + result = HashUtils.hashCode(result, this.plotShapes); + result = HashUtils.hashCode(result, this.useFillPaint); + return result; + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + this.legendArea = SerialUtils.readShape(stream); + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writeShape(this.legendArea, stream); + } +} diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYAreaRenderer2.java b/src/main/java/org/jfree/chart/renderer/xy/XYAreaRenderer2.java index 82646d94d..e4eaaab7b 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYAreaRenderer2.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYAreaRenderer2.java @@ -1,422 +1,419 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------------- - * XYAreaRenderer2.java - * -------------------- - * (C) Copyright 2004-present, by Hari and Contributors. - * - * Original Author: Hari (ourhari@hotmail.com); - * Contributor(s): David Gilbert; - * Richard Atkinson; - * Christian W. Zuckschwerdt; - * Martin Krauskopf; - * Ulrich Voigt (patch #312); - */ - -package org.jfree.chart.renderer.xy; - -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.Area; -import java.awt.geom.GeneralPath; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; - -import org.jfree.chart.LegendItem; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.entity.XYItemEntity; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.labels.XYSeriesLabelGenerator; -import org.jfree.chart.labels.XYToolTipGenerator; -import org.jfree.chart.plot.CrosshairState; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.urls.XYURLGenerator; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.chart.util.SerialUtils; -import org.jfree.chart.util.ShapeUtils; -import org.jfree.data.xy.XYDataset; - -/** - * Area item renderer for an {@link XYPlot}. The example shown here is - * generated by the {@code XYAreaRenderer2Demo1.java} program included in - * the JFreeChart demo collection: - *

    - * XYAreaRenderer2Sample.png - */ -public class XYAreaRenderer2 extends AbstractXYItemRenderer - implements XYItemRenderer, PublicCloneable { - - /** For serialization. */ - private static final long serialVersionUID = -7378069681579984133L; - - /** A flag that controls whether or not the outline is shown. */ - private boolean showOutline; - - /** - * The shape used to represent an area in each legend item (this should - * never be {@code null}). - */ - private transient Shape legendArea; - - /** - * Constructs a new renderer. - */ - public XYAreaRenderer2() { - this(null, null); - } - - /** - * Constructs a new renderer. - * - * @param labelGenerator the tool tip generator to use ({@code null} - * permitted). - * @param urlGenerator the URL generator ({@code null} permitted). - */ - public XYAreaRenderer2(XYToolTipGenerator labelGenerator, - XYURLGenerator urlGenerator) { - super(); - this.showOutline = false; - setDefaultToolTipGenerator(labelGenerator); - setURLGenerator(urlGenerator); - GeneralPath area = new GeneralPath(); - area.moveTo(0.0f, -4.0f); - area.lineTo(3.0f, -2.0f); - area.lineTo(4.0f, 4.0f); - area.lineTo(-4.0f, 4.0f); - area.lineTo(-3.0f, -2.0f); - area.closePath(); - this.legendArea = area; - } - - /** - * Returns a flag that controls whether or not outlines of the areas are - * drawn. - * - * @return The flag. - * - * @see #setOutline(boolean) - */ - public boolean isOutline() { - return this.showOutline; - } - - /** - * Sets a flag that controls whether or not outlines of the areas are - * drawn, and sends a {@link RendererChangeEvent} to all registered - * listeners. - * - * @param show the flag. - * - * @see #isOutline() - */ - public void setOutline(boolean show) { - this.showOutline = show; - fireChangeEvent(); - } - - /** - * Returns the shape used to represent an area in the legend. - * - * @return The legend area (never {@code null}). - * - * @see #setLegendArea(Shape) - */ - public Shape getLegendArea() { - return this.legendArea; - } - - /** - * Sets the shape used as an area in each legend item and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param area the area ({@code null} not permitted). - * - * @see #getLegendArea() - */ - public void setLegendArea(Shape area) { - Args.nullNotPermitted(area, "area"); - this.legendArea = area; - fireChangeEvent(); - } - - /** - * Returns a default legend item for the specified series. Subclasses - * should override this method to generate customised items. - * - * @param datasetIndex the dataset index (zero-based). - * @param series the series index (zero-based). - * - * @return A legend item for the series. - */ - @Override - public LegendItem getLegendItem(int datasetIndex, int series) { - LegendItem result = null; - XYPlot xyplot = getPlot(); - if (xyplot != null) { - XYDataset dataset = xyplot.getDataset(datasetIndex); - if (dataset != null) { - XYSeriesLabelGenerator lg = getLegendItemLabelGenerator(); - String label = lg.generateLabel(dataset, series); - String description = label; - String toolTipText = null; - if (getLegendItemToolTipGenerator() != null) { - toolTipText = getLegendItemToolTipGenerator().generateLabel( - dataset, series); - } - String urlText = null; - if (getLegendItemURLGenerator() != null) { - urlText = getLegendItemURLGenerator().generateLabel( - dataset, series); - } - Paint paint = lookupSeriesPaint(series); - result = new LegendItem(label, description, toolTipText, - urlText, this.legendArea, paint); - result.setLabelFont(lookupLegendTextFont(series)); - Paint labelPaint = lookupLegendTextPaint(series); - if (labelPaint != null) { - result.setLabelPaint(labelPaint); - } - result.setDataset(dataset); - result.setDatasetIndex(datasetIndex); - result.setSeriesKey(dataset.getSeriesKey(series)); - result.setSeriesIndex(series); - } - } - return result; - } - - /** - * Draws the visual representation of a single data item. - * - * @param g2 the graphics device. - * @param state the renderer state. - * @param dataArea the area within which the data is being drawn. - * @param info collects information about the drawing. - * @param plot the plot (can be used to obtain standard color - * information etc). - * @param domainAxis the domain axis. - * @param rangeAxis the range axis. - * @param dataset the dataset. - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * @param crosshairState crosshair information for the plot - * ({@code null} permitted). - * @param pass the pass index. - */ - @Override - public void drawItem(Graphics2D g2, XYItemRendererState state, - Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, - ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, - int series, int item, CrosshairState crosshairState, int pass) { - - if (!getItemVisible(series, item)) { - return; - } - // get the data point... - double x1 = dataset.getXValue(series, item); - double y1 = dataset.getYValue(series, item); - if (Double.isNaN(y1)) { - y1 = 0.0; - } - - double transX1 = domainAxis.valueToJava2D(x1, dataArea, - plot.getDomainAxisEdge()); - double transY1 = rangeAxis.valueToJava2D(y1, dataArea, - plot.getRangeAxisEdge()); - - // get the previous point and the next point so we can calculate a - // "hot spot" for the area (used by the chart entity)... - double x0 = dataset.getXValue(series, Math.max(item - 1, 0)); - double y0 = dataset.getYValue(series, Math.max(item - 1, 0)); - if (Double.isNaN(y0)) { - y0 = 0.0; - } - double transX0 = domainAxis.valueToJava2D(x0, dataArea, - plot.getDomainAxisEdge()); - double transY0 = rangeAxis.valueToJava2D(y0, dataArea, - plot.getRangeAxisEdge()); - - int itemCount = dataset.getItemCount(series); - double x2 = dataset.getXValue(series, Math.min(item + 1, - itemCount - 1)); - double y2 = dataset.getYValue(series, Math.min(item + 1, - itemCount - 1)); - if (Double.isNaN(y2)) { - y2 = 0.0; - } - double transX2 = domainAxis.valueToJava2D(x2, dataArea, - plot.getDomainAxisEdge()); - double transY2 = rangeAxis.valueToJava2D(y2, dataArea, - plot.getRangeAxisEdge()); - - double transZero = rangeAxis.valueToJava2D(0.0, dataArea, - plot.getRangeAxisEdge()); - GeneralPath hotspot = new GeneralPath(); - if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { - moveTo(hotspot, transZero, ((transX0 + transX1) / 2.0)); - lineTo(hotspot, ((transY0 + transY1) / 2.0), - ((transX0 + transX1) / 2.0)); - lineTo(hotspot, transY1, transX1); - lineTo(hotspot, ((transY1 + transY2) / 2.0), - ((transX1 + transX2) / 2.0)); - lineTo(hotspot, transZero, ((transX1 + transX2) / 2.0)); - } - else { // vertical orientation - moveTo(hotspot, ((transX0 + transX1) / 2.0), transZero); - lineTo(hotspot, ((transX0 + transX1) / 2.0), - ((transY0 + transY1) / 2.0)); - lineTo(hotspot, transX1, transY1); - lineTo(hotspot, ((transX1 + transX2) / 2.0), - ((transY1 + transY2) / 2.0)); - lineTo(hotspot, ((transX1 + transX2) / 2.0), transZero); - } - hotspot.closePath(); - - PlotOrientation orientation = plot.getOrientation(); - Paint paint = getItemPaint(series, item); - Stroke stroke = getItemStroke(series, item); - g2.setPaint(paint); - g2.setStroke(stroke); - - // Check if the item is the last item for the series. - // and number of items > 0. We can't draw an area for a single point. - g2.fill(hotspot); - - // draw an outline around the Area. - if (isOutline()) { - g2.setStroke(lookupSeriesOutlineStroke(series)); - g2.setPaint(lookupSeriesOutlinePaint(series)); - g2.draw(hotspot); - } - int datasetIndex = plot.indexOf(dataset); - updateCrosshairValues(crosshairState, x1, y1, datasetIndex, - transX1, transY1, orientation); - - // collect entity and tool tip information... - if (state.getInfo() != null) { - EntityCollection entities = state.getEntityCollection(); - if (entities != null) { - // limit the entity hotspot area to the data area - Area dataAreaHotspot = new Area(hotspot); - dataAreaHotspot.intersect(new Area(dataArea)); - if (!dataAreaHotspot.isEmpty()) { - String tip = null; - XYToolTipGenerator generator = getToolTipGenerator(series, - item); - if (generator != null) { - tip = generator.generateToolTip(dataset, series, item); - } - String url = null; - if (getURLGenerator() != null) { - url = getURLGenerator().generateURL(dataset, series, - item); - } - XYItemEntity entity = new XYItemEntity(dataAreaHotspot, - dataset, series, item, tip, url); - entities.add(entity); - } - } - } - - } - - /** - * Tests this renderer for equality with an arbitrary object. - * - * @param obj the object ({@code null} not permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof XYAreaRenderer2)) { - return false; - } - XYAreaRenderer2 that = (XYAreaRenderer2) obj; - if (this.showOutline != that.showOutline) { - return false; - } - if (!ShapeUtils.equal(this.legendArea, that.legendArea)) { - return false; - } - return super.equals(obj); - } - - /** - * Returns a clone of the renderer. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the renderer cannot be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone(); - clone.legendArea = ShapeUtils.clone(this.legendArea); - return clone; - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - this.legendArea = SerialUtils.readShape(stream); - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - SerialUtils.writeShape(this.legendArea, stream); - } - -} - +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * -------------------- + * XYAreaRenderer2.java + * -------------------- + * (C) Copyright 2004-present, by Hari and Contributors. + * + * Original Author: Hari (ourhari@hotmail.com); + * Contributor(s): David Gilbert; + * Richard Atkinson; + * Christian W. Zuckschwerdt; + * Martin Krauskopf; + * Ulrich Voigt (patch #312); + */ + +package org.jfree.chart.renderer.xy; + +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.jfree.chart.LegendItem; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.entity.EntityCollection; +import org.jfree.chart.entity.XYItemEntity; +import org.jfree.chart.labels.XYSeriesLabelGenerator; +import org.jfree.chart.labels.XYToolTipGenerator; +import org.jfree.chart.plot.CrosshairState; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.urls.XYURLGenerator; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.chart.util.SerialUtils; +import org.jfree.chart.util.ShapeUtils; +import org.jfree.data.xy.XYDataset; + +/** + * Area item renderer for an {@link XYPlot}. The example shown here is + * generated by the {@code XYAreaRenderer2Demo1.java} program included in + * the JFreeChart demo collection: + *

    + * XYAreaRenderer2Sample.png + */ +public class XYAreaRenderer2 extends AbstractXYItemRenderer + implements XYItemRenderer, PublicCloneable { + + /** For serialization. */ + private static final long serialVersionUID = -7378069681579984133L; + + /** A flag that controls whether or not the outline is shown. */ + private boolean showOutline; + + /** + * The shape used to represent an area in each legend item (this should + * never be {@code null}). + */ + private transient Shape legendArea; + + /** + * Constructs a new renderer. + */ + public XYAreaRenderer2() { + this(null, null); + } + + /** + * Constructs a new renderer. + * + * @param labelGenerator the tool tip generator to use ({@code null} + * permitted). + * @param urlGenerator the URL generator ({@code null} permitted). + */ + public XYAreaRenderer2(XYToolTipGenerator labelGenerator, + XYURLGenerator urlGenerator) { + super(); + this.showOutline = false; + setDefaultToolTipGenerator(labelGenerator); + setURLGenerator(urlGenerator); + GeneralPath area = new GeneralPath(); + area.moveTo(0.0f, -4.0f); + area.lineTo(3.0f, -2.0f); + area.lineTo(4.0f, 4.0f); + area.lineTo(-4.0f, 4.0f); + area.lineTo(-3.0f, -2.0f); + area.closePath(); + this.legendArea = area; + } + + /** + * Returns a flag that controls whether or not outlines of the areas are + * drawn. + * + * @return The flag. + * + * @see #setOutline(boolean) + */ + public boolean isOutline() { + return this.showOutline; + } + + /** + * Sets a flag that controls whether or not outlines of the areas are + * drawn, and calls {@link #fireChangeEvent()}. + * + * @param show the flag. + * + * @see #isOutline() + */ + public void setOutline(boolean show) { + this.showOutline = show; + fireChangeEvent(); + } + + /** + * Returns the shape used to represent an area in the legend. + * + * @return The legend area (never {@code null}). + * + * @see #setLegendArea(Shape) + */ + public Shape getLegendArea() { + return this.legendArea; + } + + /** + * Sets the shape used as an area in each legend item and calls {@link #fireChangeEvent()}. + * + * @param area the area ({@code null} not permitted). + * + * @see #getLegendArea() + */ + public void setLegendArea(Shape area) { + Args.nullNotPermitted(area, "area"); + this.legendArea = area; + fireChangeEvent(); + } + + /** + * Returns a default legend item for the specified series. Subclasses + * should override this method to generate customised items. + * + * @param datasetIndex the dataset index (zero-based). + * @param series the series index (zero-based). + * + * @return A legend item for the series. + */ + @Override + public LegendItem getLegendItem(int datasetIndex, int series) { + LegendItem result = null; + XYPlot xyplot = getPlot(); + if (xyplot != null) { + XYDataset dataset = xyplot.getDataset(datasetIndex); + if (dataset != null) { + XYSeriesLabelGenerator lg = getLegendItemLabelGenerator(); + String label = lg.generateLabel(dataset, series); + String description = label; + String toolTipText = null; + if (getLegendItemToolTipGenerator() != null) { + toolTipText = getLegendItemToolTipGenerator().generateLabel( + dataset, series); + } + String urlText = null; + if (getLegendItemURLGenerator() != null) { + urlText = getLegendItemURLGenerator().generateLabel( + dataset, series); + } + Paint paint = lookupSeriesPaint(series); + result = new LegendItem(label, description, toolTipText, + urlText, this.legendArea, paint); + result.setLabelFont(lookupLegendTextFont(series)); + Paint labelPaint = lookupLegendTextPaint(series); + if (labelPaint != null) { + result.setLabelPaint(labelPaint); + } + result.setDataset(dataset); + result.setDatasetIndex(datasetIndex); + result.setSeriesKey(dataset.getSeriesKey(series)); + result.setSeriesIndex(series); + } + } + return result; + } + + /** + * Draws the visual representation of a single data item. + * + * @param g2 the graphics device. + * @param state the renderer state. + * @param dataArea the area within which the data is being drawn. + * @param info collects information about the drawing. + * @param plot the plot (can be used to obtain standard color + * information etc). + * @param domainAxis the domain axis. + * @param rangeAxis the range axis. + * @param dataset the dataset. + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * @param crosshairState crosshair information for the plot + * ({@code null} permitted). + * @param pass the pass index. + */ + @Override + public void drawItem(Graphics2D g2, XYItemRendererState state, + Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, + ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, + int series, int item, CrosshairState crosshairState, int pass) { + + if (!getItemVisible(series, item)) { + return; + } + // get the data point... + double x1 = dataset.getXValue(series, item); + double y1 = dataset.getYValue(series, item); + if (Double.isNaN(y1)) { + y1 = 0.0; + } + + double transX1 = domainAxis.valueToJava2D(x1, dataArea, + plot.getDomainAxisEdge()); + double transY1 = rangeAxis.valueToJava2D(y1, dataArea, + plot.getRangeAxisEdge()); + + // get the previous point and the next point so we can calculate a + // "hot spot" for the area (used by the chart entity)... + double x0 = dataset.getXValue(series, Math.max(item - 1, 0)); + double y0 = dataset.getYValue(series, Math.max(item - 1, 0)); + if (Double.isNaN(y0)) { + y0 = 0.0; + } + double transX0 = domainAxis.valueToJava2D(x0, dataArea, + plot.getDomainAxisEdge()); + double transY0 = rangeAxis.valueToJava2D(y0, dataArea, + plot.getRangeAxisEdge()); + + int itemCount = dataset.getItemCount(series); + double x2 = dataset.getXValue(series, Math.min(item + 1, + itemCount - 1)); + double y2 = dataset.getYValue(series, Math.min(item + 1, + itemCount - 1)); + if (Double.isNaN(y2)) { + y2 = 0.0; + } + double transX2 = domainAxis.valueToJava2D(x2, dataArea, + plot.getDomainAxisEdge()); + double transY2 = rangeAxis.valueToJava2D(y2, dataArea, + plot.getRangeAxisEdge()); + + double transZero = rangeAxis.valueToJava2D(0.0, dataArea, + plot.getRangeAxisEdge()); + GeneralPath hotspot = new GeneralPath(); + if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { + moveTo(hotspot, transZero, ((transX0 + transX1) / 2.0)); + lineTo(hotspot, ((transY0 + transY1) / 2.0), + ((transX0 + transX1) / 2.0)); + lineTo(hotspot, transY1, transX1); + lineTo(hotspot, ((transY1 + transY2) / 2.0), + ((transX1 + transX2) / 2.0)); + lineTo(hotspot, transZero, ((transX1 + transX2) / 2.0)); + } + else { // vertical orientation + moveTo(hotspot, ((transX0 + transX1) / 2.0), transZero); + lineTo(hotspot, ((transX0 + transX1) / 2.0), + ((transY0 + transY1) / 2.0)); + lineTo(hotspot, transX1, transY1); + lineTo(hotspot, ((transX1 + transX2) / 2.0), + ((transY1 + transY2) / 2.0)); + lineTo(hotspot, ((transX1 + transX2) / 2.0), transZero); + } + hotspot.closePath(); + + PlotOrientation orientation = plot.getOrientation(); + Paint paint = getItemPaint(series, item); + Stroke stroke = getItemStroke(series, item); + g2.setPaint(paint); + g2.setStroke(stroke); + + // Check if the item is the last item for the series. + // and number of items > 0. We can't draw an area for a single point. + g2.fill(hotspot); + + // draw an outline around the Area. + if (isOutline()) { + g2.setStroke(lookupSeriesOutlineStroke(series)); + g2.setPaint(lookupSeriesOutlinePaint(series)); + g2.draw(hotspot); + } + int datasetIndex = plot.indexOf(dataset); + updateCrosshairValues(crosshairState, x1, y1, datasetIndex, + transX1, transY1, orientation); + + // collect entity and tool tip information... + if (state.getInfo() != null) { + EntityCollection entities = state.getEntityCollection(); + if (entities != null) { + // limit the entity hotspot area to the data area + Area dataAreaHotspot = new Area(hotspot); + dataAreaHotspot.intersect(new Area(dataArea)); + if (!dataAreaHotspot.isEmpty()) { + String tip = null; + XYToolTipGenerator generator = getToolTipGenerator(series, + item); + if (generator != null) { + tip = generator.generateToolTip(dataset, series, item); + } + String url = null; + if (getURLGenerator() != null) { + url = getURLGenerator().generateURL(dataset, series, + item); + } + XYItemEntity entity = new XYItemEntity(dataAreaHotspot, + dataset, series, item, tip, url); + entities.add(entity); + } + } + } + + } + + /** + * Tests this renderer for equality with an arbitrary object. + * + * @param obj the object ({@code null} not permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof XYAreaRenderer2)) { + return false; + } + XYAreaRenderer2 that = (XYAreaRenderer2) obj; + if (this.showOutline != that.showOutline) { + return false; + } + if (!ShapeUtils.equal(this.legendArea, that.legendArea)) { + return false; + } + return super.equals(obj); + } + + /** + * Returns a clone of the renderer. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the renderer cannot be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone(); + clone.legendArea = ShapeUtils.clone(this.legendArea); + return clone; + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + this.legendArea = SerialUtils.readShape(stream); + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + SerialUtils.writeShape(this.legendArea, stream); + } + +} + diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYBarRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYBarRenderer.java index 8c045e3e8..edb2009c8 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYBarRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYBarRenderer.java @@ -58,7 +58,6 @@ import org.jfree.chart.LegendItem; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.ItemLabelAnchor; import org.jfree.chart.labels.ItemLabelPosition; import org.jfree.chart.labels.XYItemLabelGenerator; @@ -291,9 +290,9 @@ public double getBase() { } /** - * Sets the base value for the bars and sends a {@link RendererChangeEvent} - * to all registered listeners. The base value is not used if the dataset's - * y-interval is being used to determine the bar length. + * Sets the base value for the bars and calls {@link #fireChangeEvent()}. + * The base value is not used if the dataset's y-interval is being used to + * determine the bar length. * * @param base the new base value. * @@ -319,8 +318,7 @@ public boolean getUseYInterval() { /** * Sets the flag that determines whether the y-interval from the dataset is - * used to calculate the length of each bar, and sends a - * {@link RendererChangeEvent} to all registered listeners. + * used to calculate the length of each bar, and calls {@link #fireChangeEvent()}. * * @param use the flag. * @@ -346,8 +344,7 @@ public double getMargin() { } /** - * Sets the percentage amount by which the bars are trimmed and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the percentage amount by which the bars are trimmed and calls {@link #fireChangeEvent()}. * * @param margin the new margin. * @@ -371,7 +368,7 @@ public boolean isDrawBarOutline() { /** * Sets the flag that controls whether or not bar outlines are drawn and - * sends a {@link RendererChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param draw the flag. * @@ -395,8 +392,7 @@ public GradientPaintTransformer getGradientPaintTransformer() { } /** - * Sets the gradient paint transformer and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the gradient paint transformer and calls {@link #fireChangeEvent()}. * * @param transformer the transformer ({@code null} permitted). * @@ -421,8 +417,7 @@ public Shape getLegendBar() { } /** - * Sets the shape used to represent bars in each legend item and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the shape used to represent bars in each legend item and calls {@link #fireChangeEvent()}. * * @param bar the bar shape ({@code null} not permitted). * @@ -448,8 +443,7 @@ public ItemLabelPosition getPositiveItemLabelPositionFallback() { /** * Sets the fallback position for positive item labels that don't fit - * within a bar, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * within a bar, and calls {@link #fireChangeEvent()}. * * @param position the position ({@code null} permitted). * @@ -475,8 +469,7 @@ public ItemLabelPosition getNegativeItemLabelPositionFallback() { /** * Sets the fallback position for negative item labels that don't fit - * within a bar, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * within a bar, and calls {@link #fireChangeEvent()}. * * @param position the position ({@code null} permitted). * @@ -498,8 +491,7 @@ public XYBarPainter getBarPainter() { } /** - * Sets the bar painter and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the bar painter and calls {@link #fireChangeEvent()}. * * @param painter the painter ({@code null} not permitted). */ @@ -521,8 +513,7 @@ public boolean getShadowsVisible() { /** * Sets the flag that controls whether or not the renderer - * draws shadows for the bars, and sends a - * {@link RendererChangeEvent} to all registered listeners. + * draws shadows for the bars, and calls {@link #fireChangeEvent()}. * * @param visible the new flag value. */ @@ -541,8 +532,7 @@ public double getShadowXOffset() { } /** - * Sets the x-offset for the bar shadow and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the x-offset for the bar shadow and calls {@link #fireChangeEvent()}. * * @param offset the offset. */ @@ -561,8 +551,7 @@ public double getShadowYOffset() { } /** - * Sets the y-offset for the bar shadow and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the y-offset for the bar shadow and calls {@link #fireChangeEvent()}. * * @param offset the offset. */ @@ -581,9 +570,9 @@ public double getBarAlignmentFactor() { } /** - * Sets the bar alignment factor and sends a {@link RendererChangeEvent} - * to all registered listeners. If the alignment factor is outside the - * range 0.0 to 1.0, no alignment will be performed by the renderer. + * Sets the bar alignment factor and calls {@link #fireChangeEvent()}. If + * the alignment factor is outside the range 0.0 to 1.0, no alignment will + * be performed by the renderer. * * @param factor the factor. */ diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYBlockRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYBlockRenderer.java index 57268cd66..a12e7c57c 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYBlockRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYBlockRenderer.java @@ -44,7 +44,6 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; @@ -118,7 +117,7 @@ public double getBlockWidth() { /** * Sets the width of the blocks used to represent each data item and - * sends a {@link RendererChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param width the new width, in data/axis units (must be > 0.0). * @@ -147,7 +146,7 @@ public double getBlockHeight() { /** * Sets the height of the blocks used to represent each data item and - * sends a {@link RendererChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param height the new height, in data/axis units (must be > 0.0). * @@ -177,7 +176,7 @@ public RectangleAnchor getBlockAnchor() { /** * Sets the anchor point used to align a block at its (x, y) location and - * sends a {@link RendererChangeEvent} to all registered listeners. + * calls {@link #fireChangeEvent()}. * * @param anchor the anchor. * @@ -205,8 +204,7 @@ public PaintScale getPaintScale() { } /** - * Sets the paint scale used by the renderer and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint scale used by the renderer and calls {@link #fireChangeEvent()}. * * @param scale the scale ({@code null} not permitted). * diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYBoxAndWhiskerRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYBoxAndWhiskerRenderer.java index 5efa1f8bc..deb56087e 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYBoxAndWhiskerRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYBoxAndWhiskerRenderer.java @@ -57,7 +57,6 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.BoxAndWhiskerXYToolTipGenerator; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotOrientation; @@ -142,8 +141,7 @@ public double getBoxWidth() { } /** - * Sets the box width and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the box width and calls {@link #fireChangeEvent()}. *

    * If you set the width to a negative value, the renderer will calculate * the box width automatically based on the space available on the chart. @@ -171,8 +169,7 @@ public Paint getBoxPaint() { } /** - * Sets the paint used to fill boxes and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the paint used to fill boxes and calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} permitted). * @@ -195,8 +192,8 @@ public boolean getFillBox() { } /** - * Sets the flag that controls whether or not the box is filled and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the flag that controls whether or not the box is filled and + * calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -221,8 +218,8 @@ public Paint getArtifactPaint() { /** * Sets the paint used to paint the various artifacts such as outliers, - * farout symbol, median line and the averages ellipse, and sends a - * {@link RendererChangeEvent} to all registered listeners. + * farout symbol, median line and the averages ellipse, and + * calls {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} not permitted). * diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYDotRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYDotRenderer.java index ad5ce44af..bb3ead69f 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYDotRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYDotRenderer.java @@ -46,7 +46,6 @@ import org.jfree.chart.LegendItem; import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; @@ -105,8 +104,7 @@ public int getDotWidth() { } /** - * Sets the dot width and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the dot width and calls {@link #fireChangeEvent()}. * * @param w the new width (must be greater than zero). * @@ -134,8 +132,7 @@ public int getDotHeight() { } /** - * Sets the dot height and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the dot height and calls {@link #fireChangeEvent()}. * * @param h the new height (must be greater than zero). * @@ -163,8 +160,8 @@ public Shape getLegendShape() { } /** - * Sets the shape used as a line in each legend item and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the shape used as a line in each legend item and calls + * {@link #fireChangeEvent()}. * * @param shape the shape ({@code null} not permitted). * diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYErrorRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYErrorRenderer.java index d114b62f3..b0d53b442 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYErrorRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYErrorRenderer.java @@ -47,7 +47,6 @@ import java.util.Objects; import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; @@ -119,9 +118,9 @@ public boolean getDrawXError() { } /** - * Sets the flag that controls whether or not the renderer draws error - * bars for the x-values and, if the flag changes, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the flag that controls whether or not the renderer draws error bars + * for the x-values and, if the flag changes, calls + * {@link #fireChangeEvent()}. * * @param draw the flag value. * @@ -147,9 +146,9 @@ public boolean getDrawYError() { } /** - * Sets the flag that controls whether or not the renderer draws error - * bars for the y-values and, if the flag changes, sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the flag that controls whether or not the renderer draws error bars + * for the y-values and, if the flag changes, calls + * {@link #fireChangeEvent()}. * * @param draw the flag value. * @@ -175,8 +174,8 @@ public double getCapLength() { } /** - * Sets the length of the cap at the end of the error bars, and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the length of the cap at the end of the error bars, and calls + * {@link #fireChangeEvent()}. * * @param length the length (in Java2D units). * @@ -200,8 +199,8 @@ public Paint getErrorPaint() { } /** - * Sets the paint used to draw the error bars and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint used to draw the error bars and calls + * {@link #fireChangeEvent()}. * * @param paint the paint ({@code null} permitted). * @@ -226,8 +225,8 @@ public Stroke getErrorStroke() { } /** - * Sets the stroke used to draw the error bars and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the stroke used to draw the error bars and calls + * {@link #fireChangeEvent()}. * * @param stroke the stroke ({@code null} permitted). * diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYItemRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYItemRenderer.java index b6c8de823..5aeb19293 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYItemRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYItemRenderer.java @@ -50,7 +50,6 @@ import org.jfree.chart.LegendItemSource; import org.jfree.chart.annotations.XYAnnotation; import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.event.RendererChangeListener; import org.jfree.chart.labels.ItemLabelPosition; import org.jfree.chart.labels.XYItemLabelGenerator; @@ -174,20 +173,19 @@ public interface XYItemRenderer extends LegendItemSource { Boolean getSeriesVisible(int series); /** - * Sets the flag that controls whether a series is visible and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param visible the flag ({@code null} permitted). - * - * @see #getSeriesVisible(int) - */ + * Sets the flag that controls whether a series is visible and notifies all + * registered listeners. + * + * @param series the series index (zero-based). + * @param visible the flag ({@code null} permitted). + * + * @see #getSeriesVisible(int) + */ void setSeriesVisible(int series, Boolean visible); /** * Sets the flag that controls whether a series is visible and, if - * requested, sends a {@link RendererChangeEvent} to all registered - * listeners. + * requested, notifies all registered listeners. * * @param series the series index. * @param visible the flag ({@code null} permitted). @@ -207,8 +205,7 @@ public interface XYItemRenderer extends LegendItemSource { boolean getDefaultSeriesVisible(); /** - * Sets the default visibility and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the default visibility and notifies all registered listeners. * * @param visible the flag. * @@ -217,14 +214,14 @@ public interface XYItemRenderer extends LegendItemSource { void setDefaultSeriesVisible(boolean visible); /** - * Sets the default visibility and, if requested, sends - * a {@link RendererChangeEvent} to all registered listeners. - * - * @param visible the visibility. - * @param notify notify listeners? - * - * @see #getDefaultSeriesVisible() - */ + * Sets the default visibility and, if requested, notifies all registered + * listeners. + * + * @param visible the visibility. + * @param notify notify listeners? + * + * @see #getDefaultSeriesVisible() + */ void setDefaultSeriesVisible(boolean visible, boolean notify); // SERIES VISIBLE IN LEGEND (not yet respected by all renderers) @@ -255,7 +252,7 @@ public interface XYItemRenderer extends LegendItemSource { /** * Sets the flag that controls whether a series is visible in the legend - * and sends a {@link RendererChangeEvent} to all registered listeners. + * and notifies all registered listeners. * * @param series the series index (zero-based). * @param visible the flag ({@code null} permitted). @@ -266,8 +263,7 @@ public interface XYItemRenderer extends LegendItemSource { /** * Sets the flag that controls whether a series is visible in the legend - * and, if requested, sends a {@link RendererChangeEvent} to all registered - * listeners. + * and, if requested, notifies all registered listeners. * * @param series the series index. * @param visible the flag ({@code null} permitted). @@ -288,8 +284,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, boolean getDefaultSeriesVisibleInLegend(); /** - * Sets the default visibility in the legend and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default visibility in the legend and notifies all registered listeners. * * @param visible the flag. * @@ -298,14 +293,14 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultSeriesVisibleInLegend(boolean visible); /** - * Sets the default visibility in the legend and, if requested, sends - * a {@link RendererChangeEvent} to all registered listeners. - * - * @param visible the visibility. - * @param notify notify listeners? - * - * @see #getDefaultSeriesVisibleInLegend() - */ + * Sets the default visibility in the legend and, if requested, notifies all + * registered listeners. + * + * @param visible the visibility. + * @param notify notify listeners? + * + * @see #getDefaultSeriesVisibleInLegend() + */ void setDefaultSeriesVisibleInLegend(boolean visible, boolean notify); @@ -333,8 +328,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getSeriesPaint(int series); /** - * Sets the paint used for a series and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the paint used for a series and notifies all registered listeners. * * @param series the series index (zero-based). * @param paint the paint ({@code null} permitted). @@ -344,15 +338,15 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setSeriesPaint(int series, Paint paint); /** - * Sets the paint used for a series and sends a {@link RendererChangeEvent} - * to all registered listeners if requested. - * - * @param series the series index (zero-based). - * @param paint the paint ({@code null} permitted). - * @param notify send a change event? - * - * @see #getSeriesPaint(int) - */ + * Sets the paint used for a series and notifies all registered listeners if + * requested. + * + * @param series the series index (zero-based). + * @param paint the paint ({@code null} permitted). + * @param notify send a change event? + * + * @see #getSeriesPaint(int) + */ void setSeriesPaint(int series, Paint paint, boolean notify); /** @@ -365,8 +359,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getDefaultPaint(); /** - * Sets the default paint and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the default paint and notifies all registered listeners. * * @param paint the paint ({@code null} not permitted). * @@ -375,14 +368,13 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultPaint(Paint paint); /** - * Sets the default paint and sends a {@link RendererChangeEvent} to all - * registered listeners if requested. - * - * @param paint the paint ({@code null} not permitted). - * @param notify send a change event? - * - * @see #getDefaultPaint() - */ + * Sets the default paint and notifies all registered listeners if requested. + * + * @param paint the paint ({@code null} not permitted). + * @param notify send a change event? + * + * @see #getDefaultPaint() + */ void setDefaultPaint(Paint paint, boolean notify); // FILL PAINT @@ -407,8 +399,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getSeriesFillPaint(int series); /** - * Sets the paint used for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the paint used for a series and notifies all registered listeners. * * @param series the series index (zero-based). * @param paint the paint ({@code null} permitted). @@ -416,13 +407,13 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setSeriesFillPaint(int series, Paint paint); /** - * Sets the paint used for a series and sends a - * {@link RendererChangeEvent} to all registered listeners if requested. - * - * @param series the series index (zero-based). - * @param paint the paint ({@code null} permitted). - * @param notify send a change event? - */ + * Sets the paint used for a series and notifies all registered listeners if + * requested. + * + * @param series the series index (zero-based). + * @param paint the paint ({@code null} permitted). + * @param notify send a change event? + */ void setSeriesFillPaint(int series, Paint paint, boolean notify); /** @@ -433,20 +424,18 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getDefaultFillPaint(); /** - * Sets the default paint and sends a {@link RendererChangeEvent} to all - * registered listeners. - * - * @param paint the paint ({@code null} not permitted). - */ + * Sets the default paint and notifies all registered listeners. + * + * @param paint the paint ({@code null} not permitted). + */ void setDefaultFillPaint(Paint paint); /** - * Sets the default paint and sends a {@link RendererChangeEvent} to all - * registered listeners if requested. - * - * @param paint the paint ({@code null} not permitted). - * @param notify send a change event? - */ + * Sets the default paint and notifies all registered listeners if requested. + * + * @param paint the paint ({@code null} not permitted). + * @param notify send a change event? + */ void setDefaultFillPaint(Paint paint, boolean notify); //// OUTLINE PAINT //////////////////////////////////////////////////////// @@ -473,26 +462,26 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getSeriesOutlinePaint(int series); /** - * Sets the paint used for a series outline and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param paint the paint ({@code null} permitted). - * - * @see #getSeriesOutlinePaint(int) - */ + * Sets the paint used for a series outline and notifies all registered + * listeners. + * + * @param series the series index (zero-based). + * @param paint the paint ({@code null} permitted). + * + * @see #getSeriesOutlinePaint(int) + */ void setSeriesOutlinePaint(int series, Paint paint); /** - * Sets the paint used for a series outline and sends a - * {@link RendererChangeEvent} to all registered listeners if requested. - * - * @param series the series index (zero-based). - * @param paint the paint ({@code null} permitted). - * @param notify send a change event? - * - * @see #getSeriesOutlinePaint(int) - */ + * Sets the paint used for a series outline and notifies all registered + * listeners if requested. + * + * @param series the series index (zero-based). + * @param paint the paint ({@code null} permitted). + * @param notify send a change event? + * + * @see #getSeriesOutlinePaint(int) + */ void setSeriesOutlinePaint(int series, Paint paint, boolean notify); /** @@ -505,8 +494,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Paint getDefaultOutlinePaint(); /** - * Sets the default outline paint and sends a {@link RendererChangeEvent} to - * all registered listeners. + * Sets the default outline paint and notifies all registered listeners. * * @param paint the paint ({@code null} not permitted). * @@ -515,14 +503,14 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultOutlinePaint(Paint paint); /** - * Sets the default outline paint and sends a {@link RendererChangeEvent} to - * all registered listeners if requested. - * - * @param paint the paint ({@code null} not permitted). - * @param notify send a change event? - * - * @see #getDefaultOutlinePaint() - */ + * Sets the default outline paint and notifies all registered listeners if + * requested. + * + * @param paint the paint ({@code null} not permitted). + * @param notify send a change event? + * + * @see #getDefaultOutlinePaint() + */ void setDefaultOutlinePaint(Paint paint, boolean notify); //// STROKE /////////////////////////////////////////////////////////////// @@ -549,8 +537,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Stroke getSeriesStroke(int series); /** - * Sets the stroke used for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the stroke used for a series and notifies all registered listeners. * * @param series the series index (zero-based). * @param stroke the stroke ({@code null} permitted). @@ -560,15 +547,15 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setSeriesStroke(int series, Stroke stroke); /** - * Sets the stroke used for a series and sends a - * {@link RendererChangeEvent} to all registered listeners if requested. - * - * @param series the series index (zero-based). - * @param stroke the stroke ({@code null} permitted). - * @param notify send a change event? - * - * @see #getSeriesStroke(int) - */ + * Sets the stroke used for a series and notifies all registered listeners if + * requested. + * + * @param series the series index (zero-based). + * @param stroke the stroke ({@code null} permitted). + * @param notify send a change event? + * + * @see #getSeriesStroke(int) + */ void setSeriesStroke(int series, Stroke stroke, boolean notify); /** @@ -581,8 +568,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Stroke getDefaultStroke(); /** - * Sets the default stroke and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the default stroke and notifies all registered listeners. * * @param stroke the stroke ({@code null} not permitted). * @@ -591,8 +577,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultStroke(Stroke stroke); /** - * Sets the default stroke and sends a {@link RendererChangeEvent} to all - * registered listeners if requested. + * Sets the default stroke and notifies all registered listeners if requested. * * @param stroke the stroke ({@code null} not permitted). * @param notify send a change event? @@ -627,26 +612,26 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Stroke getSeriesOutlineStroke(int series); /** - * Sets the outline stroke used for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param stroke the stroke ({@code null} permitted). - * - * @see #getSeriesOutlineStroke(int) - */ + * Sets the outline stroke used for a series and notifies all registered + * listeners. + * + * @param series the series index (zero-based). + * @param stroke the stroke ({@code null} permitted). + * + * @see #getSeriesOutlineStroke(int) + */ void setSeriesOutlineStroke(int series, Stroke stroke); /** - * Sets the outline stroke used for a series and sends a - * {@link RendererChangeEvent} to all registered listeners if requested. - * - * @param series the series index (zero-based). - * @param stroke the stroke ({@code null} permitted). - * @param notify send a change event? - * - * @see #getSeriesOutlineStroke(int) - */ + * Sets the outline stroke used for a series and notifies all registered + * listeners if requested. + * + * @param series the series index (zero-based). + * @param stroke the stroke ({@code null} permitted). + * @param notify send a change event? + * + * @see #getSeriesOutlineStroke(int) + */ void setSeriesOutlineStroke(int series, Stroke stroke, boolean notify); /** @@ -659,8 +644,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Stroke getDefaultOutlineStroke(); /** - * Sets the base outline stroke and sends a {@link RendererChangeEvent} to - * all registered listeners. + * Sets the base outline stroke and notifies all registered listeners. * * @param stroke the stroke ({@code null} not permitted). * @@ -669,14 +653,14 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultOutlineStroke(Stroke stroke); /** - * Sets the base outline stroke and sends a {@link RendererChangeEvent} to - * all registered listeners if requested. - * - * @param stroke the stroke ({@code null} not permitted). - * @param notify send a change event. - * - * @see #getDefaultOutlineStroke() - */ + * Sets the base outline stroke and notifies all registered listeners if + * requested. + * + * @param stroke the stroke ({@code null} not permitted). + * @param notify send a change event. + * + * @see #getDefaultOutlineStroke() + */ void setDefaultOutlineStroke(Stroke stroke, boolean notify); //// SHAPE //////////////////////////////////////////////////////////////// @@ -703,8 +687,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Shape getSeriesShape(int series); /** - * Sets the shape used for a series and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the shape used for a series and notifies all registered listeners. * * @param series the series index (zero-based). * @param shape the shape ({@code null} permitted). @@ -714,15 +697,15 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setSeriesShape(int series, Shape shape); /** - * Sets the shape used for a series and sends a {@link RendererChangeEvent} - * to all registered listeners if requested. - * - * @param series the series index (zero-based). - * @param shape the shape ({@code null} permitted). - * @param notify send a change event? - * - * @see #getSeriesShape(int) - */ + * Sets the shape used for a series and notifies all registered listeners if + * requested. + * + * @param series the series index (zero-based). + * @param shape the shape ({@code null} permitted). + * @param notify send a change event? + * + * @see #getSeriesShape(int) + */ void setSeriesShape(int series, Shape shape, boolean notify); /** @@ -735,8 +718,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, Shape getDefaultShape(); /** - * Sets the default shape and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the default shape and notifies all registered listeners. * * @param shape the shape ({@code null} not permitted). * @@ -745,14 +727,13 @@ void setSeriesVisibleInLegend(int series, Boolean visible, void setDefaultShape(Shape shape); /** - * Sets the default shape and sends a {@link RendererChangeEvent} to all - * registered listeners if requested. - * - * @param shape the shape ({@code null} not permitted). - * @param notify send a change event? - * - * @see #getDefaultShape() - */ + * Sets the default shape and notifies all registered listeners if requested. + * + * @param shape the shape ({@code null} not permitted). + * @param notify send a change event? + * + * @see #getDefaultShape() + */ void setDefaultShape(Shape shape, boolean notify); @@ -781,8 +762,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, XYSeriesLabelGenerator getLegendItemLabelGenerator(); /** - * Sets the legend item label generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the legend item label generator and notifies all registered listeners. * * @param generator the generator ({@code null} not permitted). */ @@ -813,8 +793,7 @@ void setSeriesVisibleInLegend(int series, Boolean visible, XYToolTipGenerator getSeriesToolTipGenerator(int series); /** - * Sets the tool tip generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the tool tip generator for a series and notifies all registered listeners. * * @param series the series index (zero based). * @param generator the generator ({@code null} permitted). @@ -834,8 +813,7 @@ void setSeriesToolTipGenerator(int series, XYToolTipGenerator getDefaultToolTipGenerator(); /** - * Sets the default tool tip generator and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the default tool tip generator and notifies all registered listeners. * * @param generator the generator ({@code null} permitted). * @@ -884,8 +862,7 @@ void setSeriesToolTipGenerator(int series, /** * Sets a flag that controls the visibility of the item labels for a - * series and sends a {@link RendererChangeEvent} to all registered - * listeners. + * series and notifies all registered listeners. * * @param series the series index (zero-based). * @param visible the flag. @@ -906,7 +883,7 @@ void setSeriesToolTipGenerator(int series, /** * Sets the visibility of item labels for a series and, if requested, - * sends a {@link RendererChangeEvent} to all registered listeners. + * notifies all registered listeners. * * @param series the series index (zero-based). * @param visible the visible flag. @@ -937,15 +914,14 @@ void setSeriesItemLabelsVisible(int series, Boolean visible, void setDefaultItemLabelsVisible(boolean visible); /** - * Sets the default visibility for item labels and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param visible the visibility flag. - * @param notify a flag that controls whether or not listeners are - * notified. - * - * @see #getDefaultItemLabelsVisible() - */ + * Sets the default visibility for item labels and, if requested, notifies all + * registered listeners. + * + * @param visible the visibility flag. + * @param notify a flag that controls whether or not listeners are notified. + * + * @see #getDefaultItemLabelsVisible() + */ void setDefaultItemLabelsVisible(boolean visible, boolean notify); @@ -973,8 +949,7 @@ void setSeriesItemLabelsVisible(int series, Boolean visible, XYItemLabelGenerator getSeriesItemLabelGenerator(int series); /** - * Sets the item label generator for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label generator for a series and notifies all registered listeners. * * @param series the series index (zero based). * @param generator the generator ({@code null} permitted). @@ -994,8 +969,7 @@ void setSeriesItemLabelGenerator(int series, XYItemLabelGenerator getDefaultItemLabelGenerator(); /** - * Sets the default item label generator and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default item label generator and notifies all registered listeners. * * @param generator the generator ({@code null} permitted). * @@ -1025,8 +999,7 @@ void setSeriesItemLabelGenerator(int series, Font getSeriesItemLabelFont(int series); /** - * Sets the item label font for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label font for a series and notifies all registered listeners. * * @param series the series index (zero-based). * @param font the font ({@code null} permitted). @@ -1046,8 +1019,7 @@ void setSeriesItemLabelGenerator(int series, Font getDefaultItemLabelFont(); /** - * Sets the default item label font and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the default item label font and notifies all registered listeners. * * @param font the font ({@code null} not permitted). * @@ -1079,8 +1051,7 @@ void setSeriesItemLabelGenerator(int series, Paint getSeriesItemLabelPaint(int series); /** - * Sets the item label paint for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the item label paint for a series and notifies all registered listeners. * * @param series the series (zero based index). * @param paint the paint ({@code null} permitted). @@ -1097,8 +1068,7 @@ void setSeriesItemLabelGenerator(int series, Paint getDefaultItemLabelPaint(); /** - * Sets the default item label paint and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Sets the default item label paint and notifies all registered listeners. * * @param paint the paint ({@code null} not permitted). */ @@ -1127,7 +1097,7 @@ void setSeriesItemLabelGenerator(int series, /** * Sets the item label position for all positive values in a series and - * sends a {@link RendererChangeEvent} to all registered listeners. + * notifies all registered listeners. * * @param series the series index (zero-based). * @param position the position ({@code null} permitted). @@ -1137,8 +1107,7 @@ void setSeriesPositiveItemLabelPosition(int series, /** * Sets the item label position for all positive values in a series and (if - * requested) sends a {@link RendererChangeEvent} to all registered - * listeners. + * requested) notifies all registered listeners. * * @param series the series index (zero-based). * @param position the position ({@code null} permitted). @@ -1162,12 +1131,12 @@ void setSeriesPositiveItemLabelPosition(int series, void setDefaultPositiveItemLabelPosition(ItemLabelPosition position); /** - * Sets the default positive item label position and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param position the position ({@code null} not permitted). - * @param notify notify registered listeners? - */ + * Sets the default positive item label position and, if requested, notifies all + * registered listeners. + * + * @param position the position ({@code null} not permitted). + * @param notify notify registered listeners? + */ void setDefaultPositiveItemLabelPosition(ItemLabelPosition position, boolean notify); @@ -1196,24 +1165,23 @@ void setDefaultPositiveItemLabelPosition(ItemLabelPosition position, ItemLabelPosition getSeriesNegativeItemLabelPosition(int series); /** - * Sets the item label position for negative values in a series and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - * @param position the position ({@code null} permitted). - */ + * Sets the item label position for negative values in a series and notifies all + * registered listeners. + * + * @param series the series index (zero-based). + * @param position the position ({@code null} permitted). + */ void setSeriesNegativeItemLabelPosition(int series, ItemLabelPosition position); /** - * Sets the item label position for negative values in a series and (if - * requested) sends a {@link RendererChangeEvent} to all registered - * listeners. - * - * @param series the series index (zero-based). - * @param position the position ({@code null} permitted). - * @param notify notify registered listeners? - */ + * Sets the item label position for negative values in a series and (if + * requested) notifies all registered listeners. + * + * @param series the series index (zero-based). + * @param position the position ({@code null} permitted). + * @param notify notify registered listeners? + */ void setSeriesNegativeItemLabelPosition(int series, ItemLabelPosition position, boolean notify); @@ -1225,20 +1193,20 @@ void setSeriesNegativeItemLabelPosition(int series, ItemLabelPosition getDefaultNegativeItemLabelPosition(); /** - * Sets the default item label position for negative values and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param position the position ({@code null} not permitted). - */ + * Sets the default item label position for negative values and notifies all + * registered listeners. + * + * @param position the position ({@code null} not permitted). + */ void setDefaultNegativeItemLabelPosition(ItemLabelPosition position); /** - * Sets the default negative item label position and, if requested, sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param position the position ({@code null} not permitted). - * @param notify notify registered listeners? - */ + * Sets the default negative item label position and, if requested, notifies all + * registered listeners. + * + * @param position the position ({@code null} not permitted). + * @param notify notify registered listeners? + */ void setDefaultNegativeItemLabelPosition(ItemLabelPosition position, boolean notify); @@ -1319,9 +1287,8 @@ void setSeriesCreateEntities(int series, Boolean create, //// ANNOTATIONS ////////////////////////////////////////////////////////// /** - * Adds an annotation and sends a {@link RendererChangeEvent} to all - * registered listeners. The annotation is added to the foreground - * layer. + * Adds an annotation and notifies all registered listeners. The annotation + * is added to the foreground layer. * * @param annotation the annotation ({@code null} not permitted). */ @@ -1336,8 +1303,7 @@ void setSeriesCreateEntities(int series, Boolean create, void addAnnotation(XYAnnotation annotation, Layer layer); /** - * Removes the specified annotation and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Removes the specified annotation and notifies all registered listeners. * * @param annotation the annotation to remove ({@code null} not * permitted). @@ -1348,8 +1314,7 @@ void setSeriesCreateEntities(int series, Boolean create, boolean removeAnnotation(XYAnnotation annotation); /** - * Removes all annotations and sends a {@link RendererChangeEvent} - * to all registered listeners. + * Removes all annotations and notifies all registered listeners. */ void removeAnnotations(); diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYItemRendererState.java b/src/main/java/org/jfree/chart/renderer/xy/XYItemRendererState.java index 8131b33cd..2ff1e0683 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYItemRendererState.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYItemRendererState.java @@ -40,7 +40,6 @@ import java.awt.geom.Line2D; import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.RendererState; import org.jfree.data.xy.XYDataset; @@ -126,7 +125,7 @@ public int getLastItemIndex() { } /** - * This method is called by the {@link XYPlot} when it starts a pass + * This method is called by the {@link org.jfree.chart.plot.XYPlot} when it starts a pass * through the (visible) items in a series. The default implementation * records the first and last item indices - override this method to * implement additional specialised behaviour. @@ -147,7 +146,7 @@ public void startSeriesPass(XYDataset dataset, int series, int firstItem, } /** - * This method is called by the {@link XYPlot} when it ends a pass + * This method is called by the {@link org.jfree.chart.plot.XYPlot} when it ends a pass * through the (visible) items in a series. The default implementation * does nothing, but you can override this method to implement specialised * behaviour. diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYLineAndShapeRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYLineAndShapeRenderer.java index 2bf615cf4..7610a7899 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYLineAndShapeRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYLineAndShapeRenderer.java @@ -52,7 +52,6 @@ import org.jfree.chart.LegendItem; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; @@ -179,8 +178,7 @@ public boolean getDrawSeriesLineAsPath() { /** * Sets the flag that controls whether each series is drawn as a - * single path and sends a {@link RendererChangeEvent} to all registered - * listeners. + * single path and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -239,8 +237,7 @@ public Boolean getSeriesLinesVisible(int series) { } /** - * Sets the 'lines visible' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'lines visible' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param flag the flag ({@code null} permitted). @@ -253,8 +250,7 @@ public void setSeriesLinesVisible(int series, Boolean flag) { } /** - * Sets the 'lines visible' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'lines visible' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param visible the flag. @@ -277,8 +273,7 @@ public boolean getDefaultLinesVisible() { } /** - * Sets the default 'lines visible' flag and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default 'lines visible' flag and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -301,8 +296,7 @@ public Shape getLegendLine() { } /** - * Sets the shape used as a line in each legend item and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the shape used as a line in each legend item and calls {@link #fireChangeEvent()}. * * @param line the line ({@code null} not permitted). * @@ -352,8 +346,7 @@ public Boolean getSeriesShapesVisible(int series) { } /** - * Sets the 'shapes visible' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'shapes visible' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param visible the flag. @@ -365,8 +358,7 @@ public void setSeriesShapesVisible(int series, boolean visible) { } /** - * Sets the 'shapes visible' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'shapes visible' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param flag the flag. @@ -390,8 +382,7 @@ public boolean getDefaultShapesVisible() { } /** - * Sets the default 'shapes visible' flag and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default 'shapes visible' flag and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -441,8 +432,7 @@ public Boolean getSeriesShapesFilled(int series) { } /** - * Sets the 'shapes filled' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'shapes filled' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param flag the flag. @@ -454,8 +444,7 @@ public void setSeriesShapesFilled(int series, boolean flag) { } /** - * Sets the 'shapes filled' flag for a series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'shapes filled' flag for a series and calls {@link #fireChangeEvent()}. * * @param series the series index (zero-based). * @param flag the flag. @@ -479,8 +468,7 @@ public boolean getDefaultShapesFilled() { } /** - * Sets the default 'shapes filled' flag and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the default 'shapes filled' flag and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -505,8 +493,7 @@ public boolean getDrawOutlines() { /** * Sets the flag that controls whether outlines are drawn for - * shapes, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * shapes, and calls {@link #fireChangeEvent()}. *

    * In some cases, shapes look better if they do NOT have an outline, but * this flag allows you to set your own preference. @@ -539,8 +526,7 @@ public boolean getUseFillPaint() { /** * Sets the flag that controls whether the fill paint is used to fill - * shapes, and sends a {@link RendererChangeEvent} to all - * registered listeners. + * shapes, and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -567,8 +553,7 @@ public boolean getUseOutlinePaint() { /** * Sets the flag that controls whether the outline paint is used to draw - * shape outlines, and sends a {@link RendererChangeEvent} to all - * registered listeners. + * shape outlines, and calls {@link #fireChangeEvent()}. *

    * Refer to {@code XYLineAndShapeRendererDemo2.java} to see the * effect of this flag. diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYSplineRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYSplineRenderer.java index f8b5cbf19..a05caf51d 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYSplineRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYSplineRenderer.java @@ -1,494 +1,490 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------------------- - * XYSplineRenderer.java - * --------------------- - * (C) Copyright 2007-present, by Klaus Rheinwald and Contributors. - * - * Original Author: Klaus Rheinwald; - * Contributor(s): Tobias von Petersdorff (tvp@math.umd.edu, - * http://www.wam.umd.edu/~petersd/); - * David Gilbert; - * - */ - -package org.jfree.chart.renderer.xy; - -import java.awt.GradientPaint; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.geom.GeneralPath; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.ui.GradientPaintTransformer; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.ui.StandardGradientPaintTransformer; -import org.jfree.chart.util.Args; -import org.jfree.data.xy.XYDataset; - -/** - * A renderer that connects data points with natural cubic splines and/or - * draws shapes at each data point. This renderer is designed for use with - * the {@link XYPlot} class. The example shown here is generated by the - * {@code XYSplineRendererDemo1.java} program included in the JFreeChart - * demo collection: - *

    - * XYSplineRendererSample.png - */ -public class XYSplineRenderer extends XYLineAndShapeRenderer { - - /** - * An enumeration of the fill types for the renderer. - */ - public enum FillType { - - /** No fill. */ - NONE, - - /** Fill towards zero. */ - TO_ZERO, - - /** Fill to lower bound. */ - TO_LOWER_BOUND, - - /** Fill to upper bound. */ - TO_UPPER_BOUND - } - - /** - * Represents state information that applies to a single rendering of - * a chart. - */ - public static class XYSplineState extends State { - - /** The area to fill under the curve. */ - public GeneralPath fillArea; - - /** The points. */ - public List points; - - /** - * Creates a new state instance. - * - * @param info the plot rendering info. - */ - public XYSplineState(PlotRenderingInfo info) { - super(info); - this.fillArea = new GeneralPath(); - this.points = new ArrayList<>(); - } - } - - /** - * Resolution of splines (number of line segments between points) - */ - private int precision; - - /** - * A flag that can be set to specify - * to fill the area under the spline. - */ - private FillType fillType; - - private GradientPaintTransformer gradientPaintTransformer; - - /** - * Creates a new instance with the precision attribute defaulting to 5 - * and no fill of the area 'under' the spline. - */ - public XYSplineRenderer() { - this(5, FillType.NONE); - } - - /** - * Creates a new renderer with the specified precision - * and no fill of the area 'under' (between '0' and) the spline. - * - * @param precision the number of points between data items. - */ - public XYSplineRenderer(int precision) { - this(precision, FillType.NONE); - } - - /** - * Creates a new renderer with the specified precision - * and specified fill of the area 'under' (between '0' and) the spline. - * - * @param precision the number of points between data items. - * @param fillType the type of fill beneath the curve ({@code null} - * not permitted). - */ - public XYSplineRenderer(int precision, FillType fillType) { - super(); - if (precision <= 0) { - throw new IllegalArgumentException("Requires precision > 0."); - } - Args.nullNotPermitted(fillType, "fillType"); - this.precision = precision; - this.fillType = fillType; - this.gradientPaintTransformer = new StandardGradientPaintTransformer(); - } - - /** - * Returns the number of line segments used to approximate the spline - * curve between data points. - * - * @return The number of line segments. - * - * @see #setPrecision(int) - */ - public int getPrecision() { - return this.precision; - } - - /** - * Set the resolution of splines and sends a {@link RendererChangeEvent} - * to all registered listeners. - * - * @param p number of line segments between points (must be > 0). - * - * @see #getPrecision() - */ - public void setPrecision(int p) { - if (p <= 0) { - throw new IllegalArgumentException("Requires p > 0."); - } - this.precision = p; - fireChangeEvent(); - } - - /** - * Returns the type of fill that the renderer draws beneath the curve. - * - * @return The type of fill (never {@code null}). - * - * @see #setFillType(FillType) - */ - public FillType getFillType() { - return this.fillType; - } - - /** - * Set the fill type and sends a {@link RendererChangeEvent} - * to all registered listeners. - * - * @param fillType the fill type ({@code null} not permitted). - * - * @see #getFillType() - */ - public void setFillType(FillType fillType) { - this.fillType = fillType; - fireChangeEvent(); - } - - /** - * Returns the gradient paint transformer, or {@code null}. - * - * @return The gradient paint transformer (possibly {@code null}). - */ - public GradientPaintTransformer getGradientPaintTransformer() { - return this.gradientPaintTransformer; - } - - /** - * Sets the gradient paint transformer and sends a - * {@link RendererChangeEvent} to all registered listeners. - * - * @param gpt the transformer ({@code null} permitted). - */ - public void setGradientPaintTransformer(GradientPaintTransformer gpt) { - this.gradientPaintTransformer = gpt; - fireChangeEvent(); - } - - /** - * Initialises the renderer. - *

    - * This method will be called before the first item is rendered, giving the - * renderer an opportunity to initialise any state information it wants to - * maintain. The renderer can do nothing if it chooses. - * - * @param g2 the graphics device. - * @param dataArea the area inside the axes. - * @param plot the plot. - * @param data the data. - * @param info an optional info collection object to return data back to - * the caller. - * - * @return The renderer state. - */ - @Override - public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, - XYPlot plot, XYDataset data, PlotRenderingInfo info) { - - setDrawSeriesLineAsPath(true); - XYSplineState state = new XYSplineState(info); - state.setProcessVisibleItemsOnly(false); - return state; - } - - /** - * Draws the item (first pass). This method draws the lines - * connecting the items. Instead of drawing separate lines, - * a GeneralPath is constructed and drawn at the end of - * the series painting. - * - * @param g2 the graphics device. - * @param state the renderer state. - * @param plot the plot (can be used to obtain standard color information - * etc). - * @param dataset the dataset. - * @param pass the pass. - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * @param xAxis the domain axis. - * @param yAxis the range axis. - * @param dataArea the area within which the data is being drawn. - */ - @Override - protected void drawPrimaryLineAsPath(XYItemRendererState state, - Graphics2D g2, XYPlot plot, XYDataset dataset, int pass, - int series, int item, ValueAxis xAxis, ValueAxis yAxis, - Rectangle2D dataArea) { - - XYSplineState s = (XYSplineState) state; - RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); - RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); - - // get the data points - double x1 = dataset.getXValue(series, item); - double y1 = dataset.getYValue(series, item); - double transX1 = xAxis.valueToJava2D(x1, dataArea, xAxisLocation); - double transY1 = yAxis.valueToJava2D(y1, dataArea, yAxisLocation); - - // Collect points - if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) { - Point2D p = plot.getOrientation() == PlotOrientation.HORIZONTAL - ? new Point2D.Float((float) transY1, (float) transX1) - : new Point2D.Float((float) transX1, (float) transY1); - if (!s.points.contains(p)) - s.points.add(p); - } - - if (item == dataset.getItemCount(series) - 1) { // construct path - if (s.points.size() > 1) { - Point2D origin; - if (this.fillType == FillType.TO_ZERO) { - float xz = (float) xAxis.valueToJava2D(0, dataArea, - yAxisLocation); - float yz = (float) yAxis.valueToJava2D(0, dataArea, - yAxisLocation); - origin = plot.getOrientation() == PlotOrientation.HORIZONTAL - ? new Point2D.Float(yz, xz) - : new Point2D.Float(xz, yz); - } else if (this.fillType == FillType.TO_LOWER_BOUND) { - float xlb = (float) xAxis.valueToJava2D( - xAxis.getLowerBound(), dataArea, xAxisLocation); - float ylb = (float) yAxis.valueToJava2D( - yAxis.getLowerBound(), dataArea, yAxisLocation); - origin = plot.getOrientation() == PlotOrientation.HORIZONTAL - ? new Point2D.Float(ylb, xlb) - : new Point2D.Float(xlb, ylb); - } else {// fillType == TO_UPPER_BOUND - float xub = (float) xAxis.valueToJava2D( - xAxis.getUpperBound(), dataArea, xAxisLocation); - float yub = (float) yAxis.valueToJava2D( - yAxis.getUpperBound(), dataArea, yAxisLocation); - origin = plot.getOrientation() == PlotOrientation.HORIZONTAL - ? new Point2D.Float(yub, xub) - : new Point2D.Float(xub, yub); - } - - // we need at least two points to draw something - Point2D cp0 = s.points.get(0); - s.seriesPath.moveTo(cp0.getX(), cp0.getY()); - if (this.fillType != FillType.NONE) { - if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { - s.fillArea.moveTo(origin.getX(), cp0.getY()); - } else { - s.fillArea.moveTo(cp0.getX(), origin.getY()); - } - s.fillArea.lineTo(cp0.getX(), cp0.getY()); - } - if (s.points.size() == 2) { - // we need at least 3 points to spline. Draw simple line - // for two points - Point2D cp1 = s.points.get(1); - if (this.fillType != FillType.NONE) { - s.fillArea.lineTo(cp1.getX(), cp1.getY()); - s.fillArea.lineTo(cp1.getX(), origin.getY()); - s.fillArea.closePath(); - } - s.seriesPath.lineTo(cp1.getX(), cp1.getY()); - } else { - // construct spline - int np = s.points.size(); // number of points - float[] d = new float[np]; // Newton form coefficients - float[] x = new float[np]; // x-coordinates of nodes - float y, oldy; - float t, oldt; - - float[] a = new float[np]; - float t1; - float t2; - float[] h = new float[np]; - - for (int i = 0; i < np; i++) { - Point2D.Float cpi = (Point2D.Float) s.points.get(i); - x[i] = cpi.x; - d[i] = cpi.y; - } - - for (int i = 1; i <= np - 1; i++) - h[i] = x[i] - x[i - 1]; - - float[] sub = new float[np - 1]; - float[] diag = new float[np - 1]; - float[] sup = new float[np - 1]; - - for (int i = 1; i <= np - 2; i++) { - diag[i] = (h[i] + h[i + 1]) / 3; - sup[i] = h[i + 1] / 6; - sub[i] = h[i] / 6; - a[i] = (d[i + 1] - d[i]) / h[i + 1] - - (d[i] - d[i - 1]) / h[i]; - } - solveTridiag(sub, diag, sup, a, np - 2); - - // note that a[0]=a[np-1]=0 - oldt = x[0]; - oldy = d[0]; - for (int i = 1; i <= np - 1; i++) { - // loop over intervals between nodes - for (int j = 1; j <= this.precision; j++) { - t1 = (h[i] * j) / this.precision; - t2 = h[i] - t1; - y = ((-a[i - 1] / 6 * (t2 + h[i]) * t1 + d[i - 1]) - * t2 + (-a[i] / 6 * (t1 + h[i]) * t2 - + d[i]) * t1) / h[i]; - t = x[i - 1] + t1; - s.seriesPath.lineTo(t, y); - if (this.fillType != FillType.NONE) { - s.fillArea.lineTo(t, y); - } - } - } - } - // Add last point @ y=0 for fillPath and close path - if (this.fillType != FillType.NONE) { - if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { - s.fillArea.lineTo(origin.getX(), s.points.get( - s.points.size() - 1).getY()); - } else { - s.fillArea.lineTo(s.points.get( - s.points.size() - 1).getX(), origin.getY()); - } - s.fillArea.closePath(); - } - - // fill under the curve... - if (this.fillType != FillType.NONE) { - Paint fp = getSeriesFillPaint(series); - if (this.gradientPaintTransformer != null - && fp instanceof GradientPaint) { - GradientPaint gp = this.gradientPaintTransformer - .transform((GradientPaint) fp, s.fillArea); - g2.setPaint(gp); - } else { - g2.setPaint(fp); - } - g2.fill(s.fillArea); - s.fillArea.reset(); - } - // then draw the line... - drawFirstPassShape(g2, pass, series, item, s.seriesPath); - } - // reset points vector - s.points = new ArrayList<>(); - } - } - - private void solveTridiag(float[] sub, float[] diag, float[] sup, - float[] b, int n) { -/* solve linear system with tridiagonal n by n matrix a - using Gaussian elimination *without* pivoting - where a(i,i-1) = sub[i] for 2<=i<=n - a(i,i) = diag[i] for 1<=i<=n - a(i,i+1) = sup[i] for 1<=i<=n-1 - (the values sub[1], sup[n] are ignored) - right hand side vector b[1:n] is overwritten with solution - NOTE: 1...n is used in all arrays, 0 is unused */ - int i; -/* factorization and forward substitution */ - for (i = 2; i <= n; i++) { - sub[i] /= diag[i - 1]; - diag[i] -= sub[i] * sup[i - 1]; - b[i] -= sub[i] * b[i - 1]; - } - b[n] /= diag[n]; - for (i = n - 1; i >= 1; i--) - b[i] = (b[i] - sup[i] * b[i + 1]) / diag[i]; - } - - /** - * Tests this renderer for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof XYSplineRenderer)) { - return false; - } - XYSplineRenderer that = (XYSplineRenderer) obj; - if (this.precision != that.precision) { - return false; - } - if (this.fillType != that.fillType) { - return false; - } - if (!Objects.equals(this.gradientPaintTransformer, - that.gradientPaintTransformer)) { - return false; - } - return super.equals(obj); - } -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------------------- + * XYSplineRenderer.java + * --------------------- + * (C) Copyright 2007-present, by Klaus Rheinwald and Contributors. + * + * Original Author: Klaus Rheinwald; + * Contributor(s): Tobias von Petersdorff (tvp@math.umd.edu, + * http://www.wam.umd.edu/~petersd/); + * David Gilbert; + * + */ + +package org.jfree.chart.renderer.xy; + +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.geom.GeneralPath; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.ui.GradientPaintTransformer; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.StandardGradientPaintTransformer; +import org.jfree.chart.util.Args; +import org.jfree.data.xy.XYDataset; + +/** + * A renderer that connects data points with natural cubic splines and/or + * draws shapes at each data point. This renderer is designed for use with + * the {@link XYPlot} class. The example shown here is generated by the + * {@code XYSplineRendererDemo1.java} program included in the JFreeChart + * demo collection: + *

    + * XYSplineRendererSample.png + */ +public class XYSplineRenderer extends XYLineAndShapeRenderer { + + /** + * An enumeration of the fill types for the renderer. + */ + public enum FillType { + + /** No fill. */ + NONE, + + /** Fill towards zero. */ + TO_ZERO, + + /** Fill to lower bound. */ + TO_LOWER_BOUND, + + /** Fill to upper bound. */ + TO_UPPER_BOUND + } + + /** + * Represents state information that applies to a single rendering of + * a chart. + */ + public static class XYSplineState extends State { + + /** The area to fill under the curve. */ + public GeneralPath fillArea; + + /** The points. */ + public List points; + + /** + * Creates a new state instance. + * + * @param info the plot rendering info. + */ + public XYSplineState(PlotRenderingInfo info) { + super(info); + this.fillArea = new GeneralPath(); + this.points = new ArrayList<>(); + } + } + + /** + * Resolution of splines (number of line segments between points) + */ + private int precision; + + /** + * A flag that can be set to specify + * to fill the area under the spline. + */ + private FillType fillType; + + private GradientPaintTransformer gradientPaintTransformer; + + /** + * Creates a new instance with the precision attribute defaulting to 5 + * and no fill of the area 'under' the spline. + */ + public XYSplineRenderer() { + this(5, FillType.NONE); + } + + /** + * Creates a new renderer with the specified precision + * and no fill of the area 'under' (between '0' and) the spline. + * + * @param precision the number of points between data items. + */ + public XYSplineRenderer(int precision) { + this(precision, FillType.NONE); + } + + /** + * Creates a new renderer with the specified precision + * and specified fill of the area 'under' (between '0' and) the spline. + * + * @param precision the number of points between data items. + * @param fillType the type of fill beneath the curve ({@code null} + * not permitted). + */ + public XYSplineRenderer(int precision, FillType fillType) { + super(); + if (precision <= 0) { + throw new IllegalArgumentException("Requires precision > 0."); + } + Args.nullNotPermitted(fillType, "fillType"); + this.precision = precision; + this.fillType = fillType; + this.gradientPaintTransformer = new StandardGradientPaintTransformer(); + } + + /** + * Returns the number of line segments used to approximate the spline + * curve between data points. + * + * @return The number of line segments. + * + * @see #setPrecision(int) + */ + public int getPrecision() { + return this.precision; + } + + /** + * Set the resolution of splines and calls {@link #fireChangeEvent()}. + * + * @param p number of line segments between points (must be > 0). + * + * @see #getPrecision() + */ + public void setPrecision(int p) { + if (p <= 0) { + throw new IllegalArgumentException("Requires p > 0."); + } + this.precision = p; + fireChangeEvent(); + } + + /** + * Returns the type of fill that the renderer draws beneath the curve. + * + * @return The type of fill (never {@code null}). + * + * @see #setFillType(FillType) + */ + public FillType getFillType() { + return this.fillType; + } + + /** + * Set the fill type and calls {@link #fireChangeEvent()}. + * + * @param fillType the fill type ({@code null} not permitted). + * + * @see #getFillType() + */ + public void setFillType(FillType fillType) { + this.fillType = fillType; + fireChangeEvent(); + } + + /** + * Returns the gradient paint transformer, or {@code null}. + * + * @return The gradient paint transformer (possibly {@code null}). + */ + public GradientPaintTransformer getGradientPaintTransformer() { + return this.gradientPaintTransformer; + } + + /** + * Sets the gradient paint transformer and calls {@link #fireChangeEvent()}. + * + * @param gpt the transformer ({@code null} permitted). + */ + public void setGradientPaintTransformer(GradientPaintTransformer gpt) { + this.gradientPaintTransformer = gpt; + fireChangeEvent(); + } + + /** + * Initialises the renderer. + *

    + * This method will be called before the first item is rendered, giving the + * renderer an opportunity to initialise any state information it wants to + * maintain. The renderer can do nothing if it chooses. + * + * @param g2 the graphics device. + * @param dataArea the area inside the axes. + * @param plot the plot. + * @param data the data. + * @param info an optional info collection object to return data back to + * the caller. + * + * @return The renderer state. + */ + @Override + public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, + XYPlot plot, XYDataset data, PlotRenderingInfo info) { + + setDrawSeriesLineAsPath(true); + XYSplineState state = new XYSplineState(info); + state.setProcessVisibleItemsOnly(false); + return state; + } + + /** + * Draws the item (first pass). This method draws the lines + * connecting the items. Instead of drawing separate lines, + * a GeneralPath is constructed and drawn at the end of + * the series painting. + * + * @param g2 the graphics device. + * @param state the renderer state. + * @param plot the plot (can be used to obtain standard color information + * etc). + * @param dataset the dataset. + * @param pass the pass. + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * @param xAxis the domain axis. + * @param yAxis the range axis. + * @param dataArea the area within which the data is being drawn. + */ + @Override + protected void drawPrimaryLineAsPath(XYItemRendererState state, + Graphics2D g2, XYPlot plot, XYDataset dataset, int pass, + int series, int item, ValueAxis xAxis, ValueAxis yAxis, + Rectangle2D dataArea) { + + XYSplineState s = (XYSplineState) state; + RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); + RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); + + // get the data points + double x1 = dataset.getXValue(series, item); + double y1 = dataset.getYValue(series, item); + double transX1 = xAxis.valueToJava2D(x1, dataArea, xAxisLocation); + double transY1 = yAxis.valueToJava2D(y1, dataArea, yAxisLocation); + + // Collect points + if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) { + Point2D p = plot.getOrientation() == PlotOrientation.HORIZONTAL + ? new Point2D.Float((float) transY1, (float) transX1) + : new Point2D.Float((float) transX1, (float) transY1); + if (!s.points.contains(p)) + s.points.add(p); + } + + if (item == dataset.getItemCount(series) - 1) { // construct path + if (s.points.size() > 1) { + Point2D origin; + if (this.fillType == FillType.TO_ZERO) { + float xz = (float) xAxis.valueToJava2D(0, dataArea, + yAxisLocation); + float yz = (float) yAxis.valueToJava2D(0, dataArea, + yAxisLocation); + origin = plot.getOrientation() == PlotOrientation.HORIZONTAL + ? new Point2D.Float(yz, xz) + : new Point2D.Float(xz, yz); + } else if (this.fillType == FillType.TO_LOWER_BOUND) { + float xlb = (float) xAxis.valueToJava2D( + xAxis.getLowerBound(), dataArea, xAxisLocation); + float ylb = (float) yAxis.valueToJava2D( + yAxis.getLowerBound(), dataArea, yAxisLocation); + origin = plot.getOrientation() == PlotOrientation.HORIZONTAL + ? new Point2D.Float(ylb, xlb) + : new Point2D.Float(xlb, ylb); + } else {// fillType == TO_UPPER_BOUND + float xub = (float) xAxis.valueToJava2D( + xAxis.getUpperBound(), dataArea, xAxisLocation); + float yub = (float) yAxis.valueToJava2D( + yAxis.getUpperBound(), dataArea, yAxisLocation); + origin = plot.getOrientation() == PlotOrientation.HORIZONTAL + ? new Point2D.Float(yub, xub) + : new Point2D.Float(xub, yub); + } + + // we need at least two points to draw something + Point2D cp0 = s.points.get(0); + s.seriesPath.moveTo(cp0.getX(), cp0.getY()); + if (this.fillType != FillType.NONE) { + if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { + s.fillArea.moveTo(origin.getX(), cp0.getY()); + } else { + s.fillArea.moveTo(cp0.getX(), origin.getY()); + } + s.fillArea.lineTo(cp0.getX(), cp0.getY()); + } + if (s.points.size() == 2) { + // we need at least 3 points to spline. Draw simple line + // for two points + Point2D cp1 = s.points.get(1); + if (this.fillType != FillType.NONE) { + s.fillArea.lineTo(cp1.getX(), cp1.getY()); + s.fillArea.lineTo(cp1.getX(), origin.getY()); + s.fillArea.closePath(); + } + s.seriesPath.lineTo(cp1.getX(), cp1.getY()); + } else { + // construct spline + int np = s.points.size(); // number of points + float[] d = new float[np]; // Newton form coefficients + float[] x = new float[np]; // x-coordinates of nodes + float y, oldy; + float t, oldt; + + float[] a = new float[np]; + float t1; + float t2; + float[] h = new float[np]; + + for (int i = 0; i < np; i++) { + Point2D.Float cpi = (Point2D.Float) s.points.get(i); + x[i] = cpi.x; + d[i] = cpi.y; + } + + for (int i = 1; i <= np - 1; i++) + h[i] = x[i] - x[i - 1]; + + float[] sub = new float[np - 1]; + float[] diag = new float[np - 1]; + float[] sup = new float[np - 1]; + + for (int i = 1; i <= np - 2; i++) { + diag[i] = (h[i] + h[i + 1]) / 3; + sup[i] = h[i + 1] / 6; + sub[i] = h[i] / 6; + a[i] = (d[i + 1] - d[i]) / h[i + 1] + - (d[i] - d[i - 1]) / h[i]; + } + solveTridiag(sub, diag, sup, a, np - 2); + + // note that a[0]=a[np-1]=0 + oldt = x[0]; + oldy = d[0]; + for (int i = 1; i <= np - 1; i++) { + // loop over intervals between nodes + for (int j = 1; j <= this.precision; j++) { + t1 = (h[i] * j) / this.precision; + t2 = h[i] - t1; + y = ((-a[i - 1] / 6 * (t2 + h[i]) * t1 + d[i - 1]) + * t2 + (-a[i] / 6 * (t1 + h[i]) * t2 + + d[i]) * t1) / h[i]; + t = x[i - 1] + t1; + s.seriesPath.lineTo(t, y); + if (this.fillType != FillType.NONE) { + s.fillArea.lineTo(t, y); + } + } + } + } + // Add last point @ y=0 for fillPath and close path + if (this.fillType != FillType.NONE) { + if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { + s.fillArea.lineTo(origin.getX(), s.points.get( + s.points.size() - 1).getY()); + } else { + s.fillArea.lineTo(s.points.get( + s.points.size() - 1).getX(), origin.getY()); + } + s.fillArea.closePath(); + } + + // fill under the curve... + if (this.fillType != FillType.NONE) { + Paint fp = getSeriesFillPaint(series); + if (this.gradientPaintTransformer != null + && fp instanceof GradientPaint) { + GradientPaint gp = this.gradientPaintTransformer + .transform((GradientPaint) fp, s.fillArea); + g2.setPaint(gp); + } else { + g2.setPaint(fp); + } + g2.fill(s.fillArea); + s.fillArea.reset(); + } + // then draw the line... + drawFirstPassShape(g2, pass, series, item, s.seriesPath); + } + // reset points vector + s.points = new ArrayList<>(); + } + } + + private void solveTridiag(float[] sub, float[] diag, float[] sup, + float[] b, int n) { +/* solve linear system with tridiagonal n by n matrix a + using Gaussian elimination *without* pivoting + where a(i,i-1) = sub[i] for 2<=i<=n + a(i,i) = diag[i] for 1<=i<=n + a(i,i+1) = sup[i] for 1<=i<=n-1 + (the values sub[1], sup[n] are ignored) + right hand side vector b[1:n] is overwritten with solution + NOTE: 1...n is used in all arrays, 0 is unused */ + int i; +/* factorization and forward substitution */ + for (i = 2; i <= n; i++) { + sub[i] /= diag[i - 1]; + diag[i] -= sub[i] * sup[i - 1]; + b[i] -= sub[i] * b[i - 1]; + } + b[n] /= diag[n]; + for (i = n - 1; i >= 1; i--) + b[i] = (b[i] - sup[i] * b[i + 1]) / diag[i]; + } + + /** + * Tests this renderer for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof XYSplineRenderer)) { + return false; + } + XYSplineRenderer that = (XYSplineRenderer) obj; + if (this.precision != that.precision) { + return false; + } + if (this.fillType != that.fillType) { + return false; + } + if (!Objects.equals(this.gradientPaintTransformer, + that.gradientPaintTransformer)) { + return false; + } + return super.equals(obj); + } +} diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYStepAreaRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYStepAreaRenderer.java index d07fe4e56..fe17a2981 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYStepAreaRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYStepAreaRenderer.java @@ -47,7 +47,6 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotOrientation; @@ -171,8 +170,7 @@ public boolean isOutline() { /** * Sets a flag that controls whether or not outlines of the areas are - * drawn, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * drawn, and calls {@link #fireChangeEvent()}. * * @param show the flag. * @@ -196,8 +194,7 @@ public boolean getShapesVisible() { /** * Sets the flag that controls whether or not shapes are displayed for each - * data item, and sends a {@link RendererChangeEvent} to all registered - * listeners. + * data item, and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -220,8 +217,7 @@ public boolean isShapesFilled() { } /** - * Sets the 'shapes filled' for ALL series and sends a - * {@link RendererChangeEvent} to all registered listeners. + * Sets the 'shapes filled' for ALL series and calls {@link #fireChangeEvent()}. * * @param filled the flag. * @@ -245,8 +241,7 @@ public boolean getPlotArea() { /** * Sets a flag that controls whether or not areas are drawn for each data - * item and sends a {@link RendererChangeEvent} to all registered - * listeners. + * item and calls {@link #fireChangeEvent()}. * * @param flag the flag. * @@ -272,8 +267,8 @@ public double getRangeBase() { /** * Sets the value on the range axis which defines the default border of the - * area, and sends a {@link RendererChangeEvent} to all registered - * listeners. E.g. setRangeBase(Double.NEGATIVE_INFINITY) lets areas always + * area, and calls {@link #fireChangeEvent()}. + * E.g. setRangeBase(Double.NEGATIVE_INFINITY) lets areas always * reach the lower border of the plotArea. * * @param val the value on the range axis which defines the default border @@ -302,8 +297,7 @@ public double getStepPoint() { } /** - * Sets the step point and sends a {@link RendererChangeEvent} to all - * registered listeners. + * Sets the step point and calls {@link #fireChangeEvent()}. * * @param stepPoint the step point (in the range 0.0 to 1.0) * diff --git a/src/main/java/org/jfree/chart/renderer/xy/XYStepRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/XYStepRenderer.java index 954770052..125e93dde 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/XYStepRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/XYStepRenderer.java @@ -1,338 +1,336 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------- - * XYStepRenderer.java - * ------------------- - * (C) Copyright 2002-present, by Roger Studner and Contributors. - * - * Original Author: Roger Studner; - * Contributor(s): David Gilbert; - * Matthias Rose; - * Gerald Struck (fix for bug 1569094); - * Ulrich Voigt (patch 1874890); - * Martin Hoeller (contribution to patch 1874890); - * Matthias Noebl (for Cropster GmbH); - * - */ - -package org.jfree.chart.renderer.xy; - -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Stroke; -import java.awt.geom.Line2D; -import java.awt.geom.Rectangle2D; -import java.io.Serializable; - -import org.jfree.chart.HashUtils; -import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; -import org.jfree.chart.labels.XYToolTipGenerator; -import org.jfree.chart.plot.CrosshairState; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.PlotRenderingInfo; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.ui.RectangleEdge; -import org.jfree.chart.urls.XYURLGenerator; -import org.jfree.chart.util.LineUtils; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.data.xy.XYDataset; - -/** - * Line/Step item renderer for an {@link XYPlot}. This class draws lines - * between data points, only allowing horizontal or vertical lines (steps). - * The example shown here is generated by the - * {@code XYStepRendererDemo1.java} program included in the JFreeChart - * demo collection: - *

    - * XYStepRendererSample.png - */ -public class XYStepRenderer extends XYLineAndShapeRenderer - implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = -8918141928884796108L; - - /** - * The factor (from 0.0 to 1.0) that determines the position of the - * step. - */ - private double stepPoint = 1.0d; - - /** - * Constructs a new renderer with no tooltip or URL generation. - */ - public XYStepRenderer() { - this(null, null); - } - - /** - * Constructs a new renderer with the specified tool tip and URL - * generators. - * - * @param toolTipGenerator the item label generator ({@code null} - * permitted). - * @param urlGenerator the URL generator ({@code null} permitted). - */ - public XYStepRenderer(XYToolTipGenerator toolTipGenerator, - XYURLGenerator urlGenerator) { - super(); - setDefaultToolTipGenerator(toolTipGenerator); - setURLGenerator(urlGenerator); - setDefaultShapesVisible(false); - } - - /** - * Returns the fraction of the domain position between two points on which - * the step is drawn. The default is 1.0d, which means the step is drawn - * at the domain position of the second`point. If the stepPoint is 0.5d the - * step is drawn at half between the two points. - * - * @return The fraction of the domain position between two points where the - * step is drawn. - * - * @see #setStepPoint(double) - */ - public double getStepPoint() { - return this.stepPoint; - } - - /** - * Sets the step point and sends a {@link RendererChangeEvent} to all - * registered listeners. - * - * @param stepPoint the step point (in the range 0.0 to 1.0) - * - * @see #getStepPoint() - */ - public void setStepPoint(double stepPoint) { - if (stepPoint < 0.0d || stepPoint > 1.0d) { - throw new IllegalArgumentException( - "Requires stepPoint in [0.0;1.0]"); - } - this.stepPoint = stepPoint; - fireChangeEvent(); - } - - /** - * Draws the visual representation of a single data item. - * - * @param g2 the graphics device. - * @param state the renderer state. - * @param dataArea the area within which the data is being drawn. - * @param info collects information about the drawing. - * @param plot the plot (can be used to obtain standard color - * information etc). - * @param domainAxis the domain axis. - * @param rangeAxis the vertical axis. - * @param dataset the dataset. - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * @param crosshairState crosshair information for the plot - * ({@code null} permitted). - * @param pass the pass index. - */ - @Override - public void drawItem(Graphics2D g2, XYItemRendererState state, - Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, - ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, - int series, int item, CrosshairState crosshairState, int pass) { - - // do nothing if item is not visible - if (!getItemVisible(series, item)) { - return; - } - - PlotOrientation orientation = plot.getOrientation(); - - Paint seriesPaint = getItemPaint(series, item); - Stroke seriesStroke = getItemStroke(series, item); - g2.setPaint(seriesPaint); - g2.setStroke(seriesStroke); - - // get the data point... - double x1 = dataset.getXValue(series, item); - double y1 = dataset.getYValue(series, item); - - RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); - RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); - double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); - double transY1 = (Double.isNaN(y1) ? Double.NaN - : rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation)); - - if (pass == 0 && item > 0) { - // get the previous data point... - double x0 = dataset.getXValue(series, item - 1); - double y0 = dataset.getYValue(series, item - 1); - double transX0 = domainAxis.valueToJava2D(x0, dataArea, - xAxisLocation); - double transY0 = (Double.isNaN(y0) ? Double.NaN - : rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation)); - - if (orientation == PlotOrientation.HORIZONTAL) { - if (transY0 == transY1) { - // this represents the situation - // for drawing a horizontal bar. - drawLine(g2, state.workingLine, transY0, transX0, transY1, - transX1, dataArea); - } - else { //this handles the need to perform a 'step'. - - // calculate the step point - double transXs = transX0 + (getStepPoint() - * (transX1 - transX0)); - drawLine(g2, state.workingLine, transY0, transX0, transY0, - transXs, dataArea); - drawLine(g2, state.workingLine, transY0, transXs, transY1, - transXs, dataArea); - drawLine(g2, state.workingLine, transY1, transXs, transY1, - transX1, dataArea); - } - } - else if (orientation == PlotOrientation.VERTICAL) { - if (transY0 == transY1) { // this represents the situation - // for drawing a horizontal bar. - drawLine(g2, state.workingLine, transX0, transY0, transX1, - transY1, dataArea); - } - else { //this handles the need to perform a 'step'. - // calculate the step point - double transXs = transX0 + (getStepPoint() - * (transX1 - transX0)); - drawLine(g2, state.workingLine, transX0, transY0, transXs, - transY0, dataArea); - drawLine(g2, state.workingLine, transXs, transY0, transXs, - transY1, dataArea); - drawLine(g2, state.workingLine, transXs, transY1, transX1, - transY1, dataArea); - } - } - - // submit this data item as a candidate for the crosshair point - int datasetIndex = plot.indexOf(dataset); - updateCrosshairValues(crosshairState, x1, y1, datasetIndex, - transX1, transY1, orientation); - - // collect entity and tool tip information... - EntityCollection entities = state.getEntityCollection(); - if (entities != null) { - if (orientation == PlotOrientation.HORIZONTAL) { - addEntity(entities, null, dataset, series, item, transY1, - transX1); - } else { - addEntity(entities, null, dataset, series, item, transX1, - transY1); - } - } - - } - - if (pass == 1) { - // draw the item label if there is one... - if (isItemLabelVisible(series, item)) { - double xx = transX1; - double yy = transY1; - if (orientation == PlotOrientation.HORIZONTAL) { - xx = transY1; - yy = transX1; - } - drawItemLabel(g2, orientation, dataset, series, item, xx, yy, - (y1 < 0.0)); - } - } - } - - /** - * A utility method that draws a line but only if none of the coordinates - * are NaN values. - * - * @param g2 the graphics target. - * @param line the line object. - * @param x0 the x-coordinate for the starting point of the line. - * @param y0 the y-coordinate for the starting point of the line. - * @param x1 the x-coordinate for the ending point of the line. - * @param y1 the y-coordinate for the ending point of the line. - */ - private void drawLine(Graphics2D g2, Line2D line, double x0, double y0, - double x1, double y1, Rectangle2D dataArea) { - if (Double.isNaN(x0) || Double.isNaN(x1) || Double.isNaN(y0) - || Double.isNaN(y1)) { - return; - } - line.setLine(x0, y0, x1, y1); - boolean visible = LineUtils.clipLine(line, dataArea); - if (visible) { - g2.draw(line); - } - } - - /** - * Tests this renderer for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof XYLineAndShapeRenderer)) { - return false; - } - XYStepRenderer that = (XYStepRenderer) obj; - if (this.stepPoint != that.stepPoint) { - return false; - } - return super.equals(obj); - } - - /** - * Returns a hash code for this instance. - * - * @return A hash code. - */ - @Override - public int hashCode() { - return HashUtils.hashCode(super.hashCode(), this.stepPoint); - } - - /** - * Returns a clone of the renderer. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the renderer cannot be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------- + * XYStepRenderer.java + * ------------------- + * (C) Copyright 2002-present, by Roger Studner and Contributors. + * + * Original Author: Roger Studner; + * Contributor(s): David Gilbert; + * Matthias Rose; + * Gerald Struck (fix for bug 1569094); + * Ulrich Voigt (patch 1874890); + * Martin Hoeller (contribution to patch 1874890); + * Matthias Noebl (for Cropster GmbH); + * + */ + +package org.jfree.chart.renderer.xy; + +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Stroke; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.io.Serializable; + +import org.jfree.chart.HashUtils; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.entity.EntityCollection; +import org.jfree.chart.labels.XYToolTipGenerator; +import org.jfree.chart.plot.CrosshairState; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.urls.XYURLGenerator; +import org.jfree.chart.util.LineUtils; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.data.xy.XYDataset; + +/** + * Line/Step item renderer for an {@link XYPlot}. This class draws lines + * between data points, only allowing horizontal or vertical lines (steps). + * The example shown here is generated by the + * {@code XYStepRendererDemo1.java} program included in the JFreeChart + * demo collection: + *

    + * XYStepRendererSample.png + */ +public class XYStepRenderer extends XYLineAndShapeRenderer + implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = -8918141928884796108L; + + /** + * The factor (from 0.0 to 1.0) that determines the position of the + * step. + */ + private double stepPoint = 1.0d; + + /** + * Constructs a new renderer with no tooltip or URL generation. + */ + public XYStepRenderer() { + this(null, null); + } + + /** + * Constructs a new renderer with the specified tool tip and URL + * generators. + * + * @param toolTipGenerator the item label generator ({@code null} + * permitted). + * @param urlGenerator the URL generator ({@code null} permitted). + */ + public XYStepRenderer(XYToolTipGenerator toolTipGenerator, + XYURLGenerator urlGenerator) { + super(); + setDefaultToolTipGenerator(toolTipGenerator); + setURLGenerator(urlGenerator); + setDefaultShapesVisible(false); + } + + /** + * Returns the fraction of the domain position between two points on which + * the step is drawn. The default is 1.0d, which means the step is drawn + * at the domain position of the second`point. If the stepPoint is 0.5d the + * step is drawn at half between the two points. + * + * @return The fraction of the domain position between two points where the + * step is drawn. + * + * @see #setStepPoint(double) + */ + public double getStepPoint() { + return this.stepPoint; + } + + /** + * Sets the step point and calls {@link #fireChangeEvent()}. + * + * @param stepPoint the step point (in the range 0.0 to 1.0) + * + * @see #getStepPoint() + */ + public void setStepPoint(double stepPoint) { + if (stepPoint < 0.0d || stepPoint > 1.0d) { + throw new IllegalArgumentException( + "Requires stepPoint in [0.0;1.0]"); + } + this.stepPoint = stepPoint; + fireChangeEvent(); + } + + /** + * Draws the visual representation of a single data item. + * + * @param g2 the graphics device. + * @param state the renderer state. + * @param dataArea the area within which the data is being drawn. + * @param info collects information about the drawing. + * @param plot the plot (can be used to obtain standard color + * information etc). + * @param domainAxis the domain axis. + * @param rangeAxis the vertical axis. + * @param dataset the dataset. + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * @param crosshairState crosshair information for the plot + * ({@code null} permitted). + * @param pass the pass index. + */ + @Override + public void drawItem(Graphics2D g2, XYItemRendererState state, + Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, + ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, + int series, int item, CrosshairState crosshairState, int pass) { + + // do nothing if item is not visible + if (!getItemVisible(series, item)) { + return; + } + + PlotOrientation orientation = plot.getOrientation(); + + Paint seriesPaint = getItemPaint(series, item); + Stroke seriesStroke = getItemStroke(series, item); + g2.setPaint(seriesPaint); + g2.setStroke(seriesStroke); + + // get the data point... + double x1 = dataset.getXValue(series, item); + double y1 = dataset.getYValue(series, item); + + RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); + RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); + double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); + double transY1 = (Double.isNaN(y1) ? Double.NaN + : rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation)); + + if (pass == 0 && item > 0) { + // get the previous data point... + double x0 = dataset.getXValue(series, item - 1); + double y0 = dataset.getYValue(series, item - 1); + double transX0 = domainAxis.valueToJava2D(x0, dataArea, + xAxisLocation); + double transY0 = (Double.isNaN(y0) ? Double.NaN + : rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation)); + + if (orientation == PlotOrientation.HORIZONTAL) { + if (transY0 == transY1) { + // this represents the situation + // for drawing a horizontal bar. + drawLine(g2, state.workingLine, transY0, transX0, transY1, + transX1, dataArea); + } + else { //this handles the need to perform a 'step'. + + // calculate the step point + double transXs = transX0 + (getStepPoint() + * (transX1 - transX0)); + drawLine(g2, state.workingLine, transY0, transX0, transY0, + transXs, dataArea); + drawLine(g2, state.workingLine, transY0, transXs, transY1, + transXs, dataArea); + drawLine(g2, state.workingLine, transY1, transXs, transY1, + transX1, dataArea); + } + } + else if (orientation == PlotOrientation.VERTICAL) { + if (transY0 == transY1) { // this represents the situation + // for drawing a horizontal bar. + drawLine(g2, state.workingLine, transX0, transY0, transX1, + transY1, dataArea); + } + else { //this handles the need to perform a 'step'. + // calculate the step point + double transXs = transX0 + (getStepPoint() + * (transX1 - transX0)); + drawLine(g2, state.workingLine, transX0, transY0, transXs, + transY0, dataArea); + drawLine(g2, state.workingLine, transXs, transY0, transXs, + transY1, dataArea); + drawLine(g2, state.workingLine, transXs, transY1, transX1, + transY1, dataArea); + } + } + + // submit this data item as a candidate for the crosshair point + int datasetIndex = plot.indexOf(dataset); + updateCrosshairValues(crosshairState, x1, y1, datasetIndex, + transX1, transY1, orientation); + + // collect entity and tool tip information... + EntityCollection entities = state.getEntityCollection(); + if (entities != null) { + if (orientation == PlotOrientation.HORIZONTAL) { + addEntity(entities, null, dataset, series, item, transY1, + transX1); + } else { + addEntity(entities, null, dataset, series, item, transX1, + transY1); + } + } + + } + + if (pass == 1) { + // draw the item label if there is one... + if (isItemLabelVisible(series, item)) { + double xx = transX1; + double yy = transY1; + if (orientation == PlotOrientation.HORIZONTAL) { + xx = transY1; + yy = transX1; + } + drawItemLabel(g2, orientation, dataset, series, item, xx, yy, + (y1 < 0.0)); + } + } + } + + /** + * A utility method that draws a line but only if none of the coordinates + * are NaN values. + * + * @param g2 the graphics target. + * @param line the line object. + * @param x0 the x-coordinate for the starting point of the line. + * @param y0 the y-coordinate for the starting point of the line. + * @param x1 the x-coordinate for the ending point of the line. + * @param y1 the y-coordinate for the ending point of the line. + */ + private void drawLine(Graphics2D g2, Line2D line, double x0, double y0, + double x1, double y1, Rectangle2D dataArea) { + if (Double.isNaN(x0) || Double.isNaN(x1) || Double.isNaN(y0) + || Double.isNaN(y1)) { + return; + } + line.setLine(x0, y0, x1, y1); + boolean visible = LineUtils.clipLine(line, dataArea); + if (visible) { + g2.draw(line); + } + } + + /** + * Tests this renderer for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof XYLineAndShapeRenderer)) { + return false; + } + XYStepRenderer that = (XYStepRenderer) obj; + if (this.stepPoint != that.stepPoint) { + return false; + } + return super.equals(obj); + } + + /** + * Returns a hash code for this instance. + * + * @return A hash code. + */ + @Override + public int hashCode() { + return HashUtils.hashCode(super.hashCode(), this.stepPoint); + } + + /** + * Returns a clone of the renderer. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the renderer cannot be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} diff --git a/src/main/java/org/jfree/chart/renderer/xy/YIntervalRenderer.java b/src/main/java/org/jfree/chart/renderer/xy/YIntervalRenderer.java index 33cb7f482..8d4fcc3ee 100644 --- a/src/main/java/org/jfree/chart/renderer/xy/YIntervalRenderer.java +++ b/src/main/java/org/jfree/chart/renderer/xy/YIntervalRenderer.java @@ -49,7 +49,6 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.entity.EntityCollection; -import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.labels.ItemLabelPosition; import org.jfree.chart.labels.XYItemLabelGenerator; import org.jfree.chart.plot.CrosshairState; @@ -107,10 +106,9 @@ public XYItemLabelGenerator getAdditionalItemLabelGenerator() { } /** - * Sets the generator for the item labels that appear near the lower - * y-value and sends a {@link RendererChangeEvent} to all registered - * listeners. If this is set to {@code null}, no item labels will be - * drawn. + * Sets the generator for the item labels that appear near the lower y-value + * and calls {@link #fireChangeEvent()}. If this is set to {@code null}, no + * item labels will be drawn. * * @param generator the generator ({@code null} permitted). * diff --git a/src/main/java/org/jfree/chart/title/LegendItemBlockContainer.java b/src/main/java/org/jfree/chart/title/LegendItemBlockContainer.java index cd0c7e5ac..f9d7ffc54 100644 --- a/src/main/java/org/jfree/chart/title/LegendItemBlockContainer.java +++ b/src/main/java/org/jfree/chart/title/LegendItemBlockContainer.java @@ -44,7 +44,6 @@ import org.jfree.chart.block.BlockContainer; import org.jfree.chart.block.BlockResult; import org.jfree.chart.block.EntityBlockParams; -import org.jfree.chart.block.EntityBlockResult; import org.jfree.chart.entity.EntityCollection; import org.jfree.chart.entity.LegendItemEntity; import org.jfree.chart.entity.StandardEntityCollection; @@ -162,7 +161,7 @@ public void setURLText(String text) { * @param params passed on to blocks within the container * ({@code null} permitted). * - * @return An instance of {@link EntityBlockResult}, or {@code null}. + * @return An instance of {@link BlockResult}. */ @Override public Object draw(Graphics2D g2, Rectangle2D area, Object params) { diff --git a/src/main/java/org/jfree/chart/title/TextTitle.java b/src/main/java/org/jfree/chart/title/TextTitle.java index bbf5b2095..959db5db6 100644 --- a/src/main/java/org/jfree/chart/title/TextTitle.java +++ b/src/main/java/org/jfree/chart/title/TextTitle.java @@ -56,7 +56,6 @@ import org.jfree.chart.block.LengthConstraintType; import org.jfree.chart.block.RectangleConstraint; import org.jfree.chart.entity.ChartEntity; -import org.jfree.chart.entity.EntityCollection; import org.jfree.chart.entity.StandardEntityCollection; import org.jfree.chart.entity.TitleEntity; import org.jfree.chart.event.TitleChangeEvent; @@ -614,17 +613,20 @@ public void draw(Graphics2D g2, Rectangle2D area) { } /** - * Draws the block within the specified area. - * - * @param g2 the graphics device. - * @param area the area. - * @param params if this is an instance of {@link EntityBlockParams} it - * is used to determine whether or not an - * {@link EntityCollection} is returned by this method. - * - * @return An {@link EntityCollection} containing a chart entity for the - * title, or {@code null}. - */ + * Draws the block within the specified area. + * + * @param g2 the graphics device. + * @param area the area. + * @param params if this is an instance of + * {@link org.jfree.chart.block.EntityBlockParams} it is used to + * determine whether or not an + * {@link org.jfree.chart.entity.EntityCollection} is + * returned by this method. + * + * @return An {@link org.jfree.chart.entity.EntityCollection} containing a + * chart entity for the title, + * or {@code null}. + */ @Override public Object draw(Graphics2D g2, Rectangle2D area, Object params) { if (this.content == null) { diff --git a/src/main/java/org/jfree/chart/urls/CustomPieURLGenerator.java b/src/main/java/org/jfree/chart/urls/CustomPieURLGenerator.java index 825951fe3..4a197c620 100644 --- a/src/main/java/org/jfree/chart/urls/CustomPieURLGenerator.java +++ b/src/main/java/org/jfree/chart/urls/CustomPieURLGenerator.java @@ -43,7 +43,6 @@ import java.util.Map; import java.util.Set; -import org.jfree.chart.plot.MultiplePiePlot; import org.jfree.chart.util.PublicCloneable; import org.jfree.data.general.PieDataset; @@ -141,7 +140,7 @@ public String getURL(Comparable key, int mapIndex) { * {@code URL} is a {@code String} representing a URL fragment. *

    * The map is appended to an internal list...you can add multiple maps - * if you are working with, say, a {@link MultiplePiePlot}. + * if you are working with, say, a {@link org.jfree.chart.plot.MultiplePiePlot}. * * @param urlMap the URLs ({@code null} permitted). */ diff --git a/src/main/java/org/jfree/data/ComparableObjectSeries.java b/src/main/java/org/jfree/data/ComparableObjectSeries.java index a6c066567..7bc3067ce 100644 --- a/src/main/java/org/jfree/data/ComparableObjectSeries.java +++ b/src/main/java/org/jfree/data/ComparableObjectSeries.java @@ -44,7 +44,6 @@ import org.jfree.chart.util.CloneUtils; import org.jfree.data.general.Series; -import org.jfree.data.general.SeriesChangeEvent; import org.jfree.data.general.SeriesException; /** @@ -145,8 +144,7 @@ public int getMaximumItemCount() { *

    * Typically this value is set before the series is populated with data, * but if it is applied later, it may cause some items to be removed from - * the series (in which case a {@link SeriesChangeEvent} will be sent to - * all registered listeners. + * the series (in which case {@link #fireSeriesChanged()} will be called. * * @param maximum the maximum number of items for the series. */ @@ -163,33 +161,32 @@ public void setMaximumItemCount(int maximum) { } /** - * Adds new data to the series and sends a {@link SeriesChangeEvent} to - * all registered listeners. - *

    - * Throws an exception if the x-value is a duplicate AND the - * allowDuplicateXValues flag is false. - * - * @param x the x-value ({@code null} not permitted). - * @param y the y-value ({@code null} permitted). - */ + * Adds new data to the series by calling + * {@link #add(Comparable, Object, boolean)}. + *

    + * Throws an exception if the x-value is a duplicate AND the + * allowDuplicateXValues flag is false. + * + * @param x the x-value ({@code null} not permitted). + * @param y the y-value ({@code null} permitted). + */ protected void add(Comparable x, Object y) { // argument checking delegated... add(x, y, true); } /** - * Adds new data to the series and, if requested, sends a - * {@link SeriesChangeEvent} to all registered listeners. - *

    - * Throws an exception if the x-value is a duplicate AND the - * allowDuplicateXValues flag is false. - * - * @param x the x-value ({@code null} not permitted). - * @param y the y-value ({@code null} permitted). - * @param notify a flag the controls whether or not a - * {@link SeriesChangeEvent} is sent to all registered - * listeners. - */ + * Adds new data to the series by calling + * {@link #add(ComparableObjectItem, boolean)}. + *

    + * Throws an exception if the x-value is a duplicate AND the + * allowDuplicateXValues flag is false. + * + * @param x the x-value ({@code null} not permitted). + * @param y the y-value ({@code null} permitted). + * @param notify a flag that controls whether or not to notify all registered + * listeners. + */ protected void add(Comparable x, Object y, boolean notify) { // delegate argument checking to XYDataItem... ComparableObjectItem item = new ComparableObjectItem(x, y); @@ -197,14 +194,12 @@ protected void add(Comparable x, Object y, boolean notify) { } /** - * Adds a data item to the series and, if requested, sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param item the (x, y) item ({@code null} not permitted). - * @param notify a flag that controls whether or not a - * {@link SeriesChangeEvent} is sent to all registered - * listeners. - */ + * Adds a data item to the series and, if requested, calls {@link #fireSeriesChanged()}. + * + * @param item the (x, y) item ({@code null} not permitted). + * @param notify a flag that controls whether or not + * {@link #fireSeriesChanged()} is called. + */ protected void add(ComparableObjectItem item, boolean notify) { Args.nullNotPermitted(item, "item"); @@ -301,8 +296,8 @@ protected void update(Comparable x, Object y) { } /** - * Updates the value of an item in the series and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Updates the value of an item in the series and calls + * {@link #fireSeriesChanged()}. * * @param index the item (zero based index). * @param y the new value ({@code null} permitted). @@ -325,8 +320,8 @@ protected ComparableObjectItem getDataItem(int index) { } /** - * Deletes a range of items from the series and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Deletes a range of items from the series and calls + * {@link #fireSeriesChanged()}. * * @param start the start index (zero-based). * @param end the end index (zero-based). @@ -340,8 +335,7 @@ protected void delete(int start, int end) { /** * Removes all data items from the series and, unless the series is - * already empty, sends a {@link SeriesChangeEvent} to all registered - * listeners. + * already empty, calls {@link #fireSeriesChanged()}. */ public void clear() { if (this.data.size() > 0) { @@ -351,8 +345,7 @@ public void clear() { } /** - * Removes the item at the specified index and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Removes the item at the specified index and calls {@link #fireSeriesChanged()}. * * @param index the index. * @@ -366,8 +359,7 @@ protected ComparableObjectItem remove(int index) { } /** - * Removes the item with the specified x-value and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Removes the item with the specified x-value by calling {@link #remove(int)}. * * @param x the x-value. diff --git a/src/main/java/org/jfree/data/DataUtils.java b/src/main/java/org/jfree/data/DataUtils.java index 869437b71..02412a24b 100644 --- a/src/main/java/org/jfree/data/DataUtils.java +++ b/src/main/java/org/jfree/data/DataUtils.java @@ -38,11 +38,10 @@ import java.util.Arrays; import org.jfree.chart.util.Args; -import org.jfree.data.general.DatasetUtils; /** * Utility methods for use with some of the data classes (but not the datasets, - * see {@link DatasetUtils}). + * see {@link org.jfree.data.general.DatasetUtils}). */ public abstract class DataUtils { diff --git a/src/main/java/org/jfree/data/category/DefaultCategoryDataset.java b/src/main/java/org/jfree/data/category/DefaultCategoryDataset.java index f83370a8a..2bad29b63 100644 --- a/src/main/java/org/jfree/data/category/DefaultCategoryDataset.java +++ b/src/main/java/org/jfree/data/category/DefaultCategoryDataset.java @@ -41,9 +41,7 @@ import org.jfree.chart.util.PublicCloneable; import org.jfree.data.DefaultKeyedValues2D; -import org.jfree.data.UnknownKeyException; import org.jfree.data.general.AbstractDataset; -import org.jfree.data.general.DatasetChangeEvent; /** * A default implementation of the {@link CategoryDataset} interface. @@ -196,7 +194,7 @@ public List getColumnKeys() { * * @return The value (possibly {@code null}). * - * @throws UnknownKeyException if either key is not defined in the dataset. + * @throws org.jfree.data.UnknownKeyException if either key is not defined in the dataset. * * @see #addValue(Number, Comparable, Comparable) */ @@ -236,8 +234,7 @@ public void addValue(double value, Comparable rowKey, } /** - * Adds or updates a value in the table and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Adds or updates a value in the table and calls {@link #fireDatasetChanged()}. * * @param value the value ({@code null} permitted). * @param rowKey the row key ({@code null} not permitted). @@ -252,8 +249,7 @@ public void setValue(Number value, Comparable rowKey, } /** - * Adds or updates a value in the table and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Adds or updates a value in the table and calls {@link #fireDatasetChanged()}. * * @param value the value. * @param rowKey the row key ({@code null} not permitted). @@ -274,7 +270,7 @@ public void setValue(double value, Comparable rowKey, * @param rowKey the row key ({@code null} not permitted). * @param columnKey the column key ({@code null} not permitted). * - * @throws UnknownKeyException if either key is not defined in the dataset. + * @throws org.jfree.data.UnknownKeyException if either key is not defined in the dataset. */ public void incrementValue(double value, Comparable rowKey, @@ -288,8 +284,7 @@ public void incrementValue(double value, } /** - * Removes a value from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a value from the dataset and calls {@link #fireDatasetChanged()}. * * @param rowKey the row key. * @param columnKey the column key. @@ -302,8 +297,7 @@ public void removeValue(Comparable rowKey, Comparable columnKey) { } /** - * Removes a row from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a row from the dataset and calls {@link #fireDatasetChanged()}. * * @param rowIndex the row index. * @@ -315,8 +309,7 @@ public void removeRow(int rowIndex) { } /** - * Removes a row from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a row from the dataset and calls {@link #fireDatasetChanged()}. * * @param rowKey the row key. * @@ -328,8 +321,7 @@ public void removeRow(Comparable rowKey) { } /** - * Removes a column from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a column from the dataset and calls {@link #fireDatasetChanged()}. * * @param columnIndex the column index. * @@ -341,14 +333,13 @@ public void removeColumn(int columnIndex) { } /** - * Removes a column from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a column from the dataset and calls {@link #fireDatasetChanged()}. * * @param columnKey the column key ({@code null} not permitted). * * @see #removeRow(Comparable) * - * @throws UnknownKeyException if {@code columnKey} is not defined + * @throws org.jfree.data.UnknownKeyException if {@code columnKey} is not defined * in the dataset. */ public void removeColumn(Comparable columnKey) { @@ -357,8 +348,7 @@ public void removeColumn(Comparable columnKey) { } /** - * Clears all data from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Clears all data from the dataset and calls {@link #fireDatasetChanged()}. */ public void clear() { this.data.clear(); diff --git a/src/main/java/org/jfree/data/category/SlidingCategoryDataset.java b/src/main/java/org/jfree/data/category/SlidingCategoryDataset.java index a7b140188..faf83f70e 100644 --- a/src/main/java/org/jfree/data/category/SlidingCategoryDataset.java +++ b/src/main/java/org/jfree/data/category/SlidingCategoryDataset.java @@ -42,7 +42,6 @@ import org.jfree.data.UnknownKeyException; import org.jfree.data.general.AbstractDataset; -import org.jfree.data.general.DatasetChangeEvent; /** * A {@link CategoryDataset} implementation that presents a subset of the @@ -100,8 +99,7 @@ public int getFirstCategoryIndex() { /** * Sets the index of the first category that should be used from the - * underlying dataset, and sends a {@link DatasetChangeEvent} to all - * registered listeners. + * underlying dataset, and calls {@link #fireDatasetChanged()}. * * @param first the index. * @@ -127,8 +125,7 @@ public int getMaximumCategoryCount() { } /** - * Sets the maximum category count and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Sets the maximum category count and calls {@link #fireDatasetChanged()}. * * @param max the maximum. * diff --git a/src/main/java/org/jfree/data/gantt/SlidingGanttCategoryDataset.java b/src/main/java/org/jfree/data/gantt/SlidingGanttCategoryDataset.java index 05f627da2..4f0cb2ca7 100644 --- a/src/main/java/org/jfree/data/gantt/SlidingGanttCategoryDataset.java +++ b/src/main/java/org/jfree/data/gantt/SlidingGanttCategoryDataset.java @@ -43,7 +43,6 @@ import org.jfree.data.UnknownKeyException; import org.jfree.data.general.AbstractDataset; -import org.jfree.data.general.DatasetChangeEvent; /** * A {@link GanttCategoryDataset} implementation that presents a subset of the @@ -101,8 +100,7 @@ public int getFirstCategoryIndex() { /** * Sets the index of the first category that should be used from the - * underlying dataset, and sends a {@link DatasetChangeEvent} to all - * registered listeners. + * underlying dataset, and calls {@link #fireDatasetChanged()}. * * @param first the index. * @@ -128,8 +126,7 @@ public int getMaximumCategoryCount() { } /** - * Sets the maximum category count and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Sets the maximum category count and calls {@link #fireDatasetChanged()}. * * @param max the maximum. * diff --git a/src/main/java/org/jfree/data/gantt/XYTaskDataset.java b/src/main/java/org/jfree/data/gantt/XYTaskDataset.java index 5602a104a..90ea2576a 100644 --- a/src/main/java/org/jfree/data/gantt/XYTaskDataset.java +++ b/src/main/java/org/jfree/data/gantt/XYTaskDataset.java @@ -39,8 +39,6 @@ import java.util.Date; import java.util.Objects; -import org.jfree.chart.axis.SymbolAxis; -import org.jfree.chart.renderer.xy.XYBarRenderer; import org.jfree.chart.util.Args; import org.jfree.data.general.DatasetChangeEvent; import org.jfree.data.general.DatasetChangeListener; @@ -51,8 +49,8 @@ /** * A dataset implementation that wraps a {@link TaskSeriesCollection} and * presents it as an {@link IntervalXYDataset}, allowing a set of tasks to - * be displayed using an {@link XYBarRenderer} (and usually a - * {@link SymbolAxis}). This is a very specialised dataset implementation + * be displayed using an {@link org.jfree.chart.renderer.xy.XYBarRenderer} (and usually a + * {@link org.jfree.chart.axis.SymbolAxis}). This is a very specialised dataset implementation * ---before using it, you should take some time to understand the use-cases * that it is designed for. */ diff --git a/src/main/java/org/jfree/data/general/DefaultPieDataset.java b/src/main/java/org/jfree/data/general/DefaultPieDataset.java index 3f077f52a..9a7aed4bb 100644 --- a/src/main/java/org/jfree/data/general/DefaultPieDataset.java +++ b/src/main/java/org/jfree/data/general/DefaultPieDataset.java @@ -45,7 +45,6 @@ import org.jfree.data.DefaultKeyedValues; import org.jfree.data.KeyedValues; -import org.jfree.data.UnknownKeyException; /** * A default implementation of the {@link PieDataset} interface. @@ -153,7 +152,7 @@ public Number getValue(int item) { * * @return The value (possibly {@code null}). * - * @throws UnknownKeyException if the key is not recognised. + * @throws org.jfree.data.UnknownKeyException if the key is not recognised. */ @Override public Number getValue(K key) { diff --git a/src/main/java/org/jfree/data/general/SeriesDataset.java b/src/main/java/org/jfree/data/general/SeriesDataset.java index fcb6da8b3..579c024ce 100644 --- a/src/main/java/org/jfree/data/general/SeriesDataset.java +++ b/src/main/java/org/jfree/data/general/SeriesDataset.java @@ -36,20 +36,14 @@ package org.jfree.data.general; -import org.jfree.data.category.CategoryDataset; -import org.jfree.data.xy.IntervalXYDataset; -import org.jfree.data.xy.IntervalXYZDataset; -import org.jfree.data.xy.XYDataset; -import org.jfree.data.xy.XYZDataset; - /** * The interface for a dataset consisting of one or many series of data. * - * @see CategoryDataset - * @see IntervalXYDataset - * @see IntervalXYZDataset - * @see XYDataset - * @see XYZDataset + * @see org.jfree.data.category.CategoryDataset + * @see org.jfree.data.xy.IntervalXYDataset + * @see org.jfree.data.xy.IntervalXYZDataset + * @see org.jfree.data.xy.XYDataset + * @see org.jfree.data.xy.XYZDataset */ public interface SeriesDataset extends Dataset { diff --git a/src/main/java/org/jfree/data/jdbc/JDBCCategoryDataset.java b/src/main/java/org/jfree/data/jdbc/JDBCCategoryDataset.java index f51c5d046..6e7992caf 100644 --- a/src/main/java/org/jfree/data/jdbc/JDBCCategoryDataset.java +++ b/src/main/java/org/jfree/data/jdbc/JDBCCategoryDataset.java @@ -46,11 +46,11 @@ import java.sql.Statement; import java.sql.Types; -import org.jfree.data.category.CategoryDataset; import org.jfree.data.category.DefaultCategoryDataset; /** - * A {@link CategoryDataset} implementation over a database JDBC result set. + * An extension of {@link DefaultCategoryDataset} for a database JDBC result set. + * * The dataset is populated via a call to {@link #executeQuery(String)} with * the string SQL query. The SQL query must return at least two columns. The * first column will be the category name and remaining columns values (each diff --git a/src/main/java/org/jfree/data/jdbc/JDBCPieDataset.java b/src/main/java/org/jfree/data/jdbc/JDBCPieDataset.java index cde56522a..2d2e7c846 100644 --- a/src/main/java/org/jfree/data/jdbc/JDBCPieDataset.java +++ b/src/main/java/org/jfree/data/jdbc/JDBCPieDataset.java @@ -47,14 +47,14 @@ import java.sql.Types; import org.jfree.data.general.DefaultPieDataset; -import org.jfree.data.general.PieDataset; /** - * A {@link PieDataset} that reads data from a database via JDBC. + * A {@link org.jfree.data.general.PieDataset} that reads data from a database + * via JDBC. *

    * A query should be supplied that returns data in two columns, the first - * containing VARCHAR data, and the second containing numerical data. The - * data is cached in-memory and can be refreshed at any time. + * containing VARCHAR data, and the second containing numerical data. The data + * is cached in-memory and can be refreshed at any time. */ public class JDBCPieDataset extends DefaultPieDataset { diff --git a/src/main/java/org/jfree/data/jdbc/JDBCXYDataset.java b/src/main/java/org/jfree/data/jdbc/JDBCXYDataset.java index d3a36b592..1ec91ecc4 100644 --- a/src/main/java/org/jfree/data/jdbc/JDBCXYDataset.java +++ b/src/main/java/org/jfree/data/jdbc/JDBCXYDataset.java @@ -49,7 +49,6 @@ import org.jfree.data.Range; import org.jfree.data.RangeInfo; -import org.jfree.data.general.Dataset; import org.jfree.data.xy.AbstractXYDataset; import org.jfree.data.xy.TableXYDataset; import org.jfree.data.xy.XYDataset; @@ -438,9 +437,6 @@ public int getItemCount() { * Returns the number of series in the dataset. * * @return The seriesCount value - * - * @see XYDataset - * @see Dataset */ @Override public int getSeriesCount() { @@ -453,9 +449,6 @@ public int getSeriesCount() { * @param seriesIndex the series (zero-based index). * * @return The seriesName value - * - * @see XYDataset - * @see Dataset */ @Override public Comparable getSeriesKey(int seriesIndex) { diff --git a/src/main/java/org/jfree/data/json/JSONUtils.java b/src/main/java/org/jfree/data/json/JSONUtils.java index 596830bbf..f5b621d60 100644 --- a/src/main/java/org/jfree/data/json/JSONUtils.java +++ b/src/main/java/org/jfree/data/json/JSONUtils.java @@ -1,195 +1,193 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------- - * JSONUtils.java - * -------------- - * (C) Copyright 2014-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.data.json; - -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.util.Iterator; -import java.util.List; -import org.jfree.chart.util.Args; -import org.jfree.data.KeyedValues; -import org.jfree.data.KeyedValues2D; -import org.jfree.data.category.CategoryDataset; -import org.jfree.data.general.PieDataset; -import org.jfree.data.json.impl.JSONValue; - -/** - * A utility class that can read and write data in specific JSON formats. - */ -public class JSONUtils { - - /** - * Returns a string containing the data in JSON format. The format is - * an array of arrays, where each sub-array represents one data value. - * The sub-array should contain two items, first the item key as a string - * and second the item value as a number. For example: - * {@code [["Key A", 1.0], ["Key B", 2.0]]} - *

    - * Note that this method can be used with instances of {@link PieDataset}. - * - * @param data the data ({@code null} not permitted). - * - * @return A string in JSON format. - */ - public static String writeKeyedValues(KeyedValues data) { - Args.nullNotPermitted(data, "data"); - StringWriter sw = new StringWriter(); - try { - writeKeyedValues(data, sw); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - return sw.toString(); - } - - /** - * Writes the data in JSON format to the supplied writer. - *

    - * Note that this method can be used with instances of {@link PieDataset}. - * - * @param data the data ({@code null} not permitted). - * @param writer the writer ({@code null} not permitted). - * - * @throws IOException if there is an I/O problem. - */ - public static void writeKeyedValues(KeyedValues data, Writer writer) - throws IOException { - Args.nullNotPermitted(data, "data"); - Args.nullNotPermitted(writer, "writer"); - writer.write("["); - boolean first = true; - Iterator iterator = data.getKeys().iterator(); - while (iterator.hasNext()) { - Comparable key = (Comparable) iterator.next(); - if (!first) { - writer.write(", "); - } else { - first = false; - } - writer.write("["); - writer.write(JSONValue.toJSONString(key.toString())); - writer.write(", "); - writer.write(JSONValue.toJSONString(data.getValue(key))); - writer.write("]"); - } - writer.write("]"); - } - - /** - * Returns a string containing the data in JSON format. The format is... - *

    - * Note that this method can be used with instances of - * {@link CategoryDataset}. - * - * @param data the data ({@code null} not permitted). - * - * @return A string in JSON format. - */ - public static String writeKeyedValues2D(KeyedValues2D data) { - Args.nullNotPermitted(data, "data"); - StringWriter sw = new StringWriter(); - try { - writeKeyedValues2D(data, sw); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - return sw.toString(); - } - - /** - * Writes the data in JSON format to the supplied writer. - *

    - * Note that this method can be used with instances of - * {@link CategoryDataset}. - * - * @param data the data ({@code null} not permitted). - * @param writer the writer ({@code null} not permitted). - * - * @throws IOException if there is an I/O problem. - */ - public static void writeKeyedValues2D(KeyedValues2D data, Writer writer) - throws IOException { - Args.nullNotPermitted(data, "data"); - Args.nullNotPermitted(writer, "writer"); - List> columnKeys = data.getColumnKeys(); - List> rowKeys = data.getRowKeys(); - writer.write("{"); - if (!columnKeys.isEmpty()) { - writer.write("\"columnKeys\": ["); - boolean first = true; - for (Comparable columnKey : columnKeys) { - if (!first) { - writer.write(", "); - } else { - first = false; - } - writer.write(JSONValue.toJSONString(columnKey.toString())); - } - writer.write("]"); - } - if (!rowKeys.isEmpty()) { - writer.write(", \"rows\": ["); - boolean firstRow = true; - for (Comparable rowKey : rowKeys) { - if (!firstRow) { - writer.write(", ["); - } else { - writer.write("["); - firstRow = false; - } - // write the row data - writer.write(JSONValue.toJSONString(rowKey.toString())); - writer.write(", ["); - boolean first = true; - for (Comparable columnKey : columnKeys) { - if (!first) { - writer.write(", "); - } else { - first = false; - } - writer.write(JSONValue.toJSONString(data.getValue(rowKey, - columnKey))); - } - writer.write("]]"); - } - writer.write("]"); - } - writer.write("}"); - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * -------------- + * JSONUtils.java + * -------------- + * (C) Copyright 2014-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.data.json; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Iterator; +import java.util.List; +import org.jfree.chart.util.Args; +import org.jfree.data.KeyedValues; +import org.jfree.data.KeyedValues2D; +import org.jfree.data.json.impl.JSONValue; + +/** + * A utility class that can read and write data in specific JSON formats. + */ +public class JSONUtils { + + /** + * Returns a string containing the data in JSON format. The format is + * an array of arrays, where each sub-array represents one data value. + * The sub-array should contain two items, first the item key as a string + * and second the item value as a number. For example: + * {@code [["Key A", 1.0], ["Key B", 2.0]]} + *

    + * Note that this method can be used with instances of {@link org.jfree.data.general.PieDataset}. + * + * @param data the data ({@code null} not permitted). + * + * @return A string in JSON format. + */ + public static String writeKeyedValues(KeyedValues data) { + Args.nullNotPermitted(data, "data"); + StringWriter sw = new StringWriter(); + try { + writeKeyedValues(data, sw); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + return sw.toString(); + } + + /** + * Writes the data in JSON format to the supplied writer. + *

    + * Note that this method can be used with instances of {@link org.jfree.data.general.PieDataset}. + * + * @param data the data ({@code null} not permitted). + * @param writer the writer ({@code null} not permitted). + * + * @throws IOException if there is an I/O problem. + */ + public static void writeKeyedValues(KeyedValues data, Writer writer) + throws IOException { + Args.nullNotPermitted(data, "data"); + Args.nullNotPermitted(writer, "writer"); + writer.write("["); + boolean first = true; + Iterator iterator = data.getKeys().iterator(); + while (iterator.hasNext()) { + Comparable key = (Comparable) iterator.next(); + if (!first) { + writer.write(", "); + } else { + first = false; + } + writer.write("["); + writer.write(JSONValue.toJSONString(key.toString())); + writer.write(", "); + writer.write(JSONValue.toJSONString(data.getValue(key))); + writer.write("]"); + } + writer.write("]"); + } + + /** + * Returns a string containing the data in JSON format. The format is... + *

    + * Note that this method can be used with instances of + * {@link org.jfree.data.category.CategoryDataset}. + * + * @param data the data ({@code null} not permitted). + * + * @return A string in JSON format. + */ + public static String writeKeyedValues2D(KeyedValues2D data) { + Args.nullNotPermitted(data, "data"); + StringWriter sw = new StringWriter(); + try { + writeKeyedValues2D(data, sw); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + return sw.toString(); + } + + /** + * Writes the data in JSON format to the supplied writer. + *

    + * Note that this method can be used with instances of + * {@link org.jfree.data.category.CategoryDataset}. + * + * @param data the data ({@code null} not permitted). + * @param writer the writer ({@code null} not permitted). + * + * @throws IOException if there is an I/O problem. + */ + public static void writeKeyedValues2D(KeyedValues2D data, Writer writer) + throws IOException { + Args.nullNotPermitted(data, "data"); + Args.nullNotPermitted(writer, "writer"); + List> columnKeys = data.getColumnKeys(); + List> rowKeys = data.getRowKeys(); + writer.write("{"); + if (!columnKeys.isEmpty()) { + writer.write("\"columnKeys\": ["); + boolean first = true; + for (Comparable columnKey : columnKeys) { + if (!first) { + writer.write(", "); + } else { + first = false; + } + writer.write(JSONValue.toJSONString(columnKey.toString())); + } + writer.write("]"); + } + if (!rowKeys.isEmpty()) { + writer.write(", \"rows\": ["); + boolean firstRow = true; + for (Comparable rowKey : rowKeys) { + if (!firstRow) { + writer.write(", ["); + } else { + writer.write("["); + firstRow = false; + } + // write the row data + writer.write(JSONValue.toJSONString(rowKey.toString())); + writer.write(", ["); + boolean first = true; + for (Comparable columnKey : columnKeys) { + if (!first) { + writer.write(", "); + } else { + first = false; + } + writer.write(JSONValue.toJSONString(data.getValue(rowKey, + columnKey))); + } + writer.write("]]"); + } + writer.write("]"); + } + writer.write("}"); + } + +} diff --git a/src/main/java/org/jfree/data/statistics/DefaultBoxAndWhiskerCategoryDataset.java b/src/main/java/org/jfree/data/statistics/DefaultBoxAndWhiskerCategoryDataset.java index 7ce65a156..573d33c3b 100644 --- a/src/main/java/org/jfree/data/statistics/DefaultBoxAndWhiskerCategoryDataset.java +++ b/src/main/java/org/jfree/data/statistics/DefaultBoxAndWhiskerCategoryDataset.java @@ -45,7 +45,6 @@ import org.jfree.data.Range; import org.jfree.data.RangeInfo; import org.jfree.data.general.AbstractDataset; -import org.jfree.data.general.DatasetChangeEvent; /** * A convenience class that provides a default implementation of the @@ -171,8 +170,7 @@ else if (minval < this.minimumRangeValue) { } /** - * Removes an item from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes an item from the dataset and calls {@link #fireDatasetChanged()}. * * @param rowKey the row key ({@code null} not permitted). * @param columnKey the column key ({@code null} not permitted). @@ -197,8 +195,7 @@ public void remove(Comparable rowKey, Comparable columnKey) { } /** - * Removes a row from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a row from the dataset and calls {@link #fireDatasetChanged()}. * * @param rowIndex the row index. * @@ -211,8 +208,7 @@ public void removeRow(int rowIndex) { } /** - * Removes a row from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a row from the dataset and calls {@link #fireDatasetChanged()}. * * @param rowKey the row key. * @@ -225,8 +221,7 @@ public void removeRow(Comparable rowKey) { } /** - * Removes a column from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a column from the dataset and calls {@link #fireDatasetChanged()}. * * @param columnIndex the column index. * @@ -239,8 +234,7 @@ public void removeColumn(int columnIndex) { } /** - * Removes a column from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a column from the dataset and calls {@link #fireDatasetChanged()}. * * @param columnKey the column key. * @@ -253,8 +247,7 @@ public void removeColumn(Comparable columnKey) { } /** - * Clears all data from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Clears all data from the dataset and calls {@link #fireDatasetChanged()}. */ public void clear() { this.data.clear(); diff --git a/src/main/java/org/jfree/data/statistics/DefaultBoxAndWhiskerXYDataset.java b/src/main/java/org/jfree/data/statistics/DefaultBoxAndWhiskerXYDataset.java index f83d42e01..8109a9b9c 100644 --- a/src/main/java/org/jfree/data/statistics/DefaultBoxAndWhiskerXYDataset.java +++ b/src/main/java/org/jfree/data/statistics/DefaultBoxAndWhiskerXYDataset.java @@ -44,7 +44,6 @@ import org.jfree.data.Range; import org.jfree.data.RangeInfo; -import org.jfree.data.general.DatasetChangeEvent; import org.jfree.data.xy.AbstractXYDataset; /** @@ -194,8 +193,7 @@ public int getItemCount(int series) { } /** - * Adds an item to the dataset and sends a {@link DatasetChangeEvent} to - * all registered listeners. + * Adds an item to the dataset and calls {@link #fireDatasetChanged()}. * * @param date the date ({@code null} not permitted). * @param item the item ({@code null} not permitted). diff --git a/src/main/java/org/jfree/data/statistics/DefaultMultiValueCategoryDataset.java b/src/main/java/org/jfree/data/statistics/DefaultMultiValueCategoryDataset.java index c8cd6f95f..4d9eb63cd 100644 --- a/src/main/java/org/jfree/data/statistics/DefaultMultiValueCategoryDataset.java +++ b/src/main/java/org/jfree/data/statistics/DefaultMultiValueCategoryDataset.java @@ -1,409 +1,407 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ------------------------------------- - * DefaultMultiValueCategoryDataset.java - * ------------------------------------- - * (C) Copyright 2007-present, by David Forslund and Contributors. - * - * Original Author: David Forslund; - * Contributor(s): David Gilbert; - * - */ - -package org.jfree.data.statistics; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.PublicCloneable; - -import org.jfree.data.KeyedObjects2D; -import org.jfree.data.Range; -import org.jfree.data.RangeInfo; -import org.jfree.data.general.AbstractDataset; -import org.jfree.data.general.DatasetChangeEvent; - -/** - * A category dataset that defines multiple values for each item. - */ -public class DefaultMultiValueCategoryDataset extends AbstractDataset - implements MultiValueCategoryDataset, RangeInfo, PublicCloneable { - - /** - * Storage for the data. - */ - protected KeyedObjects2D data; - - /** - * The minimum range value. - */ - private Number minimumRangeValue; - - /** - * The maximum range value. - */ - private Number maximumRangeValue; - - /** - * The range of values. - */ - private Range rangeBounds; - - /** - * Creates a new dataset. - */ - public DefaultMultiValueCategoryDataset() { - this.data = new KeyedObjects2D(); - this.minimumRangeValue = null; - this.maximumRangeValue = null; - this.rangeBounds = new Range(0.0, 0.0); - } - - /** - * Adds a list of values to the dataset ({@code null} and Double.NaN - * items are automatically removed) and sends a {@link DatasetChangeEvent} - * to all registered listeners. - * - * @param values a list of values ({@code null} not permitted). - * @param rowKey the row key ({@code null} not permitted). - * @param columnKey the column key ({@code null} not permitted). - */ - public void add(List values, Comparable rowKey, Comparable columnKey) { - - Args.nullNotPermitted(values, "values"); - Args.nullNotPermitted(rowKey, "rowKey"); - Args.nullNotPermitted(columnKey, "columnKey"); - List vlist = new ArrayList(values.size()); - Iterator iterator = values.listIterator(); - while (iterator.hasNext()) { - Object obj = iterator.next(); - if (obj instanceof Number) { - Number n = (Number) obj; - double v = n.doubleValue(); - if (!Double.isNaN(v)) { - vlist.add(n); - } - } - } - Collections.sort(vlist); - this.data.addObject(vlist, rowKey, columnKey); - - if (vlist.size() > 0) { - double maxval = Double.NEGATIVE_INFINITY; - double minval = Double.POSITIVE_INFINITY; - for (int i = 0; i < vlist.size(); i++) { - Number n = (Number) vlist.get(i); - double v = n.doubleValue(); - minval = Math.min(minval, v); - maxval = Math.max(maxval, v); - } - - // update the cached range values... - if (this.maximumRangeValue == null) { - this.maximumRangeValue = maxval; - } - else if (maxval > this.maximumRangeValue.doubleValue()) { - this.maximumRangeValue = maxval; - } - - if (this.minimumRangeValue == null) { - this.minimumRangeValue = minval; - } - else if (minval < this.minimumRangeValue.doubleValue()) { - this.minimumRangeValue = minval; - } - this.rangeBounds = new Range(this.minimumRangeValue.doubleValue(), - this.maximumRangeValue.doubleValue()); - } - - fireDatasetChanged(); - } - - /** - * Returns a list (possibly empty) of the values for the specified item. - * The returned list should be unmodifiable. - * - * @param row the row index (zero-based). - * @param column the column index (zero-based). - * - * @return The list of values. - */ - @Override - public List getValues(int row, int column) { - List values = (List) this.data.getObject(row, column); - if (values != null) { - return Collections.unmodifiableList(values); - } - else { - return Collections.EMPTY_LIST; - } - } - - /** - * Returns a list (possibly empty) of the values for the specified item. - * The returned list should be unmodifiable. - * - * @param rowKey the row key ({@code null} not permitted). - * @param columnKey the column key ({@code null} not permitted). - * - * @return The list of values. - */ - @Override - public List getValues(Comparable rowKey, Comparable columnKey) { - return Collections.unmodifiableList((List) this.data.getObject(rowKey, - columnKey)); - } - - /** - * Returns the average value for the specified item. - * - * @param row the row key. - * @param column the column key. - * - * @return The average value. - */ - @Override - public Number getValue(Comparable row, Comparable column) { - List l = (List) this.data.getObject(row, column); - double average = 0.0d; - int count = 0; - if (l != null && l.size() > 0) { - for (int i = 0; i < l.size(); i++) { - Number n = (Number) l.get(i); - average += n.doubleValue(); - count += 1; - } - if (count > 0) { - average = average / count; - } - } - if (count == 0) { - return null; - } - return average; - } - - /** - * Returns the average value for the specified item. - * - * @param row the row index. - * @param column the column index. - * - * @return The average value. - */ - @Override - public Number getValue(int row, int column) { - List l = (List) this.data.getObject(row, column); - double average = 0.0d; - int count = 0; - if (l != null && l.size() > 0) { - for (int i = 0; i < l.size(); i++) { - Number n = (Number) l.get(i); - average += n.doubleValue(); - count += 1; - } - if (count > 0) { - average = average / count; - } - } - if (count == 0) { - return null; - } - return average; - } - - /** - * Returns the column index for a given key. - * - * @param key the column key. - * - * @return The column index. - */ - @Override - public int getColumnIndex(Comparable key) { - return this.data.getColumnIndex(key); - } - - /** - * Returns a column key. - * - * @param column the column index (zero-based). - * - * @return The column key. - */ - @Override - public Comparable getColumnKey(int column) { - return this.data.getColumnKey(column); - } - - /** - * Returns the column keys. - * - * @return The keys. - */ - @Override - public List getColumnKeys() { - return this.data.getColumnKeys(); - } - - /** - * Returns the row index for a given key. - * - * @param key the row key. - * - * @return The row index. - */ - @Override - public int getRowIndex(Comparable key) { - return this.data.getRowIndex(key); - } - - /** - * Returns a row key. - * - * @param row the row index (zero-based). - * - * @return The row key. - */ - @Override - public Comparable getRowKey(int row) { - return this.data.getRowKey(row); - } - - /** - * Returns the row keys. - * - * @return The keys. - */ - @Override - public List getRowKeys() { - return this.data.getRowKeys(); - } - - /** - * Returns the number of rows in the table. - * - * @return The row count. - */ - @Override - public int getRowCount() { - return this.data.getRowCount(); - } - - /** - * Returns the number of columns in the table. - * - * @return The column count. - */ - @Override - public int getColumnCount() { - return this.data.getColumnCount(); - } - - /** - * Returns the minimum y-value in the dataset. - * - * @param includeInterval a flag that determines whether or not the - * y-interval is taken into account. - * - * @return The minimum value. - */ - @Override - public double getRangeLowerBound(boolean includeInterval) { - double result = Double.NaN; - if (this.minimumRangeValue != null) { - result = this.minimumRangeValue.doubleValue(); - } - return result; - } - - /** - * Returns the maximum y-value in the dataset. - * - * @param includeInterval a flag that determines whether or not the - * y-interval is taken into account. - * - * @return The maximum value. - */ - @Override - public double getRangeUpperBound(boolean includeInterval) { - double result = Double.NaN; - if (this.maximumRangeValue != null) { - result = this.maximumRangeValue.doubleValue(); - } - return result; - } - - /** - * Returns the range of the values in this dataset's range. - * - * @param includeInterval a flag that determines whether or not the - * y-interval is taken into account. - * @return The range. - */ - @Override - public Range getRangeBounds(boolean includeInterval) { - return this.rangeBounds; - } - - /** - * Tests this dataset for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof DefaultMultiValueCategoryDataset)) { - return false; - } - DefaultMultiValueCategoryDataset that - = (DefaultMultiValueCategoryDataset) obj; - return this.data.equals(that.data); - } - - /** - * Returns a clone of this instance. - * - * @return A clone. - * - * @throws CloneNotSupportedException if the dataset cannot be cloned. - */ - @Override - public Object clone() throws CloneNotSupportedException { - DefaultMultiValueCategoryDataset clone - = (DefaultMultiValueCategoryDataset) super.clone(); - clone.data = (KeyedObjects2D) this.data.clone(); - return clone; - } -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ------------------------------------- + * DefaultMultiValueCategoryDataset.java + * ------------------------------------- + * (C) Copyright 2007-present, by David Forslund and Contributors. + * + * Original Author: David Forslund; + * Contributor(s): David Gilbert; + * + */ + +package org.jfree.data.statistics; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; + +import org.jfree.data.KeyedObjects2D; +import org.jfree.data.Range; +import org.jfree.data.RangeInfo; +import org.jfree.data.general.AbstractDataset; + +/** + * A category dataset that defines multiple values for each item. + */ +public class DefaultMultiValueCategoryDataset extends AbstractDataset + implements MultiValueCategoryDataset, RangeInfo, PublicCloneable { + + /** + * Storage for the data. + */ + protected KeyedObjects2D data; + + /** + * The minimum range value. + */ + private Number minimumRangeValue; + + /** + * The maximum range value. + */ + private Number maximumRangeValue; + + /** + * The range of values. + */ + private Range rangeBounds; + + /** + * Creates a new dataset. + */ + public DefaultMultiValueCategoryDataset() { + this.data = new KeyedObjects2D(); + this.minimumRangeValue = null; + this.maximumRangeValue = null; + this.rangeBounds = new Range(0.0, 0.0); + } + + /** + * Adds a list of values to the dataset ({@code null} and Double.NaN + * items are automatically removed) and calls {@link #fireDatasetChanged()}. + * + * @param values a list of values ({@code null} not permitted). + * @param rowKey the row key ({@code null} not permitted). + * @param columnKey the column key ({@code null} not permitted). + */ + public void add(List values, Comparable rowKey, Comparable columnKey) { + + Args.nullNotPermitted(values, "values"); + Args.nullNotPermitted(rowKey, "rowKey"); + Args.nullNotPermitted(columnKey, "columnKey"); + List vlist = new ArrayList(values.size()); + Iterator iterator = values.listIterator(); + while (iterator.hasNext()) { + Object obj = iterator.next(); + if (obj instanceof Number) { + Number n = (Number) obj; + double v = n.doubleValue(); + if (!Double.isNaN(v)) { + vlist.add(n); + } + } + } + Collections.sort(vlist); + this.data.addObject(vlist, rowKey, columnKey); + + if (vlist.size() > 0) { + double maxval = Double.NEGATIVE_INFINITY; + double minval = Double.POSITIVE_INFINITY; + for (int i = 0; i < vlist.size(); i++) { + Number n = (Number) vlist.get(i); + double v = n.doubleValue(); + minval = Math.min(minval, v); + maxval = Math.max(maxval, v); + } + + // update the cached range values... + if (this.maximumRangeValue == null) { + this.maximumRangeValue = maxval; + } + else if (maxval > this.maximumRangeValue.doubleValue()) { + this.maximumRangeValue = maxval; + } + + if (this.minimumRangeValue == null) { + this.minimumRangeValue = minval; + } + else if (minval < this.minimumRangeValue.doubleValue()) { + this.minimumRangeValue = minval; + } + this.rangeBounds = new Range(this.minimumRangeValue.doubleValue(), + this.maximumRangeValue.doubleValue()); + } + + fireDatasetChanged(); + } + + /** + * Returns a list (possibly empty) of the values for the specified item. + * The returned list should be unmodifiable. + * + * @param row the row index (zero-based). + * @param column the column index (zero-based). + * + * @return The list of values. + */ + @Override + public List getValues(int row, int column) { + List values = (List) this.data.getObject(row, column); + if (values != null) { + return Collections.unmodifiableList(values); + } + else { + return Collections.EMPTY_LIST; + } + } + + /** + * Returns a list (possibly empty) of the values for the specified item. + * The returned list should be unmodifiable. + * + * @param rowKey the row key ({@code null} not permitted). + * @param columnKey the column key ({@code null} not permitted). + * + * @return The list of values. + */ + @Override + public List getValues(Comparable rowKey, Comparable columnKey) { + return Collections.unmodifiableList((List) this.data.getObject(rowKey, + columnKey)); + } + + /** + * Returns the average value for the specified item. + * + * @param row the row key. + * @param column the column key. + * + * @return The average value. + */ + @Override + public Number getValue(Comparable row, Comparable column) { + List l = (List) this.data.getObject(row, column); + double average = 0.0d; + int count = 0; + if (l != null && l.size() > 0) { + for (int i = 0; i < l.size(); i++) { + Number n = (Number) l.get(i); + average += n.doubleValue(); + count += 1; + } + if (count > 0) { + average = average / count; + } + } + if (count == 0) { + return null; + } + return average; + } + + /** + * Returns the average value for the specified item. + * + * @param row the row index. + * @param column the column index. + * + * @return The average value. + */ + @Override + public Number getValue(int row, int column) { + List l = (List) this.data.getObject(row, column); + double average = 0.0d; + int count = 0; + if (l != null && l.size() > 0) { + for (int i = 0; i < l.size(); i++) { + Number n = (Number) l.get(i); + average += n.doubleValue(); + count += 1; + } + if (count > 0) { + average = average / count; + } + } + if (count == 0) { + return null; + } + return average; + } + + /** + * Returns the column index for a given key. + * + * @param key the column key. + * + * @return The column index. + */ + @Override + public int getColumnIndex(Comparable key) { + return this.data.getColumnIndex(key); + } + + /** + * Returns a column key. + * + * @param column the column index (zero-based). + * + * @return The column key. + */ + @Override + public Comparable getColumnKey(int column) { + return this.data.getColumnKey(column); + } + + /** + * Returns the column keys. + * + * @return The keys. + */ + @Override + public List getColumnKeys() { + return this.data.getColumnKeys(); + } + + /** + * Returns the row index for a given key. + * + * @param key the row key. + * + * @return The row index. + */ + @Override + public int getRowIndex(Comparable key) { + return this.data.getRowIndex(key); + } + + /** + * Returns a row key. + * + * @param row the row index (zero-based). + * + * @return The row key. + */ + @Override + public Comparable getRowKey(int row) { + return this.data.getRowKey(row); + } + + /** + * Returns the row keys. + * + * @return The keys. + */ + @Override + public List getRowKeys() { + return this.data.getRowKeys(); + } + + /** + * Returns the number of rows in the table. + * + * @return The row count. + */ + @Override + public int getRowCount() { + return this.data.getRowCount(); + } + + /** + * Returns the number of columns in the table. + * + * @return The column count. + */ + @Override + public int getColumnCount() { + return this.data.getColumnCount(); + } + + /** + * Returns the minimum y-value in the dataset. + * + * @param includeInterval a flag that determines whether or not the + * y-interval is taken into account. + * + * @return The minimum value. + */ + @Override + public double getRangeLowerBound(boolean includeInterval) { + double result = Double.NaN; + if (this.minimumRangeValue != null) { + result = this.minimumRangeValue.doubleValue(); + } + return result; + } + + /** + * Returns the maximum y-value in the dataset. + * + * @param includeInterval a flag that determines whether or not the + * y-interval is taken into account. + * + * @return The maximum value. + */ + @Override + public double getRangeUpperBound(boolean includeInterval) { + double result = Double.NaN; + if (this.maximumRangeValue != null) { + result = this.maximumRangeValue.doubleValue(); + } + return result; + } + + /** + * Returns the range of the values in this dataset's range. + * + * @param includeInterval a flag that determines whether or not the + * y-interval is taken into account. + * @return The range. + */ + @Override + public Range getRangeBounds(boolean includeInterval) { + return this.rangeBounds; + } + + /** + * Tests this dataset for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof DefaultMultiValueCategoryDataset)) { + return false; + } + DefaultMultiValueCategoryDataset that + = (DefaultMultiValueCategoryDataset) obj; + return this.data.equals(that.data); + } + + /** + * Returns a clone of this instance. + * + * @return A clone. + * + * @throws CloneNotSupportedException if the dataset cannot be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + DefaultMultiValueCategoryDataset clone + = (DefaultMultiValueCategoryDataset) super.clone(); + clone.data = (KeyedObjects2D) this.data.clone(); + return clone; + } +} diff --git a/src/main/java/org/jfree/data/statistics/DefaultStatisticalCategoryDataset.java b/src/main/java/org/jfree/data/statistics/DefaultStatisticalCategoryDataset.java index 71d8f3316..98b6e4fc1 100644 --- a/src/main/java/org/jfree/data/statistics/DefaultStatisticalCategoryDataset.java +++ b/src/main/java/org/jfree/data/statistics/DefaultStatisticalCategoryDataset.java @@ -43,7 +43,6 @@ import org.jfree.data.Range; import org.jfree.data.RangeInfo; import org.jfree.data.general.AbstractDataset; -import org.jfree.data.general.DatasetChangeEvent; /** * A convenience class that provides a default implementation of the @@ -412,8 +411,7 @@ public void add(Number mean, Number standardDeviation, } /** - * Removes an item from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes an item from the dataset and calls {@link #fireDatasetChanged()}. * * @param rowKey the row key ({@code null} not permitted). * @param columnKey the column key ({@code null} not permitted). @@ -446,8 +444,7 @@ public void remove(Comparable rowKey, Comparable columnKey) { /** - * Removes a row from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a row from the dataset and calls {@link #fireDatasetChanged()}. * * @param rowIndex the row index. * @@ -460,8 +457,7 @@ public void removeRow(int rowIndex) { } /** - * Removes a row from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a row from the dataset and calls {@link #fireDatasetChanged()}. * * @param rowKey the row key ({@code null} not permitted). * @@ -474,8 +470,7 @@ public void removeRow(Comparable rowKey) { } /** - * Removes a column from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a column from the dataset and calls {@link #fireDatasetChanged()}. * * @param columnIndex the column index. * @@ -488,8 +483,7 @@ public void removeColumn(int columnIndex) { } /** - * Removes a column from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Removes a column from the dataset and calls {@link #fireDatasetChanged()}. * * @param columnKey the column key ({@code null} not permitted). * @@ -502,8 +496,7 @@ public void removeColumn(Comparable columnKey) { } /** - * Clears all data from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Clears all data from the dataset and calls {@link #fireDatasetChanged()}. */ public void clear() { this.data.clear(); diff --git a/src/main/java/org/jfree/data/statistics/HistogramDataset.java b/src/main/java/org/jfree/data/statistics/HistogramDataset.java index 66ce3729b..ebc5b5194 100644 --- a/src/main/java/org/jfree/data/statistics/HistogramDataset.java +++ b/src/main/java/org/jfree/data/statistics/HistogramDataset.java @@ -48,7 +48,6 @@ import org.jfree.chart.util.Args; import org.jfree.chart.util.PublicCloneable; -import org.jfree.data.general.DatasetChangeEvent; import org.jfree.data.xy.AbstractIntervalXYDataset; import org.jfree.data.xy.IntervalXYDataset; @@ -89,8 +88,7 @@ public HistogramType getType() { } /** - * Sets the histogram type and sends a {@link DatasetChangeEvent} to all - * registered listeners. + * Sets the histogram type and calls {@link #fireDatasetChanged()}. * * @param type the type ({@code null} not permitted). */ @@ -102,7 +100,7 @@ public void setType(HistogramType type) { /** * Adds a series to the dataset, using the specified number of bins, - * and sends a {@link DatasetChangeEvent} to all registered listeners. + * and calls {@link #fireDatasetChanged()}. * * @param key the series key ({@code null} not permitted). * @param values the values ({@code null} not permitted). diff --git a/src/main/java/org/jfree/data/time/TimePeriodValues.java b/src/main/java/org/jfree/data/time/TimePeriodValues.java index 24f6e841d..977eb998d 100644 --- a/src/main/java/org/jfree/data/time/TimePeriodValues.java +++ b/src/main/java/org/jfree/data/time/TimePeriodValues.java @@ -43,7 +43,6 @@ import org.jfree.chart.util.Args; import org.jfree.data.general.Series; -import org.jfree.data.general.SeriesChangeEvent; import org.jfree.data.general.SeriesException; /** @@ -222,8 +221,7 @@ public Number getValue(int index) { } /** - * Adds a data item to the series and sends a {@link SeriesChangeEvent} to - * all registered listeners. + * Adds a data item to the series and calls {@link #fireSeriesChanged()}. * * @param item the item ({@code null} not permitted). */ @@ -337,8 +335,8 @@ private void recalculateBounds() { } /** - * Adds a new data item to the series and sends a {@link SeriesChangeEvent} - * to all registered listeners. + * Adds a new data item to the series by calling + * {@link #add(org.jfree.data.time.TimePeriodValue)}. * * @param period the time period ({@code null} not permitted). * @param value the value. @@ -351,8 +349,8 @@ public void add(TimePeriod period, double value) { } /** - * Adds a new data item to the series and sends a {@link SeriesChangeEvent} - * to all registered listeners. + * Adds a new data item to the series by calling + * {@link #add(org.jfree.data.time.TimePeriodValue)}. * * @param period the time period ({@code null} not permitted). * @param value the value ({@code null} permitted). @@ -363,8 +361,8 @@ public void add(TimePeriod period, Number value) { } /** - * Updates (changes) the value of a data item and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Updates (changes) the value of a data item and calls + * {@link #fireSeriesChanged()}. * * @param index the index of the data item to update. * @param value the new value ({@code null} not permitted). @@ -376,8 +374,7 @@ public void update(int index, Number value) { } /** - * Deletes data from start until end index (end inclusive) and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Deletes data from start until end index (end inclusive) and calls {@link #fireSeriesChanged()}. * * @param start the index of the first period to delete. * @param end the index of the last period to delete. diff --git a/src/main/java/org/jfree/data/time/TimeSeries.java b/src/main/java/org/jfree/data/time/TimeSeries.java index 542775b55..941d3b2cd 100644 --- a/src/main/java/org/jfree/data/time/TimeSeries.java +++ b/src/main/java/org/jfree/data/time/TimeSeries.java @@ -1,1315 +1,1313 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------------- - * TimeSeries.java - * --------------- - * (C) Copyright 2001-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): Bryan Scott; - * Nick Guenther; - * - */ - -package org.jfree.data.time; - -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.TimeZone; -import org.jfree.chart.util.ObjectUtils; - -import org.jfree.chart.util.Args; -import org.jfree.data.Range; -import org.jfree.data.general.Series; -import org.jfree.data.general.SeriesChangeEvent; -import org.jfree.data.general.SeriesException; - -/** - * Represents a sequence of zero or more data items in the form (period, value) - * where 'period' is some instance of a subclass of {@link RegularTimePeriod}. - * The time series will ensure that (a) all data items have the same type of - * period (for example, {@link Day}) and (b) that each period appears at - * most one time in the series. - */ -public class TimeSeries extends Series implements Cloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = -5032960206869675528L; - - /** Default value for the domain description. */ - protected static final String DEFAULT_DOMAIN_DESCRIPTION = "Time"; - - /** Default value for the range description. */ - protected static final String DEFAULT_RANGE_DESCRIPTION = "Value"; - - /** A description of the domain. */ - private String domain; - - /** A description of the range. */ - private String range; - - /** The type of period for the data. */ - protected Class timePeriodClass; - - /** The list of data items in the series. */ - protected List data; - - /** The maximum number of items for the series. */ - private int maximumItemCount; - - /** - * The maximum age of items for the series, specified as a number of - * time periods. - */ - private long maximumItemAge; - - /** - * The minimum y-value in the series. - */ - private double minY; - - /** - * The maximum y-value in the series. - */ - private double maxY; - - /** - * Creates a new (empty) time series. By default, a daily time series is - * created. Use one of the other constructors if you require a different - * time period. - * - * @param name the series name ({@code null} not permitted). - */ - public TimeSeries(Comparable name) { - this(name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION); - } - - /** - * Creates a new time series that contains no data. - *

    - * Descriptions can be specified for the domain and range. One situation - * where this is helpful is when generating a chart for the time series - - * axis labels can be taken from the domain and range description. - * - * @param name the name of the series ({@code null} not permitted). - * @param domain the domain description ({@code null} permitted). - * @param range the range description ({@code null} permitted). - */ - public TimeSeries(Comparable name, String domain, String range) { - super(name); - this.domain = domain; - this.range = range; - this.timePeriodClass = null; - this.data = new java.util.ArrayList(); - this.maximumItemCount = Integer.MAX_VALUE; - this.maximumItemAge = Long.MAX_VALUE; - this.minY = Double.NaN; - this.maxY = Double.NaN; - } - - /** - * Returns the domain description. - * - * @return The domain description (possibly {@code null}). - * - * @see #setDomainDescription(String) - */ - public String getDomainDescription() { - return this.domain; - } - - /** - * Sets the domain description and sends a {@code PropertyChangeEvent} - * (with the property name {@code Domain}) to all registered - * property change listeners. - * - * @param description the description ({@code null} permitted). - * - * @see #getDomainDescription() - */ - public void setDomainDescription(String description) { - String old = this.domain; - this.domain = description; - firePropertyChange("Domain", old, description); - } - - /** - * Returns the range description. - * - * @return The range description (possibly {@code null}). - * - * @see #setRangeDescription(String) - */ - public String getRangeDescription() { - return this.range; - } - - /** - * Sets the range description and sends a {@code PropertyChangeEvent} - * (with the property name {@code Range}) to all registered listeners. - * - * @param description the description ({@code null} permitted). - * - * @see #getRangeDescription() - */ - public void setRangeDescription(String description) { - String old = this.range; - this.range = description; - firePropertyChange("Range", old, description); - } - - /** - * Returns the number of items in the series. - * - * @return The item count. - */ - @Override - public int getItemCount() { - return this.data.size(); - } - - /** - * Returns the list of data items for the series (the list contains - * {@link TimeSeriesDataItem} objects and is unmodifiable). - * - * @return The list of data items. - */ - public List getItems() { - // FIXME: perhaps we should clone the data list - return Collections.unmodifiableList(this.data); - } - - /** - * Returns the maximum number of items that will be retained in the series. - * The default value is {@code Integer.MAX_VALUE}. - * - * @return The maximum item count. - * - * @see #setMaximumItemCount(int) - */ - public int getMaximumItemCount() { - return this.maximumItemCount; - } - - /** - * Sets the maximum number of items that will be retained in the series. - * If you add a new item to the series such that the number of items will - * exceed the maximum item count, then the FIRST element in the series is - * automatically removed, ensuring that the maximum item count is not - * exceeded. - * - * @param maximum the maximum (requires >= 0). - * - * @see #getMaximumItemCount() - */ - public void setMaximumItemCount(int maximum) { - if (maximum < 0) { - throw new IllegalArgumentException("Negative 'maximum' argument."); - } - this.maximumItemCount = maximum; - int count = this.data.size(); - if (count > maximum) { - delete(0, count - maximum - 1); - } - } - - /** - * Returns the maximum item age (in time periods) for the series. - * - * @return The maximum item age. - * - * @see #setMaximumItemAge(long) - */ - public long getMaximumItemAge() { - return this.maximumItemAge; - } - - /** - * Sets the number of time units in the 'history' for the series. This - * provides one mechanism for automatically dropping old data from the - * time series. For example, if a series contains daily data, you might set - * the history count to 30. Then, when you add a new data item, all data - * items more than 30 days older than the latest value are automatically - * dropped from the series. - * - * @param periods the number of time periods. - * - * @see #getMaximumItemAge() - */ - public void setMaximumItemAge(long periods) { - if (periods < 0) { - throw new IllegalArgumentException("Negative 'periods' argument."); - } - this.maximumItemAge = periods; - removeAgedItems(true); // remove old items and notify if necessary - } - - /** - * Returns the range of y-values in the time series. Any {@code null} or - * {@code Double.NaN} data values in the series will be ignored (except for - * the special case where all data values are {@code null}, in which case - * the return value is {@code Range(Double.NaN, Double.NaN)}). If the time - * series contains no items, this method will return {@code null}. - * - * @return The range of y-values in the time series (possibly {@code null}). - */ - public Range findValueRange() { - if (this.data.isEmpty()) { - return null; - } - return new Range(this.minY, this.maxY); - } - - /** - * Returns the range of y-values in the time series that fall within - * the specified range of x-values. This is equivalent to - * {@code findValueRange(xRange, TimePeriodAnchor.MIDDLE, timeZone)}. - * - * @param xRange the subrange of x-values ({@code null} not permitted). - * @param timeZone the time zone used to convert x-values to time periods - * ({@code null} not permitted). - * - * @return The range. - */ - public Range findValueRange(Range xRange, TimeZone timeZone) { - return findValueRange(xRange, TimePeriodAnchor.MIDDLE, timeZone); - } - - /** - * Finds the range of y-values that fall within the specified range of - * x-values (where the x-values are interpreted as milliseconds since the - * epoch and converted to time periods using the specified timezone). - * - * @param xRange the subset of x-values to use ({@code null} not - * permitted). - * @param xAnchor the anchor point for the x-values ({@code null} - * not permitted). - * @param zone the time zone ({@code null} not permitted). - * - * @return The range of y-values. - */ - public Range findValueRange(Range xRange, TimePeriodAnchor xAnchor, - TimeZone zone) { - Args.nullNotPermitted(xRange, "xRange"); - Args.nullNotPermitted(xAnchor, "xAnchor"); - Args.nullNotPermitted(zone, "zone"); - if (this.data.isEmpty()) { - return null; - } - Calendar calendar = Calendar.getInstance(zone); - return findValueRange(xRange, xAnchor, calendar); - } - - /** - * Finds the range of y-values that fall within the specified range of - * x-values (where the x-values are interpreted as milliseconds since the - * epoch and converted to time periods using the specified calendar). - * - * @param xRange the subset of x-values to use ({@code null} not - * permitted). - * @param xAnchor the anchor point for the x-values ({@code null} - * not permitted). - * @param calendar the calendar ({@code null} not permitted). - * - * @return The range of y-values. - */ - public Range findValueRange(Range xRange, TimePeriodAnchor xAnchor, - Calendar calendar) { - // since the items are ordered, we could be more clever here and avoid - // iterating over all the data - double lowY = Double.POSITIVE_INFINITY; - double highY = Double.NEGATIVE_INFINITY; - for (int i = 0; i < this.data.size(); i++) { - TimeSeriesDataItem item = (TimeSeriesDataItem) this.data.get(i); - long millis = item.getPeriod().getMillisecond(xAnchor, calendar); - if (xRange.contains(millis)) { - Number n = item.getValue(); - if (n != null) { - double v = n.doubleValue(); - lowY = minIgnoreNaN(lowY, v); - highY = maxIgnoreNaN(highY, v); - } - } - } - if (Double.isInfinite(lowY) && Double.isInfinite(highY)) { - if (lowY < highY) { - return new Range(lowY, highY); - } else { - return new Range(Double.NaN, Double.NaN); - } - } - return new Range(lowY, highY); - } - - /** - * Returns the smallest y-value in the series, ignoring any - * {@code null} and {@code Double.NaN} values. This method - * returns {@code Double.NaN} if there is no smallest y-value (for - * example, when the series is empty). - * - * @return The smallest y-value. - * - * @see #getMaxY() - */ - public double getMinY() { - return this.minY; - } - - /** - * Returns the largest y-value in the series, ignoring any - * {@code null} and {@code Double.NaN} values. This method - * returns {@code Double.NaN} if there is no largest y-value - * (for example, when the series is empty). - * - * @return The largest y-value. - * - * @see #getMinY() - */ - public double getMaxY() { - return this.maxY; - } - - /** - * Returns the time period class for this series. - *

    - * Only one time period class can be used within a single series (enforced). - * If you add a data item with a {@link Year} for the time period, then all - * subsequent data items must also have a {@link Year} for the time period. - * - * @return The time period class (may be {@code null} but only for - * an empty series). - */ - public Class getTimePeriodClass() { - return this.timePeriodClass; - } - - /** - * Returns a data item from the dataset. Note that the returned object - * is a clone of the item in the series, so modifying it will have no - * effect on the data series. - * - * @param index the item index. - * - * @return The data item. - */ - public TimeSeriesDataItem getDataItem(int index) { - TimeSeriesDataItem item = (TimeSeriesDataItem) this.data.get(index); - return (TimeSeriesDataItem) item.clone(); - } - - /** - * Returns the data item for a specific period. Note that the returned - * object is a clone of the item in the series, so modifying it will have - * no effect on the data series. - * - * @param period the period of interest ({@code null} not allowed). - * - * @return The data item matching the specified period (or - * {@code null} if there is no match). - * - * @see #getDataItem(int) - */ - public TimeSeriesDataItem getDataItem(RegularTimePeriod period) { - int index = getIndex(period); - if (index >= 0) { - return getDataItem(index); - } - return null; - } - - /** - * Returns a data item for the series. This method returns the object - * that is used for the underlying storage - you should not modify the - * contents of the returned value unless you know what you are doing. - * - * @param index the item index (zero-based). - * - * @return The data item. - * - * @see #getDataItem(int) - */ - TimeSeriesDataItem getRawDataItem(int index) { - return (TimeSeriesDataItem) this.data.get(index); - } - - /** - * Returns a data item for the series. This method returns the object - * that is used for the underlying storage - you should not modify the - * contents of the returned value unless you know what you are doing. - * - * @param period the item index (zero-based). - * - * @return The data item. - * - * @see #getDataItem(RegularTimePeriod) - */ - TimeSeriesDataItem getRawDataItem(RegularTimePeriod period) { - int index = getIndex(period); - if (index >= 0) { - return (TimeSeriesDataItem) this.data.get(index); - } - return null; - } - - /** - * Returns the time period at the specified index. - * - * @param index the index of the data item. - * - * @return The time period. - */ - public RegularTimePeriod getTimePeriod(int index) { - return getRawDataItem(index).getPeriod(); - } - - /** - * Returns a time period that would be the next in sequence on the end of - * the time series. - * - * @return The next time period. - */ - public RegularTimePeriod getNextTimePeriod() { - RegularTimePeriod last = getTimePeriod(getItemCount() - 1); - return last.next(); - } - - /** - * Returns a collection of all the time periods in the time series. - * - * @return A collection of all the time periods. - */ - public Collection getTimePeriods() { - Collection result = new java.util.ArrayList(); - for (int i = 0; i < getItemCount(); i++) { - result.add(getTimePeriod(i)); - } - return result; - } - - /** - * Returns a collection of time periods in the specified series, but not in - * this series, and therefore unique to the specified series. - * - * @param series the series to check against this one. - * - * @return The unique time periods. - */ - public Collection getTimePeriodsUniqueToOtherSeries(TimeSeries series) { - Collection result = new java.util.ArrayList(); - for (int i = 0; i < series.getItemCount(); i++) { - RegularTimePeriod period = series.getTimePeriod(i); - int index = getIndex(period); - if (index < 0) { - result.add(period); - } - } - return result; - } - - /** - * Returns the index for the item (if any) that corresponds to a time - * period. - * - * @param period the time period ({@code null} not permitted). - * - * @return The index. - */ - public int getIndex(RegularTimePeriod period) { - Args.nullNotPermitted(period, "period"); - TimeSeriesDataItem dummy = new TimeSeriesDataItem( - period, Integer.MIN_VALUE); - return Collections.binarySearch(this.data, dummy); - } - - /** - * Returns the value at the specified index. - * - * @param index index of a value. - * - * @return The value (possibly {@code null}). - */ - public Number getValue(int index) { - return getRawDataItem(index).getValue(); - } - - /** - * Returns the value for a time period. If there is no data item with the - * specified period, this method will return {@code null}. - * - * @param period time period ({@code null} not permitted). - * - * @return The value (possibly {@code null}). - */ - public Number getValue(RegularTimePeriod period) { - int index = getIndex(period); - if (index >= 0) { - return getValue(index); - } - return null; - } - - /** - * Adds a data item to the series and sends a {@link SeriesChangeEvent} to - * all registered listeners. - * - * @param item the (timeperiod, value) pair ({@code null} not permitted). - */ - public void add(TimeSeriesDataItem item) { - add(item, true); - } - - /** - * Adds a data item to the series and sends a {@link SeriesChangeEvent} to - * all registered listeners. - * - * @param item the (timeperiod, value) pair ({@code null} not permitted). - * @param notify notify listeners? - */ - public void add(TimeSeriesDataItem item, boolean notify) { - Args.nullNotPermitted(item, "item"); - item = (TimeSeriesDataItem) item.clone(); - Class c = item.getPeriod().getClass(); - if (this.timePeriodClass == null) { - this.timePeriodClass = c; - } - else if (!this.timePeriodClass.equals(c)) { - StringBuilder b = new StringBuilder(); - b.append("You are trying to add data where the time period class "); - b.append("is "); - b.append(item.getPeriod().getClass().getName()); - b.append(", but the TimeSeries is expecting an instance of "); - b.append(this.timePeriodClass.getName()); - b.append("."); - throw new SeriesException(b.toString()); - } - - // make the change (if it's not a duplicate time period)... - boolean added = false; - int count = getItemCount(); - if (count == 0) { - this.data.add(item); - added = true; - } - else { - RegularTimePeriod last = getTimePeriod(getItemCount() - 1); - if (item.getPeriod().compareTo(last) > 0) { - this.data.add(item); - added = true; - } - else { - int index = Collections.binarySearch(this.data, item); - if (index < 0) { - this.data.add(-index - 1, item); - added = true; - } - else { - StringBuilder b = new StringBuilder(); - b.append("You are attempting to add an observation for "); - b.append("the time period "); - b.append(item.getPeriod().toString()); - b.append(" but the series already contains an observation"); - b.append(" for that time period. Duplicates are not "); - b.append("permitted. Try using the addOrUpdate() method."); - throw new SeriesException(b.toString()); - } - } - } - if (added) { - updateBoundsForAddedItem(item); - // check if this addition will exceed the maximum item count... - if (getItemCount() > this.maximumItemCount) { - TimeSeriesDataItem d = (TimeSeriesDataItem) this.data.remove(0); - updateBoundsForRemovedItem(d); - } - - removeAgedItems(false); // remove old items if necessary, but - // don't notify anyone, because that - // happens next anyway... - if (notify) { - fireSeriesChanged(); - } - } - - } - - /** - * Adds a new data item to the series and sends a {@link SeriesChangeEvent} - * to all registered listeners. - * - * @param period the time period ({@code null} not permitted). - * @param value the value. - */ - public void add(RegularTimePeriod period, double value) { - // defer argument checking... - add(period, value, true); - } - - /** - * Adds a new data item to the series and sends a {@link SeriesChangeEvent} - * to all registered listeners. - * - * @param period the time period ({@code null} not permitted). - * @param value the value. - * @param notify notify listeners? - */ - public void add(RegularTimePeriod period, double value, boolean notify) { - // defer argument checking... - TimeSeriesDataItem item = new TimeSeriesDataItem(period, value); - add(item, notify); - } - - /** - * Adds a new data item to the series and sends - * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered - * listeners. - * - * @param period the time period ({@code null} not permitted). - * @param value the value ({@code null} permitted). - */ - public void add(RegularTimePeriod period, Number value) { - // defer argument checking... - add(period, value, true); - } - - /** - * Adds a new data item to the series and sends a {@link SeriesChangeEvent} - * to all registered listeners. - * - * @param period the time period ({@code null} not permitted). - * @param value the value ({@code null} permitted). - * @param notify notify listeners? - */ - public void add(RegularTimePeriod period, Number value, boolean notify) { - // defer argument checking... - TimeSeriesDataItem item = new TimeSeriesDataItem(period, value); - add(item, notify); - } - - /** - * Updates (changes) the value for a time period. Throws a - * {@link SeriesException} if the period does not exist. - * - * @param period the period ({@code null} not permitted). - * @param value the value. - */ - public void update(RegularTimePeriod period, double value) { - update(period, Double.valueOf(value)); - } - - /** - * Updates (changes) the value for a time period. Throws a - * {@link SeriesException} if the period does not exist. - * - * @param period the period ({@code null} not permitted). - * @param value the value ({@code null} permitted). - */ - public void update(RegularTimePeriod period, Number value) { - TimeSeriesDataItem temp = new TimeSeriesDataItem(period, value); - int index = Collections.binarySearch(this.data, temp); - if (index < 0) { - throw new SeriesException("There is no existing value for the " - + "specified 'period'."); - } - update(index, value); - } - - /** - * Updates (changes) the value of a data item. - * - * @param index the index of the data item. - * @param value the new value ({@code null} permitted). - */ - public void update(int index, Number value) { - TimeSeriesDataItem item = (TimeSeriesDataItem) this.data.get(index); - boolean iterate = false; - Number oldYN = item.getValue(); - if (oldYN != null) { - double oldY = oldYN.doubleValue(); - if (!Double.isNaN(oldY)) { - iterate = oldY <= this.minY || oldY >= this.maxY; - } - } - item.setValue(value); - if (iterate) { - updateMinMaxYByIteration(); - } - else if (value != null) { - double yy = value.doubleValue(); - this.minY = minIgnoreNaN(this.minY, yy); - this.maxY = maxIgnoreNaN(this.maxY, yy); - } - fireSeriesChanged(); - } - - /** - * Adds or updates data from one series to another. Returns another series - * containing the values that were overwritten. - * - * @param series the series to merge with this. - * - * @return A series containing the values that were overwritten. - */ - public TimeSeries addAndOrUpdate(TimeSeries series) { - TimeSeries overwritten = new TimeSeries("Overwritten values from: " - + getKey()); - for (int i = 0; i < series.getItemCount(); i++) { - TimeSeriesDataItem item = series.getRawDataItem(i); - TimeSeriesDataItem oldItem = addOrUpdate(item.getPeriod(), - item.getValue()); - if (oldItem != null) { - overwritten.add(oldItem); - } - } - return overwritten; - } - - /** - * Adds or updates an item in the times series and sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param period the time period to add/update ({@code null} not - * permitted). - * @param value the new value. - * - * @return A copy of the overwritten data item, or {@code null} if no - * item was overwritten. - */ - public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period, - double value) { - return addOrUpdate(period, Double.valueOf(value)); - } - - /** - * Adds or updates an item in the times series and sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param period the time period to add/update ({@code null} not - * permitted). - * @param value the new value ({@code null} permitted). - * - * @return A copy of the overwritten data item, or {@code null} if no - * item was overwritten. - */ - public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period, - Number value) { - return addOrUpdate(new TimeSeriesDataItem(period, value)); - } - - /** - * Adds or updates an item in the times series and sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param item the data item ({@code null} not permitted). - * - * @return A copy of the overwritten data item, or {@code null} if no - * item was overwritten. - */ - public TimeSeriesDataItem addOrUpdate(TimeSeriesDataItem item) { - - Args.nullNotPermitted(item, "item"); - Class periodClass = item.getPeriod().getClass(); - if (this.timePeriodClass == null) { - this.timePeriodClass = periodClass; - } - else if (!this.timePeriodClass.equals(periodClass)) { - String msg = "You are trying to add data where the time " - + "period class is " + periodClass.getName() - + ", but the TimeSeries is expecting an instance of " - + this.timePeriodClass.getName() + "."; - throw new SeriesException(msg); - } - TimeSeriesDataItem overwritten = null; - int index = Collections.binarySearch(this.data, item); - if (index >= 0) { - TimeSeriesDataItem existing - = (TimeSeriesDataItem) this.data.get(index); - overwritten = (TimeSeriesDataItem) existing.clone(); - // figure out if we need to iterate through all the y-values - // to find the revised minY / maxY - boolean iterate = false; - Number oldYN = existing.getValue(); - double oldY = oldYN != null ? oldYN.doubleValue() : Double.NaN; - if (!Double.isNaN(oldY)) { - iterate = oldY <= this.minY || oldY >= this.maxY; - } - existing.setValue(item.getValue()); - if (iterate) { - updateMinMaxYByIteration(); - } - else if (item.getValue() != null) { - double yy = item.getValue().doubleValue(); - this.minY = minIgnoreNaN(this.minY, yy); - this.maxY = maxIgnoreNaN(this.maxY, yy); - } - } - else { - item = (TimeSeriesDataItem) item.clone(); - this.data.add(-index - 1, item); - updateBoundsForAddedItem(item); - - // check if this addition will exceed the maximum item count... - if (getItemCount() > this.maximumItemCount) { - TimeSeriesDataItem d = (TimeSeriesDataItem) this.data.remove(0); - updateBoundsForRemovedItem(d); - } - } - removeAgedItems(false); // remove old items if necessary, but - // don't notify anyone, because that - // happens next anyway... - fireSeriesChanged(); - return overwritten; - - } - - /** - * Age items in the series. Ensure that the timespan from the youngest to - * the oldest record in the series does not exceed maximumItemAge time - * periods. Oldest items will be removed if required. - * - * @param notify controls whether or not a {@link SeriesChangeEvent} is - * sent to registered listeners IF any items are removed. - */ - public void removeAgedItems(boolean notify) { - // check if there are any values earlier than specified by the history - // count... - if (getItemCount() > 1) { - long latest = getTimePeriod(getItemCount() - 1).getSerialIndex(); - boolean removed = false; - while ((latest - getTimePeriod(0).getSerialIndex()) - > this.maximumItemAge) { - this.data.remove(0); - removed = true; - } - if (removed) { - updateMinMaxYByIteration(); - if (notify) { - fireSeriesChanged(); - } - } - } - } - - /** - * Age items in the series. Ensure that the timespan from the supplied - * time to the oldest record in the series does not exceed history count. - * oldest items will be removed if required. - * - * @param latest the time to be compared against when aging data - * (specified in milliseconds). - * @param notify controls whether or not a {@link SeriesChangeEvent} is - * sent to registered listeners IF any items are removed. - */ - public void removeAgedItems(long latest, boolean notify) { - if (this.data.isEmpty()) { - return; // nothing to do - } - // find the serial index of the period specified by 'latest' - long index = Long.MAX_VALUE; - try { - Method m = RegularTimePeriod.class.getDeclaredMethod( - "createInstance", new Class[] {Class.class, Date.class, - TimeZone.class, Locale.class}); - RegularTimePeriod newest = (RegularTimePeriod) m.invoke( - this.timePeriodClass, new Object[] {this.timePeriodClass, - new Date(latest), TimeZone.getDefault(), Locale.getDefault()}); - index = newest.getSerialIndex(); - } - catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - - // check if there are any values earlier than specified by the history - // count... - boolean removed = false; - while (getItemCount() > 0 && (index - - getTimePeriod(0).getSerialIndex()) > this.maximumItemAge) { - this.data.remove(0); - removed = true; - } - if (removed) { - updateMinMaxYByIteration(); - if (notify) { - fireSeriesChanged(); - } - } - } - - /** - * Removes all data items from the series and sends a - * {@link SeriesChangeEvent} to all registered listeners. - */ - public void clear() { - if (this.data.size() > 0) { - this.data.clear(); - this.timePeriodClass = null; - this.minY = Double.NaN; - this.maxY = Double.NaN; - fireSeriesChanged(); - } - } - - /** - * Deletes the data item for the given time period and sends a - * {@link SeriesChangeEvent} to all registered listeners. If there is no - * item with the specified time period, this method does nothing. - * - * @param period the period of the item to delete ({@code null} not - * permitted). - */ - public void delete(RegularTimePeriod period) { - int index = getIndex(period); - if (index >= 0) { - TimeSeriesDataItem item = (TimeSeriesDataItem) this.data.remove( - index); - updateBoundsForRemovedItem(item); - if (this.data.isEmpty()) { - this.timePeriodClass = null; - } - fireSeriesChanged(); - } - } - - /** - * Deletes data from start until end index (end inclusive). - * - * @param start the index of the first period to delete. - * @param end the index of the last period to delete. - */ - public void delete(int start, int end) { - delete(start, end, true); - } - - /** - * Deletes data from start until end index (end inclusive). - * - * @param start the index of the first period to delete. - * @param end the index of the last period to delete. - * @param notify notify listeners? - */ - public void delete(int start, int end, boolean notify) { - if (end < start) { - throw new IllegalArgumentException("Requires start <= end."); - } - for (int i = 0; i <= (end - start); i++) { - this.data.remove(start); - } - updateMinMaxYByIteration(); - if (this.data.isEmpty()) { - this.timePeriodClass = null; - } - if (notify) { - fireSeriesChanged(); - } - } - - /** - * Returns a clone of the time series. - *

    - * Notes: - *

      - *
    • no need to clone the domain and range descriptions, since String - * object is immutable;
    • - *
    • we pass over to the more general method clone(start, end).
    • - *
    - * - * @return A clone of the time series. - * - * @throws CloneNotSupportedException not thrown by this class, but - * subclasses may differ. - */ - @Override - public Object clone() throws CloneNotSupportedException { - TimeSeries clone = (TimeSeries) super.clone(); - clone.data = (List) ObjectUtils.deepClone(this.data); - return clone; - } - - /** - * Creates a new timeseries by copying a subset of the data in this time - * series. - * - * @param start the index of the first time period to copy. - * @param end the index of the last time period to copy. - * - * @return A series containing a copy of this times series from start until - * end. - * - * @throws CloneNotSupportedException if there is a cloning problem. - */ - public TimeSeries createCopy(int start, int end) - throws CloneNotSupportedException { - if (start < 0) { - throw new IllegalArgumentException("Requires start >= 0."); - } - if (end < start) { - throw new IllegalArgumentException("Requires start <= end."); - } - TimeSeries copy = (TimeSeries) super.clone(); - copy.minY = Double.NaN; - copy.maxY = Double.NaN; - copy.data = new java.util.ArrayList(); - if (this.data.size() > 0) { - for (int index = start; index <= end; index++) { - TimeSeriesDataItem item - = (TimeSeriesDataItem) this.data.get(index); - TimeSeriesDataItem clone = (TimeSeriesDataItem) item.clone(); - try { - copy.add(clone); - } - catch (SeriesException e) { - throw new RuntimeException(e); - } - } - } - return copy; - } - - /** - * Creates a new timeseries by copying a subset of the data in this time - * series. - * - * @param start the first time period to copy ({@code null} not - * permitted). - * @param end the last time period to copy ({@code null} not permitted). - * - * @return A time series containing a copy of this time series from start - * until end. - * - * @throws CloneNotSupportedException if there is a cloning problem. - */ - public TimeSeries createCopy(RegularTimePeriod start, RegularTimePeriod end) - throws CloneNotSupportedException { - - Args.nullNotPermitted(start, "start"); - Args.nullNotPermitted(end, "end"); - if (start.compareTo(end) > 0) { - throw new IllegalArgumentException( - "Requires start on or before end."); - } - boolean emptyRange = false; - int startIndex = getIndex(start); - if (startIndex < 0) { - startIndex = -(startIndex + 1); - if (startIndex == this.data.size()) { - emptyRange = true; // start is after last data item - } - } - int endIndex = getIndex(end); - if (endIndex < 0) { // end period is not in original series - endIndex = -(endIndex + 1); // this is first item AFTER end period - endIndex = endIndex - 1; // so this is last item BEFORE end - } - if ((endIndex < 0) || (endIndex < startIndex)) { - emptyRange = true; - } - if (emptyRange) { - TimeSeries copy = (TimeSeries) super.clone(); - copy.data = new java.util.ArrayList(); - return copy; - } - return createCopy(startIndex, endIndex); - } - - /** - * Tests the series for equality with an arbitrary object. - * - * @param obj the object to test against ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof TimeSeries)) { - return false; - } - TimeSeries that = (TimeSeries) obj; - if (!Objects.equals(getDomainDescription(), - that.getDomainDescription())) { - return false; - } - if (!Objects.equals(getRangeDescription(), - that.getRangeDescription())) { - return false; - } - if (!Objects.equals(this.timePeriodClass, - that.timePeriodClass)) { - return false; - } - if (getMaximumItemAge() != that.getMaximumItemAge()) { - return false; - } - if (getMaximumItemCount() != that.getMaximumItemCount()) { - return false; - } - int count = getItemCount(); - if (count != that.getItemCount()) { - return false; - } - if (!Objects.equals(this.data, that.data)) { - return false; - } - return super.equals(obj); - } - - /** - * Returns a hash code value for the object. - * - * @return The hashcode - */ - @Override - public int hashCode() { - int result = super.hashCode(); - result = 29 * result + (this.domain != null ? this.domain.hashCode() - : 0); - result = 29 * result + (this.range != null ? this.range.hashCode() : 0); - result = 29 * result + (this.timePeriodClass != null - ? this.timePeriodClass.hashCode() : 0); - // it is too slow to look at every data item, so let's just look at - // the first, middle and last items... - int count = getItemCount(); - if (count > 0) { - TimeSeriesDataItem item = getRawDataItem(0); - result = 29 * result + item.hashCode(); - } - if (count > 1) { - TimeSeriesDataItem item = getRawDataItem(count - 1); - result = 29 * result + item.hashCode(); - } - if (count > 2) { - TimeSeriesDataItem item = getRawDataItem(count / 2); - result = 29 * result + item.hashCode(); - } - result = 29 * result + this.maximumItemCount; - result = 29 * result + (int) this.maximumItemAge; - return result; - } - - /** - * Updates the cached values for the minimum and maximum data values. - * - * @param item the item added ({@code null} not permitted). - */ - private void updateBoundsForAddedItem(TimeSeriesDataItem item) { - Number yN = item.getValue(); - if (item.getValue() != null) { - double y = yN.doubleValue(); - this.minY = minIgnoreNaN(this.minY, y); - this.maxY = maxIgnoreNaN(this.maxY, y); - } - } - - /** - * Updates the cached values for the minimum and maximum data values on - * the basis that the specified item has just been removed. - * - * @param item the item added ({@code null} not permitted). - */ - private void updateBoundsForRemovedItem(TimeSeriesDataItem item) { - Number yN = item.getValue(); - if (yN != null) { - double y = yN.doubleValue(); - if (!Double.isNaN(y)) { - if (y <= this.minY || y >= this.maxY) { - updateMinMaxYByIteration(); - } - } - } - } - - /** - * Finds the bounds of the x and y values for the series, by iterating - * through all the data items. - */ - private void updateMinMaxYByIteration() { - this.minY = Double.NaN; - this.maxY = Double.NaN; - Iterator iterator = this.data.iterator(); - while (iterator.hasNext()) { - TimeSeriesDataItem item = (TimeSeriesDataItem) iterator.next(); - updateBoundsForAddedItem(item); - } - } - - /** - * A function to find the minimum of two values, but ignoring any - * Double.NaN values. - * - * @param a the first value. - * @param b the second value. - * - * @return The minimum of the two values. - */ - private double minIgnoreNaN(double a, double b) { - if (Double.isNaN(a)) { - return b; - } - if (Double.isNaN(b)) { - return a; - } - return Math.min(a, b); - } - - /** - * A function to find the maximum of two values, but ignoring any - * Double.NaN values. - * - * @param a the first value. - * @param b the second value. - * - * @return The maximum of the two values. - */ - private double maxIgnoreNaN(double a, double b) { - if (Double.isNaN(a)) { - return b; - } - if (Double.isNaN(b)) { - return a; - } - else { - return Math.max(a, b); - } - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------------- + * TimeSeries.java + * --------------- + * (C) Copyright 2001-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): Bryan Scott; + * Nick Guenther; + * + */ + +package org.jfree.data.time; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.TimeZone; +import org.jfree.chart.util.ObjectUtils; + +import org.jfree.chart.util.Args; +import org.jfree.data.Range; +import org.jfree.data.general.Series; +import org.jfree.data.general.SeriesException; + +/** + * Represents a sequence of zero or more data items in the form (period, value) + * where 'period' is some instance of a subclass of {@link RegularTimePeriod}. + * The time series will ensure that (a) all data items have the same type of + * period (for example, {@link Day}) and (b) that each period appears at + * most one time in the series. + */ +public class TimeSeries extends Series implements Cloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = -5032960206869675528L; + + /** Default value for the domain description. */ + protected static final String DEFAULT_DOMAIN_DESCRIPTION = "Time"; + + /** Default value for the range description. */ + protected static final String DEFAULT_RANGE_DESCRIPTION = "Value"; + + /** A description of the domain. */ + private String domain; + + /** A description of the range. */ + private String range; + + /** The type of period for the data. */ + protected Class timePeriodClass; + + /** The list of data items in the series. */ + protected List data; + + /** The maximum number of items for the series. */ + private int maximumItemCount; + + /** + * The maximum age of items for the series, specified as a number of + * time periods. + */ + private long maximumItemAge; + + /** + * The minimum y-value in the series. + */ + private double minY; + + /** + * The maximum y-value in the series. + */ + private double maxY; + + /** + * Creates a new (empty) time series. By default, a daily time series is + * created. Use one of the other constructors if you require a different + * time period. + * + * @param name the series name ({@code null} not permitted). + */ + public TimeSeries(Comparable name) { + this(name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION); + } + + /** + * Creates a new time series that contains no data. + *

    + * Descriptions can be specified for the domain and range. One situation + * where this is helpful is when generating a chart for the time series - + * axis labels can be taken from the domain and range description. + * + * @param name the name of the series ({@code null} not permitted). + * @param domain the domain description ({@code null} permitted). + * @param range the range description ({@code null} permitted). + */ + public TimeSeries(Comparable name, String domain, String range) { + super(name); + this.domain = domain; + this.range = range; + this.timePeriodClass = null; + this.data = new java.util.ArrayList(); + this.maximumItemCount = Integer.MAX_VALUE; + this.maximumItemAge = Long.MAX_VALUE; + this.minY = Double.NaN; + this.maxY = Double.NaN; + } + + /** + * Returns the domain description. + * + * @return The domain description (possibly {@code null}). + * + * @see #setDomainDescription(String) + */ + public String getDomainDescription() { + return this.domain; + } + + /** + * Sets the domain description and sends a {@code PropertyChangeEvent} + * (with the property name {@code Domain}) to all registered + * property change listeners. + * + * @param description the description ({@code null} permitted). + * + * @see #getDomainDescription() + */ + public void setDomainDescription(String description) { + String old = this.domain; + this.domain = description; + firePropertyChange("Domain", old, description); + } + + /** + * Returns the range description. + * + * @return The range description (possibly {@code null}). + * + * @see #setRangeDescription(String) + */ + public String getRangeDescription() { + return this.range; + } + + /** + * Sets the range description and sends a {@code PropertyChangeEvent} + * (with the property name {@code Range}) to all registered listeners. + * + * @param description the description ({@code null} permitted). + * + * @see #getRangeDescription() + */ + public void setRangeDescription(String description) { + String old = this.range; + this.range = description; + firePropertyChange("Range", old, description); + } + + /** + * Returns the number of items in the series. + * + * @return The item count. + */ + @Override + public int getItemCount() { + return this.data.size(); + } + + /** + * Returns the list of data items for the series (the list contains + * {@link TimeSeriesDataItem} objects and is unmodifiable). + * + * @return The list of data items. + */ + public List getItems() { + // FIXME: perhaps we should clone the data list + return Collections.unmodifiableList(this.data); + } + + /** + * Returns the maximum number of items that will be retained in the series. + * The default value is {@code Integer.MAX_VALUE}. + * + * @return The maximum item count. + * + * @see #setMaximumItemCount(int) + */ + public int getMaximumItemCount() { + return this.maximumItemCount; + } + + /** + * Sets the maximum number of items that will be retained in the series. + * If you add a new item to the series such that the number of items will + * exceed the maximum item count, then the FIRST element in the series is + * automatically removed, ensuring that the maximum item count is not + * exceeded. + * + * @param maximum the maximum (requires >= 0). + * + * @see #getMaximumItemCount() + */ + public void setMaximumItemCount(int maximum) { + if (maximum < 0) { + throw new IllegalArgumentException("Negative 'maximum' argument."); + } + this.maximumItemCount = maximum; + int count = this.data.size(); + if (count > maximum) { + delete(0, count - maximum - 1); + } + } + + /** + * Returns the maximum item age (in time periods) for the series. + * + * @return The maximum item age. + * + * @see #setMaximumItemAge(long) + */ + public long getMaximumItemAge() { + return this.maximumItemAge; + } + + /** + * Sets the number of time units in the 'history' for the series. This + * provides one mechanism for automatically dropping old data from the + * time series. For example, if a series contains daily data, you might set + * the history count to 30. Then, when you add a new data item, all data + * items more than 30 days older than the latest value are automatically + * dropped from the series. + * + * @param periods the number of time periods. + * + * @see #getMaximumItemAge() + */ + public void setMaximumItemAge(long periods) { + if (periods < 0) { + throw new IllegalArgumentException("Negative 'periods' argument."); + } + this.maximumItemAge = periods; + removeAgedItems(true); // remove old items and notify if necessary + } + + /** + * Returns the range of y-values in the time series. Any {@code null} or + * {@code Double.NaN} data values in the series will be ignored (except for + * the special case where all data values are {@code null}, in which case + * the return value is {@code Range(Double.NaN, Double.NaN)}). If the time + * series contains no items, this method will return {@code null}. + * + * @return The range of y-values in the time series (possibly {@code null}). + */ + public Range findValueRange() { + if (this.data.isEmpty()) { + return null; + } + return new Range(this.minY, this.maxY); + } + + /** + * Returns the range of y-values in the time series that fall within + * the specified range of x-values. This is equivalent to + * {@code findValueRange(xRange, TimePeriodAnchor.MIDDLE, timeZone)}. + * + * @param xRange the subrange of x-values ({@code null} not permitted). + * @param timeZone the time zone used to convert x-values to time periods + * ({@code null} not permitted). + * + * @return The range. + */ + public Range findValueRange(Range xRange, TimeZone timeZone) { + return findValueRange(xRange, TimePeriodAnchor.MIDDLE, timeZone); + } + + /** + * Finds the range of y-values that fall within the specified range of + * x-values (where the x-values are interpreted as milliseconds since the + * epoch and converted to time periods using the specified timezone). + * + * @param xRange the subset of x-values to use ({@code null} not + * permitted). + * @param xAnchor the anchor point for the x-values ({@code null} + * not permitted). + * @param zone the time zone ({@code null} not permitted). + * + * @return The range of y-values. + */ + public Range findValueRange(Range xRange, TimePeriodAnchor xAnchor, + TimeZone zone) { + Args.nullNotPermitted(xRange, "xRange"); + Args.nullNotPermitted(xAnchor, "xAnchor"); + Args.nullNotPermitted(zone, "zone"); + if (this.data.isEmpty()) { + return null; + } + Calendar calendar = Calendar.getInstance(zone); + return findValueRange(xRange, xAnchor, calendar); + } + + /** + * Finds the range of y-values that fall within the specified range of + * x-values (where the x-values are interpreted as milliseconds since the + * epoch and converted to time periods using the specified calendar). + * + * @param xRange the subset of x-values to use ({@code null} not + * permitted). + * @param xAnchor the anchor point for the x-values ({@code null} + * not permitted). + * @param calendar the calendar ({@code null} not permitted). + * + * @return The range of y-values. + */ + public Range findValueRange(Range xRange, TimePeriodAnchor xAnchor, + Calendar calendar) { + // since the items are ordered, we could be more clever here and avoid + // iterating over all the data + double lowY = Double.POSITIVE_INFINITY; + double highY = Double.NEGATIVE_INFINITY; + for (int i = 0; i < this.data.size(); i++) { + TimeSeriesDataItem item = (TimeSeriesDataItem) this.data.get(i); + long millis = item.getPeriod().getMillisecond(xAnchor, calendar); + if (xRange.contains(millis)) { + Number n = item.getValue(); + if (n != null) { + double v = n.doubleValue(); + lowY = minIgnoreNaN(lowY, v); + highY = maxIgnoreNaN(highY, v); + } + } + } + if (Double.isInfinite(lowY) && Double.isInfinite(highY)) { + if (lowY < highY) { + return new Range(lowY, highY); + } else { + return new Range(Double.NaN, Double.NaN); + } + } + return new Range(lowY, highY); + } + + /** + * Returns the smallest y-value in the series, ignoring any + * {@code null} and {@code Double.NaN} values. This method + * returns {@code Double.NaN} if there is no smallest y-value (for + * example, when the series is empty). + * + * @return The smallest y-value. + * + * @see #getMaxY() + */ + public double getMinY() { + return this.minY; + } + + /** + * Returns the largest y-value in the series, ignoring any + * {@code null} and {@code Double.NaN} values. This method + * returns {@code Double.NaN} if there is no largest y-value + * (for example, when the series is empty). + * + * @return The largest y-value. + * + * @see #getMinY() + */ + public double getMaxY() { + return this.maxY; + } + + /** + * Returns the time period class for this series. + *

    + * Only one time period class can be used within a single series (enforced). + * If you add a data item with a {@link Year} for the time period, then all + * subsequent data items must also have a {@link Year} for the time period. + * + * @return The time period class (may be {@code null} but only for + * an empty series). + */ + public Class getTimePeriodClass() { + return this.timePeriodClass; + } + + /** + * Returns a data item from the dataset. Note that the returned object + * is a clone of the item in the series, so modifying it will have no + * effect on the data series. + * + * @param index the item index. + * + * @return The data item. + */ + public TimeSeriesDataItem getDataItem(int index) { + TimeSeriesDataItem item = (TimeSeriesDataItem) this.data.get(index); + return (TimeSeriesDataItem) item.clone(); + } + + /** + * Returns the data item for a specific period. Note that the returned + * object is a clone of the item in the series, so modifying it will have + * no effect on the data series. + * + * @param period the period of interest ({@code null} not allowed). + * + * @return The data item matching the specified period (or + * {@code null} if there is no match). + * + * @see #getDataItem(int) + */ + public TimeSeriesDataItem getDataItem(RegularTimePeriod period) { + int index = getIndex(period); + if (index >= 0) { + return getDataItem(index); + } + return null; + } + + /** + * Returns a data item for the series. This method returns the object + * that is used for the underlying storage - you should not modify the + * contents of the returned value unless you know what you are doing. + * + * @param index the item index (zero-based). + * + * @return The data item. + * + * @see #getDataItem(int) + */ + TimeSeriesDataItem getRawDataItem(int index) { + return (TimeSeriesDataItem) this.data.get(index); + } + + /** + * Returns a data item for the series. This method returns the object + * that is used for the underlying storage - you should not modify the + * contents of the returned value unless you know what you are doing. + * + * @param period the item index (zero-based). + * + * @return The data item. + * + * @see #getDataItem(RegularTimePeriod) + */ + TimeSeriesDataItem getRawDataItem(RegularTimePeriod period) { + int index = getIndex(period); + if (index >= 0) { + return (TimeSeriesDataItem) this.data.get(index); + } + return null; + } + + /** + * Returns the time period at the specified index. + * + * @param index the index of the data item. + * + * @return The time period. + */ + public RegularTimePeriod getTimePeriod(int index) { + return getRawDataItem(index).getPeriod(); + } + + /** + * Returns a time period that would be the next in sequence on the end of + * the time series. + * + * @return The next time period. + */ + public RegularTimePeriod getNextTimePeriod() { + RegularTimePeriod last = getTimePeriod(getItemCount() - 1); + return last.next(); + } + + /** + * Returns a collection of all the time periods in the time series. + * + * @return A collection of all the time periods. + */ + public Collection getTimePeriods() { + Collection result = new java.util.ArrayList(); + for (int i = 0; i < getItemCount(); i++) { + result.add(getTimePeriod(i)); + } + return result; + } + + /** + * Returns a collection of time periods in the specified series, but not in + * this series, and therefore unique to the specified series. + * + * @param series the series to check against this one. + * + * @return The unique time periods. + */ + public Collection getTimePeriodsUniqueToOtherSeries(TimeSeries series) { + Collection result = new java.util.ArrayList(); + for (int i = 0; i < series.getItemCount(); i++) { + RegularTimePeriod period = series.getTimePeriod(i); + int index = getIndex(period); + if (index < 0) { + result.add(period); + } + } + return result; + } + + /** + * Returns the index for the item (if any) that corresponds to a time + * period. + * + * @param period the time period ({@code null} not permitted). + * + * @return The index. + */ + public int getIndex(RegularTimePeriod period) { + Args.nullNotPermitted(period, "period"); + TimeSeriesDataItem dummy = new TimeSeriesDataItem( + period, Integer.MIN_VALUE); + return Collections.binarySearch(this.data, dummy); + } + + /** + * Returns the value at the specified index. + * + * @param index index of a value. + * + * @return The value (possibly {@code null}). + */ + public Number getValue(int index) { + return getRawDataItem(index).getValue(); + } + + /** + * Returns the value for a time period. If there is no data item with the + * specified period, this method will return {@code null}. + * + * @param period time period ({@code null} not permitted). + * + * @return The value (possibly {@code null}). + */ + public Number getValue(RegularTimePeriod period) { + int index = getIndex(period); + if (index >= 0) { + return getValue(index); + } + return null; + } + + /** + * Adds a data item to the series by calling + * {@link #add(org.jfree.data.time.TimeSeriesDataItem, boolean)}. + * + * @param item the (timeperiod, value) pair ({@code null} not permitted). + */ + public void add(TimeSeriesDataItem item) { + add(item, true); + } + + /** + * Adds a data item to the series and calls {@link #fireSeriesChanged()}. + * + * @param item the (timeperiod, value) pair ({@code null} not permitted). + * @param notify notify listeners? + */ + public void add(TimeSeriesDataItem item, boolean notify) { + Args.nullNotPermitted(item, "item"); + item = (TimeSeriesDataItem) item.clone(); + Class c = item.getPeriod().getClass(); + if (this.timePeriodClass == null) { + this.timePeriodClass = c; + } + else if (!this.timePeriodClass.equals(c)) { + StringBuilder b = new StringBuilder(); + b.append("You are trying to add data where the time period class "); + b.append("is "); + b.append(item.getPeriod().getClass().getName()); + b.append(", but the TimeSeries is expecting an instance of "); + b.append(this.timePeriodClass.getName()); + b.append("."); + throw new SeriesException(b.toString()); + } + + // make the change (if it's not a duplicate time period)... + boolean added = false; + int count = getItemCount(); + if (count == 0) { + this.data.add(item); + added = true; + } + else { + RegularTimePeriod last = getTimePeriod(getItemCount() - 1); + if (item.getPeriod().compareTo(last) > 0) { + this.data.add(item); + added = true; + } + else { + int index = Collections.binarySearch(this.data, item); + if (index < 0) { + this.data.add(-index - 1, item); + added = true; + } + else { + StringBuilder b = new StringBuilder(); + b.append("You are attempting to add an observation for "); + b.append("the time period "); + b.append(item.getPeriod().toString()); + b.append(" but the series already contains an observation"); + b.append(" for that time period. Duplicates are not "); + b.append("permitted. Try using the addOrUpdate() method."); + throw new SeriesException(b.toString()); + } + } + } + if (added) { + updateBoundsForAddedItem(item); + // check if this addition will exceed the maximum item count... + if (getItemCount() > this.maximumItemCount) { + TimeSeriesDataItem d = (TimeSeriesDataItem) this.data.remove(0); + updateBoundsForRemovedItem(d); + } + + removeAgedItems(false); // remove old items if necessary, but + // don't notify anyone, because that + // happens next anyway... + if (notify) { + fireSeriesChanged(); + } + } + + } + + /** + * Adds a new data item to the series and calls + * {@link #add(org.jfree.data.time.RegularTimePeriod, double, boolean)}. + * + * @param period the time period ({@code null} not permitted). + * @param value the value. + */ + public void add(RegularTimePeriod period, double value) { + // defer argument checking... + add(period, value, true); + } + + /** + * Adds a new data item to the series and calls + * {@link #add(org.jfree.data.time.TimeSeriesDataItem, boolean)}. + * + * @param period the time period ({@code null} not permitted). + * @param value the value. + * @param notify notify listeners? + */ + public void add(RegularTimePeriod period, double value, boolean notify) { + // defer argument checking... + TimeSeriesDataItem item = new TimeSeriesDataItem(period, value); + add(item, notify); + } + + /** + * Adds a new data item to the series and sends + * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered + * listeners. + * + * @param period the time period ({@code null} not permitted). + * @param value the value ({@code null} permitted). + */ + public void add(RegularTimePeriod period, Number value) { + // defer argument checking... + add(period, value, true); + } + + /** + * Adds a new data item to the series by calling + * {@link #add(org.jfree.data.time.TimeSeriesDataItem, boolean)}. + * + * @param period the time period ({@code null} not permitted). + * @param value the value ({@code null} permitted). + * @param notify notify listeners? + */ + public void add(RegularTimePeriod period, Number value, boolean notify) { + // defer argument checking... + TimeSeriesDataItem item = new TimeSeriesDataItem(period, value); + add(item, notify); + } + + /** + * Updates (changes) the value for a time period. Throws a + * {@link SeriesException} if the period does not exist. + * + * @param period the period ({@code null} not permitted). + * @param value the value. + */ + public void update(RegularTimePeriod period, double value) { + update(period, Double.valueOf(value)); + } + + /** + * Updates (changes) the value for a time period. Throws a + * {@link SeriesException} if the period does not exist. + * + * @param period the period ({@code null} not permitted). + * @param value the value ({@code null} permitted). + */ + public void update(RegularTimePeriod period, Number value) { + TimeSeriesDataItem temp = new TimeSeriesDataItem(period, value); + int index = Collections.binarySearch(this.data, temp); + if (index < 0) { + throw new SeriesException("There is no existing value for the " + + "specified 'period'."); + } + update(index, value); + } + + /** + * Updates (changes) the value of a data item. + * + * @param index the index of the data item. + * @param value the new value ({@code null} permitted). + */ + public void update(int index, Number value) { + TimeSeriesDataItem item = (TimeSeriesDataItem) this.data.get(index); + boolean iterate = false; + Number oldYN = item.getValue(); + if (oldYN != null) { + double oldY = oldYN.doubleValue(); + if (!Double.isNaN(oldY)) { + iterate = oldY <= this.minY || oldY >= this.maxY; + } + } + item.setValue(value); + if (iterate) { + updateMinMaxYByIteration(); + } + else if (value != null) { + double yy = value.doubleValue(); + this.minY = minIgnoreNaN(this.minY, yy); + this.maxY = maxIgnoreNaN(this.maxY, yy); + } + fireSeriesChanged(); + } + + /** + * Adds or updates data from one series to another. Returns another series + * containing the values that were overwritten. + * + * @param series the series to merge with this. + * + * @return A series containing the values that were overwritten. + */ + public TimeSeries addAndOrUpdate(TimeSeries series) { + TimeSeries overwritten = new TimeSeries("Overwritten values from: " + + getKey()); + for (int i = 0; i < series.getItemCount(); i++) { + TimeSeriesDataItem item = series.getRawDataItem(i); + TimeSeriesDataItem oldItem = addOrUpdate(item.getPeriod(), + item.getValue()); + if (oldItem != null) { + overwritten.add(oldItem); + } + } + return overwritten; + } + + /** + * Adds or updates an item in the times series by calling + * {@link #addOrUpdate(org.jfree.data.time.RegularTimePeriod, double)}. + * + * @param period the time period to add/update ({@code null} not + * permitted). + * @param value the new value. + * + * @return A copy of the overwritten data item, or {@code null} if no + * item was overwritten. + */ + public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period, + double value) { + return addOrUpdate(period, Double.valueOf(value)); + } + + /** + * Adds or updates an item in the times series by calling + * {@link #addOrUpdate(org.jfree.data.time.TimeSeriesDataItem)}. + * + * @param period the time period to add/update ({@code null} not + * permitted). + * @param value the new value ({@code null} permitted). + * + * @return A copy of the overwritten data item, or {@code null} if no + * item was overwritten. + */ + public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period, + Number value) { + return addOrUpdate(new TimeSeriesDataItem(period, value)); + } + + /** + * Adds or updates an item in the times series and calls + * {@link #fireSeriesChanged()}. + * + * @param item the data item ({@code null} not permitted). + * + * @return A copy of the overwritten data item, or {@code null} if no + * item was overwritten. + */ + public TimeSeriesDataItem addOrUpdate(TimeSeriesDataItem item) { + + Args.nullNotPermitted(item, "item"); + Class periodClass = item.getPeriod().getClass(); + if (this.timePeriodClass == null) { + this.timePeriodClass = periodClass; + } + else if (!this.timePeriodClass.equals(periodClass)) { + String msg = "You are trying to add data where the time " + + "period class is " + periodClass.getName() + + ", but the TimeSeries is expecting an instance of " + + this.timePeriodClass.getName() + "."; + throw new SeriesException(msg); + } + TimeSeriesDataItem overwritten = null; + int index = Collections.binarySearch(this.data, item); + if (index >= 0) { + TimeSeriesDataItem existing + = (TimeSeriesDataItem) this.data.get(index); + overwritten = (TimeSeriesDataItem) existing.clone(); + // figure out if we need to iterate through all the y-values + // to find the revised minY / maxY + boolean iterate = false; + Number oldYN = existing.getValue(); + double oldY = oldYN != null ? oldYN.doubleValue() : Double.NaN; + if (!Double.isNaN(oldY)) { + iterate = oldY <= this.minY || oldY >= this.maxY; + } + existing.setValue(item.getValue()); + if (iterate) { + updateMinMaxYByIteration(); + } + else if (item.getValue() != null) { + double yy = item.getValue().doubleValue(); + this.minY = minIgnoreNaN(this.minY, yy); + this.maxY = maxIgnoreNaN(this.maxY, yy); + } + } + else { + item = (TimeSeriesDataItem) item.clone(); + this.data.add(-index - 1, item); + updateBoundsForAddedItem(item); + + // check if this addition will exceed the maximum item count... + if (getItemCount() > this.maximumItemCount) { + TimeSeriesDataItem d = (TimeSeriesDataItem) this.data.remove(0); + updateBoundsForRemovedItem(d); + } + } + removeAgedItems(false); // remove old items if necessary, but + // don't notify anyone, because that + // happens next anyway... + fireSeriesChanged(); + return overwritten; + + } + + /** + * Age items in the series. Ensure that the timespan from the youngest to + * the oldest record in the series does not exceed maximumItemAge time + * periods. Oldest items will be removed if required. + * + * @param notify controls whether or not {@link #fireSeriesChanged()} is + * called IF any items are removed. + */ + public void removeAgedItems(boolean notify) { + // check if there are any values earlier than specified by the history + // count... + if (getItemCount() > 1) { + long latest = getTimePeriod(getItemCount() - 1).getSerialIndex(); + boolean removed = false; + while ((latest - getTimePeriod(0).getSerialIndex()) + > this.maximumItemAge) { + this.data.remove(0); + removed = true; + } + if (removed) { + updateMinMaxYByIteration(); + if (notify) { + fireSeriesChanged(); + } + } + } + } + + /** + * Age items in the series. Ensure that the timespan from the supplied + * time to the oldest record in the series does not exceed history count. + * oldest items will be removed if required. + * + * @param latest the time to be compared against when aging data (specified + * in milliseconds). + * @param notify controls whether or not {@link #fireSeriesChanged()} is + * called IF any items are removed. + */ + public void removeAgedItems(long latest, boolean notify) { + if (this.data.isEmpty()) { + return; // nothing to do + } + // find the serial index of the period specified by 'latest' + long index = Long.MAX_VALUE; + try { + Method m = RegularTimePeriod.class.getDeclaredMethod( + "createInstance", new Class[] {Class.class, Date.class, + TimeZone.class, Locale.class}); + RegularTimePeriod newest = (RegularTimePeriod) m.invoke( + this.timePeriodClass, new Object[] {this.timePeriodClass, + new Date(latest), TimeZone.getDefault(), Locale.getDefault()}); + index = newest.getSerialIndex(); + } + catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + + // check if there are any values earlier than specified by the history + // count... + boolean removed = false; + while (getItemCount() > 0 && (index + - getTimePeriod(0).getSerialIndex()) > this.maximumItemAge) { + this.data.remove(0); + removed = true; + } + if (removed) { + updateMinMaxYByIteration(); + if (notify) { + fireSeriesChanged(); + } + } + } + + /** + * Removes all data items from the series and calls + * {@link #fireSeriesChanged()}. + */ + public void clear() { + if (this.data.size() > 0) { + this.data.clear(); + this.timePeriodClass = null; + this.minY = Double.NaN; + this.maxY = Double.NaN; + fireSeriesChanged(); + } + } + + /** + * Deletes the data item for the given time period and calls + * {@link #fireSeriesChanged()}. If there is no item with the specified time + * period, this method does nothing. + * + * @param period the period of the item to delete ({@code null} not + * permitted). + */ + public void delete(RegularTimePeriod period) { + int index = getIndex(period); + if (index >= 0) { + TimeSeriesDataItem item = (TimeSeriesDataItem) this.data.remove( + index); + updateBoundsForRemovedItem(item); + if (this.data.isEmpty()) { + this.timePeriodClass = null; + } + fireSeriesChanged(); + } + } + + /** + * Deletes data from start until end index (end inclusive). + * + * @param start the index of the first period to delete. + * @param end the index of the last period to delete. + */ + public void delete(int start, int end) { + delete(start, end, true); + } + + /** + * Deletes data from start until end index (end inclusive). + * + * @param start the index of the first period to delete. + * @param end the index of the last period to delete. + * @param notify notify listeners? + */ + public void delete(int start, int end, boolean notify) { + if (end < start) { + throw new IllegalArgumentException("Requires start <= end."); + } + for (int i = 0; i <= (end - start); i++) { + this.data.remove(start); + } + updateMinMaxYByIteration(); + if (this.data.isEmpty()) { + this.timePeriodClass = null; + } + if (notify) { + fireSeriesChanged(); + } + } + + /** + * Returns a clone of the time series. + *

    + * Notes: + *

      + *
    • no need to clone the domain and range descriptions, since String + * object is immutable;
    • + *
    • we pass over to the more general method clone(start, end).
    • + *
    + * + * @return A clone of the time series. + * + * @throws CloneNotSupportedException not thrown by this class, but + * subclasses may differ. + */ + @Override + public Object clone() throws CloneNotSupportedException { + TimeSeries clone = (TimeSeries) super.clone(); + clone.data = (List) ObjectUtils.deepClone(this.data); + return clone; + } + + /** + * Creates a new timeseries by copying a subset of the data in this time + * series. + * + * @param start the index of the first time period to copy. + * @param end the index of the last time period to copy. + * + * @return A series containing a copy of this times series from start until + * end. + * + * @throws CloneNotSupportedException if there is a cloning problem. + */ + public TimeSeries createCopy(int start, int end) + throws CloneNotSupportedException { + if (start < 0) { + throw new IllegalArgumentException("Requires start >= 0."); + } + if (end < start) { + throw new IllegalArgumentException("Requires start <= end."); + } + TimeSeries copy = (TimeSeries) super.clone(); + copy.minY = Double.NaN; + copy.maxY = Double.NaN; + copy.data = new java.util.ArrayList(); + if (this.data.size() > 0) { + for (int index = start; index <= end; index++) { + TimeSeriesDataItem item + = (TimeSeriesDataItem) this.data.get(index); + TimeSeriesDataItem clone = (TimeSeriesDataItem) item.clone(); + try { + copy.add(clone); + } + catch (SeriesException e) { + throw new RuntimeException(e); + } + } + } + return copy; + } + + /** + * Creates a new timeseries by copying a subset of the data in this time + * series. + * + * @param start the first time period to copy ({@code null} not + * permitted). + * @param end the last time period to copy ({@code null} not permitted). + * + * @return A time series containing a copy of this time series from start + * until end. + * + * @throws CloneNotSupportedException if there is a cloning problem. + */ + public TimeSeries createCopy(RegularTimePeriod start, RegularTimePeriod end) + throws CloneNotSupportedException { + + Args.nullNotPermitted(start, "start"); + Args.nullNotPermitted(end, "end"); + if (start.compareTo(end) > 0) { + throw new IllegalArgumentException( + "Requires start on or before end."); + } + boolean emptyRange = false; + int startIndex = getIndex(start); + if (startIndex < 0) { + startIndex = -(startIndex + 1); + if (startIndex == this.data.size()) { + emptyRange = true; // start is after last data item + } + } + int endIndex = getIndex(end); + if (endIndex < 0) { // end period is not in original series + endIndex = -(endIndex + 1); // this is first item AFTER end period + endIndex = endIndex - 1; // so this is last item BEFORE end + } + if ((endIndex < 0) || (endIndex < startIndex)) { + emptyRange = true; + } + if (emptyRange) { + TimeSeries copy = (TimeSeries) super.clone(); + copy.data = new java.util.ArrayList(); + return copy; + } + return createCopy(startIndex, endIndex); + } + + /** + * Tests the series for equality with an arbitrary object. + * + * @param obj the object to test against ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof TimeSeries)) { + return false; + } + TimeSeries that = (TimeSeries) obj; + if (!Objects.equals(getDomainDescription(), + that.getDomainDescription())) { + return false; + } + if (!Objects.equals(getRangeDescription(), + that.getRangeDescription())) { + return false; + } + if (!Objects.equals(this.timePeriodClass, + that.timePeriodClass)) { + return false; + } + if (getMaximumItemAge() != that.getMaximumItemAge()) { + return false; + } + if (getMaximumItemCount() != that.getMaximumItemCount()) { + return false; + } + int count = getItemCount(); + if (count != that.getItemCount()) { + return false; + } + if (!Objects.equals(this.data, that.data)) { + return false; + } + return super.equals(obj); + } + + /** + * Returns a hash code value for the object. + * + * @return The hashcode + */ + @Override + public int hashCode() { + int result = super.hashCode(); + result = 29 * result + (this.domain != null ? this.domain.hashCode() + : 0); + result = 29 * result + (this.range != null ? this.range.hashCode() : 0); + result = 29 * result + (this.timePeriodClass != null + ? this.timePeriodClass.hashCode() : 0); + // it is too slow to look at every data item, so let's just look at + // the first, middle and last items... + int count = getItemCount(); + if (count > 0) { + TimeSeriesDataItem item = getRawDataItem(0); + result = 29 * result + item.hashCode(); + } + if (count > 1) { + TimeSeriesDataItem item = getRawDataItem(count - 1); + result = 29 * result + item.hashCode(); + } + if (count > 2) { + TimeSeriesDataItem item = getRawDataItem(count / 2); + result = 29 * result + item.hashCode(); + } + result = 29 * result + this.maximumItemCount; + result = 29 * result + (int) this.maximumItemAge; + return result; + } + + /** + * Updates the cached values for the minimum and maximum data values. + * + * @param item the item added ({@code null} not permitted). + */ + private void updateBoundsForAddedItem(TimeSeriesDataItem item) { + Number yN = item.getValue(); + if (item.getValue() != null) { + double y = yN.doubleValue(); + this.minY = minIgnoreNaN(this.minY, y); + this.maxY = maxIgnoreNaN(this.maxY, y); + } + } + + /** + * Updates the cached values for the minimum and maximum data values on + * the basis that the specified item has just been removed. + * + * @param item the item added ({@code null} not permitted). + */ + private void updateBoundsForRemovedItem(TimeSeriesDataItem item) { + Number yN = item.getValue(); + if (yN != null) { + double y = yN.doubleValue(); + if (!Double.isNaN(y)) { + if (y <= this.minY || y >= this.maxY) { + updateMinMaxYByIteration(); + } + } + } + } + + /** + * Finds the bounds of the x and y values for the series, by iterating + * through all the data items. + */ + private void updateMinMaxYByIteration() { + this.minY = Double.NaN; + this.maxY = Double.NaN; + Iterator iterator = this.data.iterator(); + while (iterator.hasNext()) { + TimeSeriesDataItem item = (TimeSeriesDataItem) iterator.next(); + updateBoundsForAddedItem(item); + } + } + + /** + * A function to find the minimum of two values, but ignoring any + * Double.NaN values. + * + * @param a the first value. + * @param b the second value. + * + * @return The minimum of the two values. + */ + private double minIgnoreNaN(double a, double b) { + if (Double.isNaN(a)) { + return b; + } + if (Double.isNaN(b)) { + return a; + } + return Math.min(a, b); + } + + /** + * A function to find the maximum of two values, but ignoring any + * Double.NaN values. + * + * @param a the first value. + * @param b the second value. + * + * @return The maximum of the two values. + */ + private double maxIgnoreNaN(double a, double b) { + if (Double.isNaN(a)) { + return b; + } + if (Double.isNaN(b)) { + return a; + } + else { + return Math.max(a, b); + } + } + +} diff --git a/src/main/java/org/jfree/data/time/ohlc/OHLCSeriesCollection.java b/src/main/java/org/jfree/data/time/ohlc/OHLCSeriesCollection.java index 8f6936d27..8458d4808 100644 --- a/src/main/java/org/jfree/data/time/ohlc/OHLCSeriesCollection.java +++ b/src/main/java/org/jfree/data/time/ohlc/OHLCSeriesCollection.java @@ -48,7 +48,6 @@ import org.jfree.data.time.TimePeriodAnchor; import org.jfree.data.xy.AbstractXYDataset; import org.jfree.data.xy.OHLCDataset; -import org.jfree.data.xy.XYDataset; /** * A collection of {@link OHLCSeries} objects. @@ -72,7 +71,7 @@ public OHLCSeriesCollection() { /** * Returns the position within each time period that is used for the X - * value when the collection is used as an {@link XYDataset}. + * value when the collection is used as an {@link org.jfree.data.xy.XYDataset}. * * @return The anchor position (never {@code null}). */ @@ -82,7 +81,7 @@ public TimePeriodAnchor getXPosition() { /** * Sets the position within each time period that is used for the X values - * when the collection is used as an {@link XYDataset}, then sends a + * when the collection is used as an {@link org.jfree.data.xy.XYDataset}, then sends a * {@link DatasetChangeEvent} is sent to all registered listeners. * * @param anchor the anchor position ({@code null} not permitted). diff --git a/src/main/java/org/jfree/data/xy/CategoryTableXYDataset.java b/src/main/java/org/jfree/data/xy/CategoryTableXYDataset.java index 1ef675bb3..d52226af3 100644 --- a/src/main/java/org/jfree/data/xy/CategoryTableXYDataset.java +++ b/src/main/java/org/jfree/data/xy/CategoryTableXYDataset.java @@ -1,414 +1,411 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------------------------- - * CategoryTableXYDataset.java - * --------------------------- - * (C) Copyright 2004-present, by Andreas Schroeder and Contributors. - * - * Original Author: Andreas Schroeder; - * Contributor(s): David Gilbert; - * - */ - -package org.jfree.data.xy; - -import org.jfree.chart.util.PublicCloneable; -import org.jfree.data.DefaultKeyedValues2D; -import org.jfree.data.DomainInfo; -import org.jfree.data.Range; -import org.jfree.data.general.DatasetChangeEvent; -import org.jfree.data.general.DatasetUtils; - -/** - * An implementation variant of the {@link TableXYDataset} where every series - * shares the same x-values (required for generating stacked area charts). - * This implementation uses a {@link DefaultKeyedValues2D} Object as backend - * implementation and is hence more "category oriented" than the {@link - * DefaultTableXYDataset} implementation. - *

    - * This implementation provides no means to remove data items yet. - * This is due to the lack of such facility in the DefaultKeyedValues2D class. - *

    - * This class also implements the {@link IntervalXYDataset} interface, but this - * implementation is provisional. - */ -public class CategoryTableXYDataset extends AbstractIntervalXYDataset - implements TableXYDataset, IntervalXYDataset, DomainInfo, - PublicCloneable { - - /** - * The backing data structure. - */ - private DefaultKeyedValues2D values; - - /** A delegate for controlling the interval width. */ - private IntervalXYDelegate intervalDelegate; - - /** - * Creates a new empty CategoryTableXYDataset. - */ - public CategoryTableXYDataset() { - this.values = new DefaultKeyedValues2D(true); - this.intervalDelegate = new IntervalXYDelegate(this); - addChangeListener(this.intervalDelegate); - } - - /** - * Adds a data item to this dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. - * - * @param x the x value. - * @param y the y value. - * @param seriesName the name of the series to add the data item. - */ - public void add(double x, double y, String seriesName) { - add(x, y, seriesName, true); - } - - /** - * Adds a data item to this dataset and, if requested, sends a - * {@link DatasetChangeEvent} to all registered listeners. - * - * @param x the x value. - * @param y the y value. - * @param seriesName the name of the series to add the data item. - * @param notify notify listeners? - */ - public void add(Number x, Number y, String seriesName, boolean notify) { - this.values.addValue(y, (Comparable) x, seriesName); - if (notify) { - fireDatasetChanged(); - } - } - - /** - * Removes a value from the dataset. - * - * @param x the x-value. - * @param seriesName the series name. - */ - public void remove(double x, String seriesName) { - remove(x, seriesName, true); - } - - /** - * Removes an item from the dataset. - * - * @param x the x-value. - * @param seriesName the series name. - * @param notify notify listeners? - */ - public void remove(Number x, String seriesName, boolean notify) { - this.values.removeValue((Comparable) x, seriesName); - if (notify) { - fireDatasetChanged(); - } - } - - /** - * Clears all data from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. - */ - public void clear() { - this.values.clear(); - fireDatasetChanged(); - } - - /** - * Returns the number of series in the collection. - * - * @return The series count. - */ - @Override - public int getSeriesCount() { - return this.values.getColumnCount(); - } - - /** - * Returns the key for a series. - * - * @param series the series index (zero-based). - * - * @return The key for a series. - */ - @Override - public Comparable getSeriesKey(int series) { - return this.values.getColumnKey(series); - } - - /** - * Returns the number of x values in the dataset. - * - * @return The item count. - */ - @Override - public int getItemCount() { - return this.values.getRowCount(); - } - - /** - * Returns the number of items in the specified series. - * Returns the same as {@link CategoryTableXYDataset#getItemCount()}. - * - * @param series the series index (zero-based). - * - * @return The item count. - */ - @Override - public int getItemCount(int series) { - return getItemCount(); // all series have the same number of items in - // this dataset - } - - /** - * Returns the x-value for the specified series and item. - * - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * - * @return The value. - */ - @Override - public Number getX(int series, int item) { - return (Number) this.values.getRowKey(item); - } - - /** - * Returns the starting X value for the specified series and item. - * - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * - * @return The starting X value. - */ - @Override - public Number getStartX(int series, int item) { - return this.intervalDelegate.getStartX(series, item); - } - - /** - * Returns the ending X value for the specified series and item. - * - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * - * @return The ending X value. - */ - @Override - public Number getEndX(int series, int item) { - return this.intervalDelegate.getEndX(series, item); - } - - /** - * Returns the y-value for the specified series and item. - * - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * - * @return The y value (possibly {@code null}). - */ - @Override - public Number getY(int series, int item) { - return this.values.getValue(item, series); - } - - /** - * Returns the starting Y value for the specified series and item. - * - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * - * @return The starting Y value. - */ - @Override - public Number getStartY(int series, int item) { - return getY(series, item); - } - - /** - * Returns the ending Y value for the specified series and item. - * - * @param series the series index (zero-based). - * @param item the item index (zero-based). - * - * @return The ending Y value. - */ - @Override - public Number getEndY(int series, int item) { - return getY(series, item); - } - - /** - * Returns the minimum x-value in the dataset. - * - * @param includeInterval a flag that determines whether or not the - * x-interval is taken into account. - * - * @return The minimum value. - */ - @Override - public double getDomainLowerBound(boolean includeInterval) { - return this.intervalDelegate.getDomainLowerBound(includeInterval); - } - - /** - * Returns the maximum x-value in the dataset. - * - * @param includeInterval a flag that determines whether or not the - * x-interval is taken into account. - * - * @return The maximum value. - */ - @Override - public double getDomainUpperBound(boolean includeInterval) { - return this.intervalDelegate.getDomainUpperBound(includeInterval); - } - - /** - * Returns the range of the values in this dataset's domain. - * - * @param includeInterval a flag that determines whether or not the - * x-interval is taken into account. - * - * @return The range. - */ - @Override - public Range getDomainBounds(boolean includeInterval) { - if (includeInterval) { - return this.intervalDelegate.getDomainBounds(includeInterval); - } - else { - return DatasetUtils.iterateDomainBounds(this, includeInterval); - } - } - - /** - * Returns the interval position factor. - * - * @return The interval position factor. - */ - public double getIntervalPositionFactor() { - return this.intervalDelegate.getIntervalPositionFactor(); - } - - /** - * Sets the interval position factor. Must be between 0.0 and 1.0 inclusive. - * If the factor is 0.5, the gap is in the middle of the x values. If it - * is lesser than 0.5, the gap is farther to the left and if greater than - * 0.5 it gets farther to the right. - * - * @param d the new interval position factor. - */ - public void setIntervalPositionFactor(double d) { - this.intervalDelegate.setIntervalPositionFactor(d); - fireDatasetChanged(); - } - - /** - * Returns the full interval width. - * - * @return The interval width to use. - */ - public double getIntervalWidth() { - return this.intervalDelegate.getIntervalWidth(); - } - - /** - * Sets the interval width to a fixed value, and sends a - * {@link DatasetChangeEvent} to all registered listeners. - * - * @param d the new interval width (must be > 0). - */ - public void setIntervalWidth(double d) { - this.intervalDelegate.setFixedIntervalWidth(d); - fireDatasetChanged(); - } - - /** - * Returns whether the interval width is automatically calculated or not. - * - * @return whether the width is automatically calculated or not. - */ - public boolean isAutoWidth() { - return this.intervalDelegate.isAutoWidth(); - } - - /** - * Sets the flag that indicates whether the interval width is automatically - * calculated or not. - * - * @param b the flag. - */ - public void setAutoWidth(boolean b) { - this.intervalDelegate.setAutoWidth(b); - fireDatasetChanged(); - } - - /** - * Tests this dataset for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (!(obj instanceof CategoryTableXYDataset)) { - return false; - } - CategoryTableXYDataset that = (CategoryTableXYDataset) obj; - if (!this.intervalDelegate.equals(that.intervalDelegate)) { - return false; - } - if (!this.values.equals(that.values)) { - return false; - } - return true; - } - - /** - * Returns an independent copy of this dataset. - * - * @return A clone. - * - * @throws CloneNotSupportedException if there is some reason that cloning - * cannot be performed. - */ - @Override - public Object clone() throws CloneNotSupportedException { - CategoryTableXYDataset clone = (CategoryTableXYDataset) super.clone(); - clone.values = (DefaultKeyedValues2D) this.values.clone(); - clone.intervalDelegate = new IntervalXYDelegate(clone); - // need to configure the intervalDelegate to match the original - clone.intervalDelegate.setFixedIntervalWidth(getIntervalWidth()); - clone.intervalDelegate.setAutoWidth(isAutoWidth()); - clone.intervalDelegate.setIntervalPositionFactor( - getIntervalPositionFactor()); - return clone; - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------------------------- + * CategoryTableXYDataset.java + * --------------------------- + * (C) Copyright 2004-present, by Andreas Schroeder and Contributors. + * + * Original Author: Andreas Schroeder; + * Contributor(s): David Gilbert; + * + */ + +package org.jfree.data.xy; + +import org.jfree.chart.util.PublicCloneable; +import org.jfree.data.DefaultKeyedValues2D; +import org.jfree.data.DomainInfo; +import org.jfree.data.Range; +import org.jfree.data.general.DatasetUtils; + +/** + * An implementation variant of the {@link TableXYDataset} where every series + * shares the same x-values (required for generating stacked area charts). + * This implementation uses a {@link DefaultKeyedValues2D} Object as backend + * implementation and is hence more "category oriented" than the {@link + * DefaultTableXYDataset} implementation. + *

    + * This implementation provides no means to remove data items yet. + * This is due to the lack of such facility in the DefaultKeyedValues2D class. + *

    + * This class also implements the {@link IntervalXYDataset} interface, but this + * implementation is provisional. + */ +public class CategoryTableXYDataset extends AbstractIntervalXYDataset + implements TableXYDataset, IntervalXYDataset, DomainInfo, + PublicCloneable { + + /** + * The backing data structure. + */ + private DefaultKeyedValues2D values; + + /** A delegate for controlling the interval width. */ + private IntervalXYDelegate intervalDelegate; + + /** + * Creates a new empty CategoryTableXYDataset. + */ + public CategoryTableXYDataset() { + this.values = new DefaultKeyedValues2D(true); + this.intervalDelegate = new IntervalXYDelegate(this); + addChangeListener(this.intervalDelegate); + } + + /** + * Adds a data item to this dataset by calling + * {@link #add(java.lang.Number, java.lang.Number, java.lang.String, boolean)}. + * + * @param x the x value. + * @param y the y value. + * @param seriesName the name of the series to add the data item. + */ + public void add(double x, double y, String seriesName) { + add(x, y, seriesName, true); + } + + /** + * Adds a data item to this dataset and, if requested, calls + * {@link #fireDatasetChanged()}. + * + * @param x the x value. + * @param y the y value. + * @param seriesName the name of the series to add the data item. + * @param notify notify listeners? + */ + public void add(Number x, Number y, String seriesName, boolean notify) { + this.values.addValue(y, (Comparable) x, seriesName); + if (notify) { + fireDatasetChanged(); + } + } + + /** + * Removes a value from the dataset. + * + * @param x the x-value. + * @param seriesName the series name. + */ + public void remove(double x, String seriesName) { + remove(x, seriesName, true); + } + + /** + * Removes an item from the dataset. + * + * @param x the x-value. + * @param seriesName the series name. + * @param notify notify listeners? + */ + public void remove(Number x, String seriesName, boolean notify) { + this.values.removeValue((Comparable) x, seriesName); + if (notify) { + fireDatasetChanged(); + } + } + + /** + * Clears all data from the dataset and calls {@link #fireDatasetChanged()}. + */ + public void clear() { + this.values.clear(); + fireDatasetChanged(); + } + + /** + * Returns the number of series in the collection. + * + * @return The series count. + */ + @Override + public int getSeriesCount() { + return this.values.getColumnCount(); + } + + /** + * Returns the key for a series. + * + * @param series the series index (zero-based). + * + * @return The key for a series. + */ + @Override + public Comparable getSeriesKey(int series) { + return this.values.getColumnKey(series); + } + + /** + * Returns the number of x values in the dataset. + * + * @return The item count. + */ + @Override + public int getItemCount() { + return this.values.getRowCount(); + } + + /** + * Returns the number of items in the specified series. + * Returns the same as {@link CategoryTableXYDataset#getItemCount()}. + * + * @param series the series index (zero-based). + * + * @return The item count. + */ + @Override + public int getItemCount(int series) { + return getItemCount(); // all series have the same number of items in + // this dataset + } + + /** + * Returns the x-value for the specified series and item. + * + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * + * @return The value. + */ + @Override + public Number getX(int series, int item) { + return (Number) this.values.getRowKey(item); + } + + /** + * Returns the starting X value for the specified series and item. + * + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * + * @return The starting X value. + */ + @Override + public Number getStartX(int series, int item) { + return this.intervalDelegate.getStartX(series, item); + } + + /** + * Returns the ending X value for the specified series and item. + * + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * + * @return The ending X value. + */ + @Override + public Number getEndX(int series, int item) { + return this.intervalDelegate.getEndX(series, item); + } + + /** + * Returns the y-value for the specified series and item. + * + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * + * @return The y value (possibly {@code null}). + */ + @Override + public Number getY(int series, int item) { + return this.values.getValue(item, series); + } + + /** + * Returns the starting Y value for the specified series and item. + * + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * + * @return The starting Y value. + */ + @Override + public Number getStartY(int series, int item) { + return getY(series, item); + } + + /** + * Returns the ending Y value for the specified series and item. + * + * @param series the series index (zero-based). + * @param item the item index (zero-based). + * + * @return The ending Y value. + */ + @Override + public Number getEndY(int series, int item) { + return getY(series, item); + } + + /** + * Returns the minimum x-value in the dataset. + * + * @param includeInterval a flag that determines whether or not the + * x-interval is taken into account. + * + * @return The minimum value. + */ + @Override + public double getDomainLowerBound(boolean includeInterval) { + return this.intervalDelegate.getDomainLowerBound(includeInterval); + } + + /** + * Returns the maximum x-value in the dataset. + * + * @param includeInterval a flag that determines whether or not the + * x-interval is taken into account. + * + * @return The maximum value. + */ + @Override + public double getDomainUpperBound(boolean includeInterval) { + return this.intervalDelegate.getDomainUpperBound(includeInterval); + } + + /** + * Returns the range of the values in this dataset's domain. + * + * @param includeInterval a flag that determines whether or not the + * x-interval is taken into account. + * + * @return The range. + */ + @Override + public Range getDomainBounds(boolean includeInterval) { + if (includeInterval) { + return this.intervalDelegate.getDomainBounds(includeInterval); + } + else { + return DatasetUtils.iterateDomainBounds(this, includeInterval); + } + } + + /** + * Returns the interval position factor. + * + * @return The interval position factor. + */ + public double getIntervalPositionFactor() { + return this.intervalDelegate.getIntervalPositionFactor(); + } + + /** + * Sets the interval position factor. Must be between 0.0 and 1.0 inclusive. + * If the factor is 0.5, the gap is in the middle of the x values. If it + * is lesser than 0.5, the gap is farther to the left and if greater than + * 0.5 it gets farther to the right. + * + * @param d the new interval position factor. + */ + public void setIntervalPositionFactor(double d) { + this.intervalDelegate.setIntervalPositionFactor(d); + fireDatasetChanged(); + } + + /** + * Returns the full interval width. + * + * @return The interval width to use. + */ + public double getIntervalWidth() { + return this.intervalDelegate.getIntervalWidth(); + } + + /** + * Sets the interval width to a fixed value, and calls {@link #fireDatasetChanged()}. + * + * @param d the new interval width (must be > 0). + */ + public void setIntervalWidth(double d) { + this.intervalDelegate.setFixedIntervalWidth(d); + fireDatasetChanged(); + } + + /** + * Returns whether the interval width is automatically calculated or not. + * + * @return whether the width is automatically calculated or not. + */ + public boolean isAutoWidth() { + return this.intervalDelegate.isAutoWidth(); + } + + /** + * Sets the flag that indicates whether the interval width is automatically + * calculated or not. + * + * @param b the flag. + */ + public void setAutoWidth(boolean b) { + this.intervalDelegate.setAutoWidth(b); + fireDatasetChanged(); + } + + /** + * Tests this dataset for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CategoryTableXYDataset)) { + return false; + } + CategoryTableXYDataset that = (CategoryTableXYDataset) obj; + if (!this.intervalDelegate.equals(that.intervalDelegate)) { + return false; + } + if (!this.values.equals(that.values)) { + return false; + } + return true; + } + + /** + * Returns an independent copy of this dataset. + * + * @return A clone. + * + * @throws CloneNotSupportedException if there is some reason that cloning + * cannot be performed. + */ + @Override + public Object clone() throws CloneNotSupportedException { + CategoryTableXYDataset clone = (CategoryTableXYDataset) super.clone(); + clone.values = (DefaultKeyedValues2D) this.values.clone(); + clone.intervalDelegate = new IntervalXYDelegate(clone); + // need to configure the intervalDelegate to match the original + clone.intervalDelegate.setFixedIntervalWidth(getIntervalWidth()); + clone.intervalDelegate.setAutoWidth(isAutoWidth()); + clone.intervalDelegate.setIntervalPositionFactor( + getIntervalPositionFactor()); + return clone; + } + +} diff --git a/src/main/java/org/jfree/data/xy/DefaultTableXYDataset.java b/src/main/java/org/jfree/data/xy/DefaultTableXYDataset.java index 9f88ab3bc..19c288d80 100644 --- a/src/main/java/org/jfree/data/xy/DefaultTableXYDataset.java +++ b/src/main/java/org/jfree/data/xy/DefaultTableXYDataset.java @@ -48,7 +48,6 @@ import org.jfree.data.DomainInfo; import org.jfree.data.Range; -import org.jfree.data.general.DatasetChangeEvent; import org.jfree.data.general.DatasetUtils; import org.jfree.data.general.SeriesChangeEvent; @@ -111,8 +110,7 @@ public boolean isAutoPrune() { } /** - * Adds a series to the collection and sends a {@link DatasetChangeEvent} - * to all registered listeners. The series should be configured to NOT + * Adds a series to the collection and calls {@link #fireDatasetChanged()}. The series should be configured to NOT * allow duplicate x-values. * * @param series the series ({@code null} not permitted). @@ -328,8 +326,7 @@ public Number getEndY(int series, int item) { } /** - * Removes all the series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes all the series from the collection and calls {@link #fireDatasetChanged()}. */ public void removeAllSeries() { @@ -347,8 +344,7 @@ public void removeAllSeries() { } /** - * Removes a series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes a series from the collection and calls {@link #fireDatasetChanged()}. * * @param series the series ({@code null} not permitted). */ @@ -365,8 +361,7 @@ public void removeSeries(XYSeries series) { } /** - * Removes a series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes a series from the collection and calls {@link #fireDatasetChanged()}. * * @param series the series (zero based index). */ @@ -444,7 +439,7 @@ public void prune() { /** * This method receives notification when a series belonging to the dataset * changes. It responds by updating the x-points for the entire dataset - * and sending a {@link DatasetChangeEvent} to all registered listeners. + * and calls {@link #fireDatasetChanged()}. * * @param event information about the change. */ @@ -607,8 +602,7 @@ public double getIntervalWidth() { } /** - * Sets the interval width to a fixed value, and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Sets the interval width to a fixed value, and calls {@link #fireDatasetChanged()}. * * @param d the new interval width (must be > 0). */ diff --git a/src/main/java/org/jfree/data/xy/IntervalXYDelegate.java b/src/main/java/org/jfree/data/xy/IntervalXYDelegate.java index b6dfd3382..d85592e2d 100644 --- a/src/main/java/org/jfree/data/xy/IntervalXYDelegate.java +++ b/src/main/java/org/jfree/data/xy/IntervalXYDelegate.java @@ -43,7 +43,6 @@ import org.jfree.chart.util.PublicCloneable; import org.jfree.data.DomainInfo; import org.jfree.data.Range; -import org.jfree.data.RangeInfo; import org.jfree.data.general.DatasetChangeEvent; import org.jfree.data.general.DatasetChangeListener; import org.jfree.data.general.DatasetUtils; @@ -56,7 +55,8 @@ *

    * The decorator pattern was not used because of the several possibly * implemented interfaces of the decorated instance (e.g. - * {@link TableXYDataset}, {@link RangeInfo}, {@link DomainInfo} etc.). + * {@link org.jfree.data.xy.TableXYDataset}, {@link org.jfree.data.RangeInfo}, + * {@link org.jfree.data.DomainInfo} etc.). *

    * The width can be set manually or calculated automatically. The switch * autoWidth allows to determine which behavior is used. The auto width diff --git a/src/main/java/org/jfree/data/xy/VectorSeries.java b/src/main/java/org/jfree/data/xy/VectorSeries.java index 033561c52..436fda078 100644 --- a/src/main/java/org/jfree/data/xy/VectorSeries.java +++ b/src/main/java/org/jfree/data/xy/VectorSeries.java @@ -38,7 +38,6 @@ import org.jfree.data.ComparableObjectItem; import org.jfree.data.ComparableObjectSeries; -import org.jfree.data.general.SeriesChangeEvent; /** * A list of (x,y, deltaX, deltaY) data items. @@ -84,9 +83,9 @@ public void add(double x, double y, double deltaX, double deltaY) { } /** - * Adds a data item to the series and, if requested, sends a - * {@link SeriesChangeEvent} to all registered listeners. - * + * Adds a data item to the series by calling + * {@link ComparableObjectSeries#add(org.jfree.data.ComparableObjectItem, boolean)}. + * * @param item the data item ({@code null} not permitted). * @param notify notify listeners? */ @@ -95,8 +94,8 @@ public void add(VectorDataItem item, boolean notify) { } /** - * Removes the item at the specified index and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Removes the item at the specified index and calls + * {@link #fireSeriesChanged()}. * * @param index the index. * diff --git a/src/main/java/org/jfree/data/xy/VectorSeriesCollection.java b/src/main/java/org/jfree/data/xy/VectorSeriesCollection.java index 4ffd55b21..222aeea1f 100644 --- a/src/main/java/org/jfree/data/xy/VectorSeriesCollection.java +++ b/src/main/java/org/jfree/data/xy/VectorSeriesCollection.java @@ -43,8 +43,6 @@ import org.jfree.chart.util.Args; import org.jfree.chart.util.PublicCloneable; -import org.jfree.data.general.DatasetChangeEvent; - /** * A collection of {@link VectorSeries} objects. */ @@ -62,8 +60,7 @@ public VectorSeriesCollection() { } /** - * Adds a series to the collection and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Adds a series to the collection and calls {@link #fireDatasetChanged()}. * * @param series the series ({@code null} not permitted). */ @@ -75,8 +72,7 @@ public void addSeries(VectorSeries series) { } /** - * Removes the specified series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes the specified series from the collection and calls {@link #fireDatasetChanged()}. * * @param series the series ({@code null} not permitted). * @@ -94,8 +90,7 @@ public boolean removeSeries(VectorSeries series) { } /** - * Removes all the series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes all the series from the collection and calls {@link #fireDatasetChanged()}. */ public void removeAllSeries() { diff --git a/src/main/java/org/jfree/data/xy/XIntervalSeries.java b/src/main/java/org/jfree/data/xy/XIntervalSeries.java index a082a7363..e71368ef3 100644 --- a/src/main/java/org/jfree/data/xy/XIntervalSeries.java +++ b/src/main/java/org/jfree/data/xy/XIntervalSeries.java @@ -38,7 +38,6 @@ import org.jfree.data.ComparableObjectItem; import org.jfree.data.ComparableObjectSeries; -import org.jfree.data.general.SeriesChangeEvent; /** * A list of (x, x-low, x-high, y) data items. @@ -74,25 +73,25 @@ public XIntervalSeries(Comparable key, boolean autoSort, } /** - * Adds a data item to the series and sends a {@link SeriesChangeEvent} to - * all registered listeners. - * - * @param x the x-value. - * @param y the y-value. - * @param xLow the lower bound of the y-interval. - * @param xHigh the upper bound of the y-interval. - */ + * Adds a data item to the series by calling + * {@link #add(XIntervalDataItem, boolean)} + * + * @param x the x-value. + * @param y the y-value. + * @param xLow the lower bound of the y-interval. + * @param xHigh the upper bound of the y-interval. + */ public void add(double x, double xLow, double xHigh, double y) { add(new XIntervalDataItem(x, xLow, xHigh, y), true); } /** - * Adds a data item to the series and, if requested, sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param item the data item ({@code null} not permitted). - * @param notify notify listeners? - */ + * Adds a data item to the series by calling + * {@link #add(ComparableObjectItem, boolean)} + * + * @param item the data item ({@code null} not permitted). + * @param notify notify listeners? + */ public void add(XIntervalDataItem item, boolean notify) { super.add(item, notify); } diff --git a/src/main/java/org/jfree/data/xy/XIntervalSeriesCollection.java b/src/main/java/org/jfree/data/xy/XIntervalSeriesCollection.java index f3a11ed5c..eeea96828 100644 --- a/src/main/java/org/jfree/data/xy/XIntervalSeriesCollection.java +++ b/src/main/java/org/jfree/data/xy/XIntervalSeriesCollection.java @@ -43,8 +43,6 @@ import org.jfree.chart.util.Args; import org.jfree.chart.util.PublicCloneable; -import org.jfree.data.general.DatasetChangeEvent; - /** * A collection of {@link XIntervalSeries} objects. * @@ -64,8 +62,7 @@ public XIntervalSeriesCollection() { } /** - * Adds a series to the collection and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Adds a series to the collection and calls {@link #fireDatasetChanged}. * * @param series the series ({@code null} not permitted). */ @@ -270,8 +267,7 @@ public Number getEndY(int series, int item) { } /** - * Removes a series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes a series from the collection and calls {@link #fireDatasetChanged}. * * @param series the series index (zero-based). */ @@ -286,8 +282,7 @@ public void removeSeries(int series) { } /** - * Removes a series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes a series from the collection and calls {@link #fireDatasetChanged}. * * @param series the series ({@code null} not permitted). */ @@ -301,9 +296,9 @@ public void removeSeries(XIntervalSeries series) { } /** - * Removes all the series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. - */ + * Removes all the series from the collection and calls + * {@link #fireDatasetChanged}. + */ public void removeAllSeries() { // Unregister the collection as a change listener to each series in // the collection. diff --git a/src/main/java/org/jfree/data/xy/XYIntervalSeries.java b/src/main/java/org/jfree/data/xy/XYIntervalSeries.java index 1359f9301..76e23ba28 100644 --- a/src/main/java/org/jfree/data/xy/XYIntervalSeries.java +++ b/src/main/java/org/jfree/data/xy/XYIntervalSeries.java @@ -38,7 +38,6 @@ import org.jfree.data.ComparableObjectItem; import org.jfree.data.ComparableObjectSeries; -import org.jfree.data.general.SeriesChangeEvent; /** * A list of (x, x-low, x-high, y, y-low, y-high) data items. @@ -74,24 +73,24 @@ public XYIntervalSeries(Comparable key, boolean autoSort, } /** - * Adds a data item to the series and sends a {@link SeriesChangeEvent} to - * all registered listeners. - * - * @param x the x-value. - * @param xLow the lower bound of the x-interval. - * @param xHigh the upper bound of the x-interval. - * @param y the y-value. - * @param yLow the lower bound of the y-interval. - * @param yHigh the upper bound of the y-interval. - */ + * Adds a data item to the series by calling + * {@link #add(XYIntervalDataItem, boolean)}. + * + * @param x the x-value. + * @param xLow the lower bound of the x-interval. + * @param xHigh the upper bound of the x-interval. + * @param y the y-value. + * @param yLow the lower bound of the y-interval. + * @param yHigh the upper bound of the y-interval. + */ public void add(double x, double xLow, double xHigh, double y, double yLow, double yHigh) { add(new XYIntervalDataItem(x, xLow, xHigh, y, yLow, yHigh), true); } /** - * Adds a data item to the series and, if requested, sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Adds a data item to the series by calling + * {@link #add(XYIntervalDataItem, boolean)}. * * @param item the data item ({@code null} not permitted). * @param notify notify listeners? diff --git a/src/main/java/org/jfree/data/xy/XYIntervalSeriesCollection.java b/src/main/java/org/jfree/data/xy/XYIntervalSeriesCollection.java index d4be92bcf..14ed42f8a 100644 --- a/src/main/java/org/jfree/data/xy/XYIntervalSeriesCollection.java +++ b/src/main/java/org/jfree/data/xy/XYIntervalSeriesCollection.java @@ -42,8 +42,6 @@ import org.jfree.chart.util.Args; import org.jfree.chart.util.PublicCloneable; -import org.jfree.data.general.DatasetChangeEvent; - /** * A collection of {@link XYIntervalSeries} objects. * @@ -63,8 +61,7 @@ public XYIntervalSeriesCollection() { } /** - * Adds a series to the collection and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Adds a series to the collection and calls {@link #fireDatasetChanged}. * * @param series the series ({@code null} not permitted). */ @@ -292,8 +289,7 @@ public Number getEndY(int series, int item) { } /** - * Removes a series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes a series from the collection and calls {@link #fireDatasetChanged}. * * @param series the series index (zero-based). */ @@ -308,8 +304,7 @@ public void removeSeries(int series) { } /** - * Removes a series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes a series from the collection and calls {@link #fireDatasetChanged}. * * @param series the series ({@code null} not permitted). */ @@ -323,9 +318,9 @@ public void removeSeries(XYIntervalSeries series) { } /** - * Removes all the series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. - */ + * Removes all the series from the collection and calls + * {@link #fireDatasetChanged}. + */ public void removeAllSeries() { // Unregister the collection as a change listener to each series in // the collection. diff --git a/src/main/java/org/jfree/data/xy/XYSeries.java b/src/main/java/org/jfree/data/xy/XYSeries.java index 5a0777e8b..729687c02 100644 --- a/src/main/java/org/jfree/data/xy/XYSeries.java +++ b/src/main/java/org/jfree/data/xy/XYSeries.java @@ -49,7 +49,6 @@ import org.jfree.chart.util.Args; import org.jfree.data.general.Series; -import org.jfree.data.general.SeriesChangeEvent; import org.jfree.data.general.SeriesException; /** @@ -317,19 +316,17 @@ public int getMaximumItemCount() { } /** - * Sets the maximum number of items that will be retained in the series. - * If you add a new item to the series such that the number of items will - * exceed the maximum item count, then the first element in the series is - * automatically removed, ensuring that the maximum item count is not - * exceeded. - *

    - * Typically this value is set before the series is populated with data, - * but if it is applied later, it may cause some items to be removed from - * the series (in which case a {@link SeriesChangeEvent} will be sent to - * all registered listeners). - * - * @param maximum the maximum number of items for the series. - */ + * Sets the maximum number of items that will be retained in the series. If you + * add a new item to the series such that the number of items will exceed the + * maximum item count, then the first element in the series is automatically + * removed, ensuring that the maximum item count is not exceeded. + *

    + * Typically this value is set before the series is populated with data, but if + * it is applied later, it may cause some items to be removed from the series + * (in which case notification will be sent to all registered listeners). + * + * @param maximum the maximum number of items for the series. + */ public void setMaximumItemCount(int maximum) { this.maximumItemCount = maximum; int remove = this.data.size() - maximum; @@ -341,72 +338,68 @@ public void setMaximumItemCount(int maximum) { } /** - * Adds a data item to the series and sends a {@link SeriesChangeEvent} to - * all registered listeners. - * - * @param item the (x, y) item ({@code null} not permitted). - */ + * Adds a data item to the series by calling {@link #add(XYDataItem, boolean)}. + * + * @param item the (x, y) item ({@code null} not permitted). + */ public void add(XYDataItem item) { // argument checking delegated... add(item, true); } /** - * Adds a data item to the series and sends a {@link SeriesChangeEvent} to - * all registered listeners. - * - * @param x the x value. - * @param y the y value. - */ + * Adds a data item to the series by calling + * {@link #add(double, double, boolean)}. + * + * @param x the x value. + * @param y the y value. + */ public void add(double x, double y) { add(x, y, true); } /** - * Adds a data item to the series and, if requested, sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param x the x value. - * @param y the y value. - * @param notify a flag that controls whether or not a - * {@link SeriesChangeEvent} is sent to all registered - * listeners. - */ + * Adds a data item to the series by calling + * {@link #add(Number, Number, boolean)}. + * + * @param x the x value. + * @param y the y value. + * @param notify a flag that controls whether or not notification is sent + * to all registered listeners. + */ public void add(double x, double y, boolean notify) { add(Double.valueOf(x), Double.valueOf(y), notify); } /** - * Adds a data item to the series and sends a {@link SeriesChangeEvent} to - * all registered listeners. The unusual pairing of parameter types is to - * make it easier to add {@code null} y-values. - * - * @param x the x value. - * @param y the y value ({@code null} permitted). - */ + * Adds a data item to the series by calling {@link #add(Number, Number)}. + * The unusual pairing of parameter types is to make it easier to add + * {@code null} y-values. + * + * @param x the x value. + * @param y the y value ({@code null} permitted). + */ public void add(double x, Number y) { add(Double.valueOf(x), y); } /** - * Adds a data item to the series and, if requested, sends a - * {@link SeriesChangeEvent} to all registered listeners. The unusual - * pairing of parameter types is to make it easier to add null y-values. - * - * @param x the x value. - * @param y the y value ({@code null} permitted). - * @param notify a flag that controls whether or not a - * {@link SeriesChangeEvent} is sent to all registered - * listeners. - */ + * Adds a data item to the series by calling + * {@link #add(Number, Number, boolean)}. The unusual pairing of parameter + * types is to make it easier to add null y-values. + * + * @param x the x value. + * @param y the y value ({@code null} permitted). + * @param notify a flag that controls whether or not notification is sent + * to all registered listeners. + */ public void add(double x, Number y, boolean notify) { add(Double.valueOf(x), y, notify); } /** - * Adds a new data item to the series (in the correct position if the - * {@code autoSort} flag is set for the series) and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Adds a new data item to the series by calling + * {@link #add(Number, Number, boolean)}. *

    * Throws an exception if the x-value is a duplicate AND the * allowDuplicateXValues flag is false. @@ -423,17 +416,15 @@ public void add(Number x, Number y) { } /** - * Adds new data to the series and, if requested, sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Adds new data to the series by calling {@link #add(XYDataItem, boolean)}. *

    * Throws an exception if the x-value is a duplicate AND the * allowDuplicateXValues flag is false. * * @param x the x-value ({@code null} not permitted). * @param y the y-value ({@code null} permitted). - * @param notify a flag the controls whether or not a - * {@link SeriesChangeEvent} is sent to all registered - * listeners. + * @param notify a flag that controls whether or not notification is sent + * to all registered listeners. */ public void add(Number x, Number y, boolean notify) { // delegate argument checking to XYDataItem... @@ -442,14 +433,13 @@ public void add(Number x, Number y, boolean notify) { } /** - * Adds a data item to the series and, if requested, sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param item the (x, y) item ({@code null} not permitted). - * @param notify a flag that controls whether or not a - * {@link SeriesChangeEvent} is sent to all registered - * listeners. - */ + * Adds a data item to the series and, if requested, calls + * {@link #fireSeriesChanged()}. + * + * @param item the (x, y) item ({@code null} not permitted). + * @param notify a flag that controls whether or not notification is sent to all + * registered listeners. + */ public void add(XYDataItem item, boolean notify) { Args.nullNotPermitted(item, "item"); item = (XYDataItem) item.clone(); @@ -500,8 +490,8 @@ public void add(XYDataItem item, boolean notify) { } /** - * Deletes a range of items from the series and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Deletes a range of items from the series and calls + * {@link #fireSeriesChanged()}. * * @param start the start index (zero-based). * @param end the end index (zero-based). @@ -513,13 +503,13 @@ public void delete(int start, int end) { } /** - * Removes the item at the specified index and sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param index the index. - * - * @return The item removed. - */ + * Removes the item at the specified index and calls + * {@link #fireSeriesChanged()}. + * + * @param index the index. + * + * @return The item removed. + */ public XYDataItem remove(int index) { XYDataItem removed = (XYDataItem) this.data.remove(index); updateBoundsForRemovedItem(removed); @@ -528,23 +518,22 @@ public XYDataItem remove(int index) { } /** - * Removes an item with the specified x-value and sends a - * {@link SeriesChangeEvent} to all registered listeners. Note that when - * a series permits multiple items with the same x-value, this method - * could remove any one of the items with that x-value. - * - * @param x the x-value. - - * @return The item removed. - */ + * Removes an item with the specified x-value by calling {@link #remove(int)}. + * Note that when a series permits multiple items with the same x-value, this + * method could remove any one of the items with that x-value. + * + * @param x the x-value. + * + * @return The item removed. + */ public XYDataItem remove(Number x) { return remove(indexOf(x)); } /** - * Removes all data items from the series and sends a - * {@link SeriesChangeEvent} to all registered listeners. - */ + * Removes all data items from the series and calls + * {@link #fireSeriesChanged()}. + */ public void clear() { if (this.data.size() > 0) { this.data.clear(); @@ -640,8 +629,8 @@ private double maxIgnoreNaN(double a, double b) { } /** - * Updates the value of an item in the series and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Updates the value of an item in the series and calls + * {@link #fireSeriesChanged()}. * * @param index the item (zero based index). * @param y the new value ({@code null} permitted). @@ -686,21 +675,21 @@ public void update(Number x, Number y) { } /** - * Adds or updates an item in the series and sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param x the x-value. - * @param y the y-value. - * - * @return The item that was overwritten, if any. - */ + * Adds or updates an item in the series by calling + * {@link #addOrUpdate(Number, Number)}. + * + * @param x the x-value. + * @param y the y-value. + * + * @return The item that was overwritten, if any. + */ public XYDataItem addOrUpdate(double x, double y) { return addOrUpdate(Double.valueOf(x), Double.valueOf(y)); } /** - * Adds or updates an item in the series and sends a - * {@link SeriesChangeEvent} to all registered listeners. + * Adds or updates an item in the series by calling + * {@link #addOrUpdate(XYDataItem)}. * * @param x the x-value ({@code null} not permitted). * @param y the y-value ({@code null} permitted). @@ -714,14 +703,13 @@ public XYDataItem addOrUpdate(Number x, Number y) { } /** - * Adds or updates an item in the series and sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param item the data item ({@code null} not permitted). - * - * @return A copy of the overwritten data item, or {@code null} if no - * item was overwritten. - */ + * Adds or updates an item in the series and calls {@link #fireSeriesChanged()}. + * + * @param item the data item ({@code null} not permitted). + * + * @return A copy of the overwritten data item, or {@code null} if no item was + * overwritten. + */ public XYDataItem addOrUpdate(XYDataItem item) { Args.nullNotPermitted(item, "item"); if (this.allowDuplicateXValues) { diff --git a/src/main/java/org/jfree/data/xy/XYSeriesCollection.java b/src/main/java/org/jfree/data/xy/XYSeriesCollection.java index 95eb4a4b5..a5fbb7dd6 100644 --- a/src/main/java/org/jfree/data/xy/XYSeriesCollection.java +++ b/src/main/java/org/jfree/data/xy/XYSeriesCollection.java @@ -1,751 +1,752 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ----------------------- - * XYSeriesCollection.java - * ----------------------- - * (C) Copyright 2001-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): Aaron Metzger; - * - */ - -package org.jfree.data.xy; - -import org.jfree.chart.HashUtils; -import org.jfree.chart.util.Args; -import org.jfree.chart.util.ObjectUtils; -import org.jfree.chart.util.PublicCloneable; -import org.jfree.data.*; -import org.jfree.data.general.DatasetChangeEvent; -import org.jfree.data.general.Series; - -import java.beans.PropertyChangeEvent; -import java.beans.PropertyVetoException; -import java.beans.VetoableChangeListener; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * Represents a collection of {@link XYSeries} objects that can be used as a - * dataset. - */ -public class XYSeriesCollection extends AbstractIntervalXYDataset - implements IntervalXYDataset, DomainInfo, RangeInfo, - VetoableChangeListener, PublicCloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = -7590013825931496766L; - - /** The series that are included in the collection. */ - private List data; - - /** The interval delegate (used to calculate the start and end x-values). */ - private IntervalXYDelegate intervalDelegate; - - /** - * Constructs an empty dataset. - */ - public XYSeriesCollection() { - this(null); - } - - /** - * Constructs a dataset and populates it with a single series. - * - * @param series the series ({@code null} ignored). - */ - public XYSeriesCollection(XYSeries series) { - this.data = new java.util.ArrayList(); - this.intervalDelegate = new IntervalXYDelegate(this, false); - addChangeListener(this.intervalDelegate); - if (series != null) { - this.data.add(series); - series.addChangeListener(this); - series.addVetoableChangeListener(this); - } - } - - /** - * Returns the order of the domain (X) values, if this is known. - * - * @return The domain order. - */ - @Override - public DomainOrder getDomainOrder() { - int seriesCount = getSeriesCount(); - for (int i = 0; i < seriesCount; i++) { - XYSeries s = getSeries(i); - if (!s.getAutoSort()) { - return DomainOrder.NONE; // we can't be sure of the order - } - } - return DomainOrder.ASCENDING; - } - - /** - * Adds a series to the collection and sends a {@link DatasetChangeEvent} - * to all registered listeners. - * - * @param series the series ({@code null} not permitted). - * - * @throws IllegalArgumentException if the key for the series is null or - * not unique within the dataset. - */ - public void addSeries(XYSeries series) { - Args.nullNotPermitted(series, "series"); - if (getSeriesIndex(series.getKey()) >= 0) { - throw new IllegalArgumentException( - "This dataset already contains a series with the key " - + series.getKey()); - } - this.data.add(series); - series.addChangeListener(this); - series.addVetoableChangeListener(this); - fireDatasetChanged(); - } - - /** - * Removes a series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. - * - * @param series the series index (zero-based). - */ - public void removeSeries(int series) { - if ((series < 0) || (series >= getSeriesCount())) { - throw new IllegalArgumentException("Series index out of bounds."); - } - XYSeries s = (XYSeries) this.data.get(series); - if (s != null) { - removeSeries(s); - } - } - - /** - * Removes a series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. - * - * @param series the series ({@code null} not permitted). - */ - public void removeSeries(XYSeries series) { - Args.nullNotPermitted(series, "series"); - if (this.data.contains(series)) { - series.removeChangeListener(this); - series.removeVetoableChangeListener(this); - this.data.remove(series); - fireDatasetChanged(); - } - } - - /** - * Removes all the series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. - */ - public void removeAllSeries() { - // Unregister the collection as a change listener to each series in - // the collection. - for (Object item : this.data) { - XYSeries series = (XYSeries) item; - series.removeChangeListener(this); - series.removeVetoableChangeListener(this); - } - - // Remove all the series from the collection and notify listeners. - this.data.clear(); - fireDatasetChanged(); - } - - /** - * Returns the number of series in the collection. - * - * @return The series count. - */ - @Override - public int getSeriesCount() { - return this.data.size(); - } - - /** - * Returns a list of all the series in the collection. - * - * @return The list (which is unmodifiable). - */ - public List getSeries() { - return Collections.unmodifiableList(this.data); - } - - /** - * Returns the index of the specified series, or -1 if that series is not - * present in the dataset. - * - * @param series the series ({@code null} not permitted). - * - * @return The series index. - */ - public int indexOf(XYSeries series) { - Args.nullNotPermitted(series, "series"); - return this.data.indexOf(series); - } - - /** - * Returns a series from the collection. - * - * @param series the series index (zero-based). - * - * @return The series. - * - * @throws IllegalArgumentException if {@code series} is not in the - * range {@code 0} to {@code getSeriesCount() - 1}. - */ - public XYSeries getSeries(int series) { - if ((series < 0) || (series >= getSeriesCount())) { - throw new IllegalArgumentException("Series index out of bounds"); - } - return (XYSeries) this.data.get(series); - } - - /** - * Returns a series from the collection. - * - * @param key the key ({@code null} not permitted). - * - * @return The series with the specified key. - * - * @throws UnknownKeyException if {@code key} is not found in the - * collection. - */ - public XYSeries getSeries(Comparable key) { - Args.nullNotPermitted(key, "key"); - for (Object item : this.data) { - XYSeries series = (XYSeries) item; - if (key.equals(series.getKey())) { - return series; - } - } - throw new UnknownKeyException("Key not found: " + key); - } - - /** - * Returns the key for a series. - * - * @param series the series index (in the range {@code 0} to - * {@code getSeriesCount() - 1}). - * - * @return The key for a series. - * - * @throws IllegalArgumentException if {@code series} is not in the - * specified range. - */ - @Override - public Comparable getSeriesKey(int series) { - // defer argument checking - return getSeries(series).getKey(); - } - - /** - * Returns the index of the series with the specified key, or -1 if no - * series has that key. - * - * @param key the key ({@code null} not permitted). - * - * @return The index. - */ - public int getSeriesIndex(Comparable key) { - Args.nullNotPermitted(key, "key"); - int seriesCount = getSeriesCount(); - for (int i = 0; i < seriesCount; i++) { - XYSeries series = (XYSeries) this.data.get(i); - if (key.equals(series.getKey())) { - return i; - } - } - return -1; - } - - /** - * Returns the number of items in the specified series. - * - * @param series the series (zero-based index). - * - * @return The item count. - * - * @throws IllegalArgumentException if {@code series} is not in the - * range {@code 0} to {@code getSeriesCount() - 1}. - */ - @Override - public int getItemCount(int series) { - // defer argument checking - return getSeries(series).getItemCount(); - } - - /** - * Returns the x-value for the specified series and item. - * - * @param series the series (zero-based index). - * @param item the item (zero-based index). - * - * @return The value. - */ - @Override - public Number getX(int series, int item) { - XYSeries s = (XYSeries) this.data.get(series); - return s.getX(item); - } - - /** - * Returns the starting X value for the specified series and item. - * - * @param series the series (zero-based index). - * @param item the item (zero-based index). - * - * @return The starting X value. - */ - @Override - public Number getStartX(int series, int item) { - return this.intervalDelegate.getStartX(series, item); - } - - /** - * Returns the ending X value for the specified series and item. - * - * @param series the series (zero-based index). - * @param item the item (zero-based index). - * - * @return The ending X value. - */ - @Override - public Number getEndX(int series, int item) { - return this.intervalDelegate.getEndX(series, item); - } - - /** - * Returns the y-value for the specified series and item. - * - * @param series the series (zero-based index). - * @param index the index of the item of interest (zero-based). - * - * @return The value (possibly {@code null}). - */ - @Override - public Number getY(int series, int index) { - XYSeries s = (XYSeries) this.data.get(series); - return s.getY(index); - } - - /** - * Returns the starting Y value for the specified series and item. - * - * @param series the series (zero-based index). - * @param item the item (zero-based index). - * - * @return The starting Y value. - */ - @Override - public Number getStartY(int series, int item) { - return getY(series, item); - } - - /** - * Returns the ending Y value for the specified series and item. - * - * @param series the series (zero-based index). - * @param item the item (zero-based index). - * - * @return The ending Y value. - */ - @Override - public Number getEndY(int series, int item) { - return getY(series, item); - } - - /** - * Tests this collection for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof XYSeriesCollection)) { - return false; - } - XYSeriesCollection that = (XYSeriesCollection) obj; - if (!this.intervalDelegate.equals(that.intervalDelegate)) { - return false; - } - return Objects.equals(this.data, that.data); - } - - /** - * Returns a clone of this instance. - * - * @return A clone. - * - * @throws CloneNotSupportedException if there is a problem. - */ - @Override - public Object clone() throws CloneNotSupportedException { - XYSeriesCollection clone = (XYSeriesCollection) super.clone(); - clone.data = (List) ObjectUtils.deepClone(this.data); - clone.intervalDelegate - = (IntervalXYDelegate) this.intervalDelegate.clone(); - return clone; - } - - /** - * Returns a hash code. - * - * @return A hash code. - */ - @Override - public int hashCode() { - int hash = 5; - hash = HashUtils.hashCode(hash, this.intervalDelegate); - hash = HashUtils.hashCode(hash, this.data); - return hash; - } - - /** - * Returns the minimum x-value in the dataset. - * - * @param includeInterval a flag that determines whether the - * x-interval is taken into account. - * - * @return The minimum value. - */ - @Override - public double getDomainLowerBound(boolean includeInterval) { - if (includeInterval) { - return this.intervalDelegate.getDomainLowerBound(includeInterval); - } - double result = Double.NaN; - int seriesCount = getSeriesCount(); - for (int s = 0; s < seriesCount; s++) { - XYSeries series = getSeries(s); - double lowX = series.getMinX(); - if (Double.isNaN(result)) { - result = lowX; - } - else { - if (!Double.isNaN(lowX)) { - result = Math.min(result, lowX); - } - } - } - return result; - } - - /** - * Returns the maximum x-value in the dataset. - * - * @param includeInterval a flag that determines whether the - * x-interval is taken into account. - * - * @return The maximum value. - */ - @Override - public double getDomainUpperBound(boolean includeInterval) { - if (includeInterval) { - return this.intervalDelegate.getDomainUpperBound(includeInterval); - } - else { - double result = Double.NaN; - int seriesCount = getSeriesCount(); - for (int s = 0; s < seriesCount; s++) { - XYSeries series = getSeries(s); - double hiX = series.getMaxX(); - if (Double.isNaN(result)) { - result = hiX; - } - else { - if (!Double.isNaN(hiX)) { - result = Math.max(result, hiX); - } - } - } - return result; - } - } - - /** - * Returns the range of the values in this dataset's domain. - * - * @param includeInterval a flag that determines whether the - * x-interval is taken into account. - * - * @return The range (or {@code null} if the dataset contains no - * values). - */ - @Override - public Range getDomainBounds(boolean includeInterval) { - if (includeInterval) { - return this.intervalDelegate.getDomainBounds(includeInterval); - } - else { - double lower = Double.POSITIVE_INFINITY; - double upper = Double.NEGATIVE_INFINITY; - int seriesCount = getSeriesCount(); - for (int s = 0; s < seriesCount; s++) { - XYSeries series = getSeries(s); - double minX = series.getMinX(); - if (!Double.isNaN(minX)) { - lower = Math.min(lower, minX); - } - double maxX = series.getMaxX(); - if (!Double.isNaN(maxX)) { - upper = Math.max(upper, maxX); - } - } - if (lower > upper) { - return null; - } - else { - return new Range(lower, upper); - } - } - } - - /** - * Returns the interval width. This is used to calculate the start and end - * x-values, if/when the dataset is used as an {@link IntervalXYDataset}. - * - * @return The interval width. - */ - public double getIntervalWidth() { - return this.intervalDelegate.getIntervalWidth(); - } - - /** - * Sets the interval width and sends a {@link DatasetChangeEvent} to all - * registered listeners. - * - * @param width the width (negative values not permitted). - */ - public void setIntervalWidth(double width) { - if (width < 0.0) { - throw new IllegalArgumentException("Negative 'width' argument."); - } - this.intervalDelegate.setFixedIntervalWidth(width); - fireDatasetChanged(); - } - - /** - * Returns the interval position factor. - * - * @return The interval position factor. - */ - public double getIntervalPositionFactor() { - return this.intervalDelegate.getIntervalPositionFactor(); - } - - /** - * Sets the interval position factor. This controls where the x-value is in - * relation to the interval surrounding the x-value (0.0 means the x-value - * will be positioned at the start, 0.5 in the middle, and 1.0 at the end). - * - * @param factor the factor. - */ - public void setIntervalPositionFactor(double factor) { - this.intervalDelegate.setIntervalPositionFactor(factor); - fireDatasetChanged(); - } - - /** - * Returns whether the interval width is automatically calculated or not. - * - * @return Whether the width is automatically calculated or not. - */ - public boolean isAutoWidth() { - return this.intervalDelegate.isAutoWidth(); - } - - /** - * Sets the flag that indicates whether the interval width is automatically - * calculated or not. - * - * @param b a boolean. - */ - public void setAutoWidth(boolean b) { - this.intervalDelegate.setAutoWidth(b); - fireDatasetChanged(); - } - - /** - * Returns the range of the values in this dataset's range. - * - * @param includeInterval ignored. - * - * @return The range (or {@code null} if the dataset contains no - * values). - */ - @Override - public Range getRangeBounds(boolean includeInterval) { - double lower = Double.POSITIVE_INFINITY; - double upper = Double.NEGATIVE_INFINITY; - int seriesCount = getSeriesCount(); - for (int s = 0; s < seriesCount; s++) { - XYSeries series = getSeries(s); - double minY = series.getMinY(); - if (!Double.isNaN(minY)) { - lower = Math.min(lower, minY); - } - double maxY = series.getMaxY(); - if (!Double.isNaN(maxY)) { - upper = Math.max(upper, maxY); - } - } - if (lower > upper) { - return null; - } - else { - return new Range(lower, upper); - } - } - - /** - * Returns the minimum y-value in the dataset. - * - * @param includeInterval a flag that determines whether the - * y-interval is taken into account. - * - * @return The minimum value. - */ - @Override - public double getRangeLowerBound(boolean includeInterval) { - double result = Double.NaN; - int seriesCount = getSeriesCount(); - for (int s = 0; s < seriesCount; s++) { - XYSeries series = getSeries(s); - double lowY = series.getMinY(); - if (Double.isNaN(result)) { - result = lowY; - } - else { - if (!Double.isNaN(lowY)) { - result = Math.min(result, lowY); - } - } - } - return result; - } - - /** - * Returns the maximum y-value in the dataset. - * - * @param includeInterval a flag that determines whether the - * y-interval is taken into account. - * - * @return The maximum value. - */ - @Override - public double getRangeUpperBound(boolean includeInterval) { - double result = Double.NaN; - int seriesCount = getSeriesCount(); - for (int s = 0; s < seriesCount; s++) { - XYSeries series = getSeries(s); - double hiY = series.getMaxY(); - if (Double.isNaN(result)) { - result = hiY; - } - else { - if (!Double.isNaN(hiY)) { - result = Math.max(result, hiY); - } - } - } - return result; - } - - /** - * Receives notification that the key for one of the series in the - * collection has changed, and vetos it if the key is already present in - * the collection. - * - * @param e the event. - * - * @throws PropertyVetoException if the series name is already present in - * the collection. - */ - @Override - public void vetoableChange(PropertyChangeEvent e) throws PropertyVetoException { - // if it is not the series name, then we have no interest - if (!"Key".equals(e.getPropertyName())) { - return; - } - - // to be defensive, let's check that the source series does in fact - // belong to this collection - Series s = (Series) e.getSource(); - if (getSeriesIndex(s.getKey()) == -1) { - throw new IllegalStateException("Receiving events from a series " + - "that does not belong to this collection."); - } - // check if the new series name already exists for another series - Comparable key = (Comparable) e.getNewValue(); - if (getSeriesIndex(key) >= 0) { - throw new PropertyVetoException("Duplicate key2", e); - } - } - - /** - * Provides serialization support. - * - * @param stream the output stream. - * - * @throws IOException if there is an I/O error. - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - } - - /** - * Provides serialization support. - * - * @param stream the input stream. - * - * @throws IOException if there is an I/O error. - * @throws ClassNotFoundException if there is a classpath problem. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - for (Object item : this.data) { - XYSeries series = (XYSeries) item; - series.addChangeListener(this); - } - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ----------------------- + * XYSeriesCollection.java + * ----------------------- + * (C) Copyright 2001-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): Aaron Metzger; + * + */ + +package org.jfree.data.xy; + +import org.jfree.chart.HashUtils; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.ObjectUtils; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.data.DomainInfo; +import org.jfree.data.DomainOrder; +import org.jfree.data.Range; +import org.jfree.data.RangeInfo; +import org.jfree.data.UnknownKeyException; +import org.jfree.data.general.Series; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyVetoException; +import java.beans.VetoableChangeListener; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Represents a collection of {@link XYSeries} objects that can be used as a + * dataset. + */ +public class XYSeriesCollection extends AbstractIntervalXYDataset + implements IntervalXYDataset, DomainInfo, RangeInfo, + VetoableChangeListener, PublicCloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = -7590013825931496766L; + + /** The series that are included in the collection. */ + private List data; + + /** The interval delegate (used to calculate the start and end x-values). */ + private IntervalXYDelegate intervalDelegate; + + /** + * Constructs an empty dataset. + */ + public XYSeriesCollection() { + this(null); + } + + /** + * Constructs a dataset and populates it with a single series. + * + * @param series the series ({@code null} ignored). + */ + public XYSeriesCollection(XYSeries series) { + this.data = new java.util.ArrayList(); + this.intervalDelegate = new IntervalXYDelegate(this, false); + addChangeListener(this.intervalDelegate); + if (series != null) { + this.data.add(series); + series.addChangeListener(this); + series.addVetoableChangeListener(this); + } + } + + /** + * Returns the order of the domain (X) values, if this is known. + * + * @return The domain order. + */ + @Override + public DomainOrder getDomainOrder() { + int seriesCount = getSeriesCount(); + for (int i = 0; i < seriesCount; i++) { + XYSeries s = getSeries(i); + if (!s.getAutoSort()) { + return DomainOrder.NONE; // we can't be sure of the order + } + } + return DomainOrder.ASCENDING; + } + + /** + * Adds a series to the collection and calls {@link #fireDatasetChanged()}. + * + * @param series the series ({@code null} not permitted). + * + * @throws IllegalArgumentException if the key for the series is null or + * not unique within the dataset. + */ + public void addSeries(XYSeries series) { + Args.nullNotPermitted(series, "series"); + if (getSeriesIndex(series.getKey()) >= 0) { + throw new IllegalArgumentException( + "This dataset already contains a series with the key " + + series.getKey()); + } + this.data.add(series); + series.addChangeListener(this); + series.addVetoableChangeListener(this); + fireDatasetChanged(); + } + + /** + * Removes a series from the collection and calls + * {@link #fireDatasetChanged()}. + * + * @param series the series index (zero-based). + */ + public void removeSeries(int series) { + if ((series < 0) || (series >= getSeriesCount())) { + throw new IllegalArgumentException("Series index out of bounds."); + } + XYSeries s = (XYSeries) this.data.get(series); + if (s != null) { + removeSeries(s); + } + } + + /** + * Removes a series from the collection and calls + * {@link #fireDatasetChanged()}. + * + * @param series the series ({@code null} not permitted). + */ + public void removeSeries(XYSeries series) { + Args.nullNotPermitted(series, "series"); + if (this.data.contains(series)) { + series.removeChangeListener(this); + series.removeVetoableChangeListener(this); + this.data.remove(series); + fireDatasetChanged(); + } + } + + /** + * Removes all the series from the collection and and calls + * {@link #fireDatasetChanged()}. + */ + public void removeAllSeries() { + // Unregister the collection as a change listener to each series in + // the collection. + for (Object item : this.data) { + XYSeries series = (XYSeries) item; + series.removeChangeListener(this); + series.removeVetoableChangeListener(this); + } + + // Remove all the series from the collection and notify listeners. + this.data.clear(); + fireDatasetChanged(); + } + + /** + * Returns the number of series in the collection. + * + * @return The series count. + */ + @Override + public int getSeriesCount() { + return this.data.size(); + } + + /** + * Returns a list of all the series in the collection. + * + * @return The list (which is unmodifiable). + */ + public List getSeries() { + return Collections.unmodifiableList(this.data); + } + + /** + * Returns the index of the specified series, or -1 if that series is not + * present in the dataset. + * + * @param series the series ({@code null} not permitted). + * + * @return The series index. + */ + public int indexOf(XYSeries series) { + Args.nullNotPermitted(series, "series"); + return this.data.indexOf(series); + } + + /** + * Returns a series from the collection. + * + * @param series the series index (zero-based). + * + * @return The series. + * + * @throws IllegalArgumentException if {@code series} is not in the + * range {@code 0} to {@code getSeriesCount() - 1}. + */ + public XYSeries getSeries(int series) { + if ((series < 0) || (series >= getSeriesCount())) { + throw new IllegalArgumentException("Series index out of bounds"); + } + return (XYSeries) this.data.get(series); + } + + /** + * Returns a series from the collection. + * + * @param key the key ({@code null} not permitted). + * + * @return The series with the specified key. + * + * @throws UnknownKeyException if {@code key} is not found in the + * collection. + */ + public XYSeries getSeries(Comparable key) { + Args.nullNotPermitted(key, "key"); + for (Object item : this.data) { + XYSeries series = (XYSeries) item; + if (key.equals(series.getKey())) { + return series; + } + } + throw new UnknownKeyException("Key not found: " + key); + } + + /** + * Returns the key for a series. + * + * @param series the series index (in the range {@code 0} to + * {@code getSeriesCount() - 1}). + * + * @return The key for a series. + * + * @throws IllegalArgumentException if {@code series} is not in the + * specified range. + */ + @Override + public Comparable getSeriesKey(int series) { + // defer argument checking + return getSeries(series).getKey(); + } + + /** + * Returns the index of the series with the specified key, or -1 if no + * series has that key. + * + * @param key the key ({@code null} not permitted). + * + * @return The index. + */ + public int getSeriesIndex(Comparable key) { + Args.nullNotPermitted(key, "key"); + int seriesCount = getSeriesCount(); + for (int i = 0; i < seriesCount; i++) { + XYSeries series = (XYSeries) this.data.get(i); + if (key.equals(series.getKey())) { + return i; + } + } + return -1; + } + + /** + * Returns the number of items in the specified series. + * + * @param series the series (zero-based index). + * + * @return The item count. + * + * @throws IllegalArgumentException if {@code series} is not in the + * range {@code 0} to {@code getSeriesCount() - 1}. + */ + @Override + public int getItemCount(int series) { + // defer argument checking + return getSeries(series).getItemCount(); + } + + /** + * Returns the x-value for the specified series and item. + * + * @param series the series (zero-based index). + * @param item the item (zero-based index). + * + * @return The value. + */ + @Override + public Number getX(int series, int item) { + XYSeries s = (XYSeries) this.data.get(series); + return s.getX(item); + } + + /** + * Returns the starting X value for the specified series and item. + * + * @param series the series (zero-based index). + * @param item the item (zero-based index). + * + * @return The starting X value. + */ + @Override + public Number getStartX(int series, int item) { + return this.intervalDelegate.getStartX(series, item); + } + + /** + * Returns the ending X value for the specified series and item. + * + * @param series the series (zero-based index). + * @param item the item (zero-based index). + * + * @return The ending X value. + */ + @Override + public Number getEndX(int series, int item) { + return this.intervalDelegate.getEndX(series, item); + } + + /** + * Returns the y-value for the specified series and item. + * + * @param series the series (zero-based index). + * @param index the index of the item of interest (zero-based). + * + * @return The value (possibly {@code null}). + */ + @Override + public Number getY(int series, int index) { + XYSeries s = (XYSeries) this.data.get(series); + return s.getY(index); + } + + /** + * Returns the starting Y value for the specified series and item. + * + * @param series the series (zero-based index). + * @param item the item (zero-based index). + * + * @return The starting Y value. + */ + @Override + public Number getStartY(int series, int item) { + return getY(series, item); + } + + /** + * Returns the ending Y value for the specified series and item. + * + * @param series the series (zero-based index). + * @param item the item (zero-based index). + * + * @return The ending Y value. + */ + @Override + public Number getEndY(int series, int item) { + return getY(series, item); + } + + /** + * Tests this collection for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof XYSeriesCollection)) { + return false; + } + XYSeriesCollection that = (XYSeriesCollection) obj; + if (!this.intervalDelegate.equals(that.intervalDelegate)) { + return false; + } + return Objects.equals(this.data, that.data); + } + + /** + * Returns a clone of this instance. + * + * @return A clone. + * + * @throws CloneNotSupportedException if there is a problem. + */ + @Override + public Object clone() throws CloneNotSupportedException { + XYSeriesCollection clone = (XYSeriesCollection) super.clone(); + clone.data = (List) ObjectUtils.deepClone(this.data); + clone.intervalDelegate + = (IntervalXYDelegate) this.intervalDelegate.clone(); + return clone; + } + + /** + * Returns a hash code. + * + * @return A hash code. + */ + @Override + public int hashCode() { + int hash = 5; + hash = HashUtils.hashCode(hash, this.intervalDelegate); + hash = HashUtils.hashCode(hash, this.data); + return hash; + } + + /** + * Returns the minimum x-value in the dataset. + * + * @param includeInterval a flag that determines whether the + * x-interval is taken into account. + * + * @return The minimum value. + */ + @Override + public double getDomainLowerBound(boolean includeInterval) { + if (includeInterval) { + return this.intervalDelegate.getDomainLowerBound(includeInterval); + } + double result = Double.NaN; + int seriesCount = getSeriesCount(); + for (int s = 0; s < seriesCount; s++) { + XYSeries series = getSeries(s); + double lowX = series.getMinX(); + if (Double.isNaN(result)) { + result = lowX; + } + else { + if (!Double.isNaN(lowX)) { + result = Math.min(result, lowX); + } + } + } + return result; + } + + /** + * Returns the maximum x-value in the dataset. + * + * @param includeInterval a flag that determines whether the + * x-interval is taken into account. + * + * @return The maximum value. + */ + @Override + public double getDomainUpperBound(boolean includeInterval) { + if (includeInterval) { + return this.intervalDelegate.getDomainUpperBound(includeInterval); + } + else { + double result = Double.NaN; + int seriesCount = getSeriesCount(); + for (int s = 0; s < seriesCount; s++) { + XYSeries series = getSeries(s); + double hiX = series.getMaxX(); + if (Double.isNaN(result)) { + result = hiX; + } + else { + if (!Double.isNaN(hiX)) { + result = Math.max(result, hiX); + } + } + } + return result; + } + } + + /** + * Returns the range of the values in this dataset's domain. + * + * @param includeInterval a flag that determines whether the + * x-interval is taken into account. + * + * @return The range (or {@code null} if the dataset contains no + * values). + */ + @Override + public Range getDomainBounds(boolean includeInterval) { + if (includeInterval) { + return this.intervalDelegate.getDomainBounds(includeInterval); + } + else { + double lower = Double.POSITIVE_INFINITY; + double upper = Double.NEGATIVE_INFINITY; + int seriesCount = getSeriesCount(); + for (int s = 0; s < seriesCount; s++) { + XYSeries series = getSeries(s); + double minX = series.getMinX(); + if (!Double.isNaN(minX)) { + lower = Math.min(lower, minX); + } + double maxX = series.getMaxX(); + if (!Double.isNaN(maxX)) { + upper = Math.max(upper, maxX); + } + } + if (lower > upper) { + return null; + } + else { + return new Range(lower, upper); + } + } + } + + /** + * Returns the interval width. This is used to calculate the start and end + * x-values, if/when the dataset is used as an {@link IntervalXYDataset}. + * + * @return The interval width. + */ + public double getIntervalWidth() { + return this.intervalDelegate.getIntervalWidth(); + } + + /** + * Sets the interval width and calls {@link #fireDatasetChanged()}. + * + * @param width the width (negative values not permitted). + */ + public void setIntervalWidth(double width) { + if (width < 0.0) { + throw new IllegalArgumentException("Negative 'width' argument."); + } + this.intervalDelegate.setFixedIntervalWidth(width); + fireDatasetChanged(); + } + + /** + * Returns the interval position factor. + * + * @return The interval position factor. + */ + public double getIntervalPositionFactor() { + return this.intervalDelegate.getIntervalPositionFactor(); + } + + /** + * Sets the interval position factor. This controls where the x-value is in + * relation to the interval surrounding the x-value (0.0 means the x-value + * will be positioned at the start, 0.5 in the middle, and 1.0 at the end). + * + * @param factor the factor. + */ + public void setIntervalPositionFactor(double factor) { + this.intervalDelegate.setIntervalPositionFactor(factor); + fireDatasetChanged(); + } + + /** + * Returns whether the interval width is automatically calculated or not. + * + * @return Whether the width is automatically calculated or not. + */ + public boolean isAutoWidth() { + return this.intervalDelegate.isAutoWidth(); + } + + /** + * Sets the flag that indicates whether the interval width is automatically + * calculated or not. + * + * @param b a boolean. + */ + public void setAutoWidth(boolean b) { + this.intervalDelegate.setAutoWidth(b); + fireDatasetChanged(); + } + + /** + * Returns the range of the values in this dataset's range. + * + * @param includeInterval ignored. + * + * @return The range (or {@code null} if the dataset contains no + * values). + */ + @Override + public Range getRangeBounds(boolean includeInterval) { + double lower = Double.POSITIVE_INFINITY; + double upper = Double.NEGATIVE_INFINITY; + int seriesCount = getSeriesCount(); + for (int s = 0; s < seriesCount; s++) { + XYSeries series = getSeries(s); + double minY = series.getMinY(); + if (!Double.isNaN(minY)) { + lower = Math.min(lower, minY); + } + double maxY = series.getMaxY(); + if (!Double.isNaN(maxY)) { + upper = Math.max(upper, maxY); + } + } + if (lower > upper) { + return null; + } + else { + return new Range(lower, upper); + } + } + + /** + * Returns the minimum y-value in the dataset. + * + * @param includeInterval a flag that determines whether the + * y-interval is taken into account. + * + * @return The minimum value. + */ + @Override + public double getRangeLowerBound(boolean includeInterval) { + double result = Double.NaN; + int seriesCount = getSeriesCount(); + for (int s = 0; s < seriesCount; s++) { + XYSeries series = getSeries(s); + double lowY = series.getMinY(); + if (Double.isNaN(result)) { + result = lowY; + } + else { + if (!Double.isNaN(lowY)) { + result = Math.min(result, lowY); + } + } + } + return result; + } + + /** + * Returns the maximum y-value in the dataset. + * + * @param includeInterval a flag that determines whether the + * y-interval is taken into account. + * + * @return The maximum value. + */ + @Override + public double getRangeUpperBound(boolean includeInterval) { + double result = Double.NaN; + int seriesCount = getSeriesCount(); + for (int s = 0; s < seriesCount; s++) { + XYSeries series = getSeries(s); + double hiY = series.getMaxY(); + if (Double.isNaN(result)) { + result = hiY; + } + else { + if (!Double.isNaN(hiY)) { + result = Math.max(result, hiY); + } + } + } + return result; + } + + /** + * Receives notification that the key for one of the series in the + * collection has changed, and vetos it if the key is already present in + * the collection. + * + * @param e the event. + * + * @throws PropertyVetoException if the series name is already present in + * the collection. + */ + @Override + public void vetoableChange(PropertyChangeEvent e) throws PropertyVetoException { + // if it is not the series name, then we have no interest + if (!"Key".equals(e.getPropertyName())) { + return; + } + + // to be defensive, let's check that the source series does in fact + // belong to this collection + Series s = (Series) e.getSource(); + if (getSeriesIndex(s.getKey()) == -1) { + throw new IllegalStateException("Receiving events from a series " + + "that does not belong to this collection."); + } + // check if the new series name already exists for another series + Comparable key = (Comparable) e.getNewValue(); + if (getSeriesIndex(key) >= 0) { + throw new PropertyVetoException("Duplicate key2", e); + } + } + + /** + * Provides serialization support. + * + * @param stream the output stream. + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + } + + /** + * Provides serialization support. + * + * @param stream the input stream. + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + for (Object item : this.data) { + XYSeries series = (XYSeries) item; + series.addChangeListener(this); + } + } + +} diff --git a/src/main/java/org/jfree/data/xy/YIntervalSeries.java b/src/main/java/org/jfree/data/xy/YIntervalSeries.java index 5cbf64f81..6136ae2c3 100644 --- a/src/main/java/org/jfree/data/xy/YIntervalSeries.java +++ b/src/main/java/org/jfree/data/xy/YIntervalSeries.java @@ -38,7 +38,6 @@ import org.jfree.data.ComparableObjectItem; import org.jfree.data.ComparableObjectSeries; -import org.jfree.data.general.SeriesChangeEvent; /** * A list of (x, y, y-low, y-high) data items. @@ -74,25 +73,25 @@ public YIntervalSeries(Comparable key, boolean autoSort, } /** - * Adds a data item to the series and sends a {@link SeriesChangeEvent} to - * all registered listeners. - * - * @param x the x-value. - * @param y the y-value. - * @param yLow the lower bound of the y-interval. - * @param yHigh the upper bound of the y-interval. - */ + * Adds a data item to the series by calling + * {@link #add(YIntervalDataItem, boolean)} + * + * @param x the x-value. + * @param y the y-value. + * @param yLow the lower bound of the y-interval. + * @param yHigh the upper bound of the y-interval. + */ public void add(double x, double y, double yLow, double yHigh) { add(new YIntervalDataItem(x, y, yLow, yHigh), true); } /** - * Adds a data item to the series and, if requested, sends a - * {@link SeriesChangeEvent} to all registered listeners. - * - * @param item the data item ({@code null} not permitted). - * @param notify notify listeners? - */ + * Adds a data item to the series by calling + * {@link #add(ComparableObjectItem, boolean)} + * + * @param item the data item ({@code null} not permitted). + * @param notify notify listeners? + */ public void add(YIntervalDataItem item, boolean notify) { super.add(item, notify); } diff --git a/src/main/java/org/jfree/data/xy/YIntervalSeriesCollection.java b/src/main/java/org/jfree/data/xy/YIntervalSeriesCollection.java index 69e4bf1bb..aaa3825b7 100644 --- a/src/main/java/org/jfree/data/xy/YIntervalSeriesCollection.java +++ b/src/main/java/org/jfree/data/xy/YIntervalSeriesCollection.java @@ -43,8 +43,6 @@ import org.jfree.chart.util.Args; import org.jfree.chart.util.PublicCloneable; -import org.jfree.data.general.DatasetChangeEvent; - /** * A collection of {@link YIntervalSeries} objects. * @@ -64,8 +62,8 @@ public YIntervalSeriesCollection() { } /** - * Adds a series to the collection and sends a {@link DatasetChangeEvent} - * to all registered listeners. + * Adds a series to the collection and calls + * {@link #fireDatasetChanged()} * * @param series the series ({@code null} not permitted). */ @@ -266,8 +264,8 @@ public Number getEndY(int series, int item) { } /** - * Removes a series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes a series from the collection and calls + * {@link #fireDatasetChanged()} * * @param series the series index (zero-based). */ @@ -282,8 +280,8 @@ public void removeSeries(int series) { } /** - * Removes a series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. + * Removes a series from the collection and calls + * {@link #fireDatasetChanged()} * * @param series the series ({@code null} not permitted). */ @@ -297,9 +295,9 @@ public void removeSeries(YIntervalSeries series) { } /** - * Removes all the series from the collection and sends a - * {@link DatasetChangeEvent} to all registered listeners. - */ + * Removes all the series from the collection and calls + * {@link #fireDatasetChanged()} + */ public void removeAllSeries() { // Unregister the collection as a change listener to each series in // the collection. diff --git a/src/test/java/org/jfree/data/general/DatasetUtilsTest.java b/src/test/java/org/jfree/data/general/DatasetUtilsTest.java index 83c86332e..1e568e0d2 100644 --- a/src/test/java/org/jfree/data/general/DatasetUtilsTest.java +++ b/src/test/java/org/jfree/data/general/DatasetUtilsTest.java @@ -1,1466 +1,1464 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * --------------------- - * DatasetUtilsTest.java - * --------------------- - * (C) Copyright 2003-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.data.general; - -import java.util.*; - -import org.jfree.data.KeyToGroupMap; -import org.jfree.data.Range; -import org.jfree.data.category.CategoryDataset; -import org.jfree.data.category.DefaultCategoryDataset; -import org.jfree.data.category.DefaultIntervalCategoryDataset; -import org.jfree.data.function.Function2D; -import org.jfree.data.function.LineFunction2D; -import org.jfree.data.statistics.BoxAndWhiskerItem; -import org.jfree.data.statistics.DefaultBoxAndWhiskerXYDataset; -import org.jfree.data.statistics.DefaultMultiValueCategoryDataset; -import org.jfree.data.statistics.DefaultStatisticalCategoryDataset; -import org.jfree.data.statistics.MultiValueCategoryDataset; -import org.jfree.data.xy.DefaultIntervalXYDataset; -import org.jfree.data.xy.DefaultTableXYDataset; -import org.jfree.data.xy.DefaultXYDataset; -import org.jfree.data.xy.IntervalXYDataset; -import org.jfree.data.xy.TableXYDataset; -import org.jfree.data.xy.XYDataset; -import org.jfree.data.xy.XYIntervalSeries; -import org.jfree.data.xy.XYIntervalSeriesCollection; -import org.jfree.data.xy.XYSeries; -import org.jfree.data.xy.XYSeriesCollection; -import org.jfree.data.xy.YIntervalSeries; -import org.jfree.data.xy.YIntervalSeriesCollection; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -/** - * Tests for the {@link DatasetUtils} class. - */ -public class DatasetUtilsTest { - - private static final double EPSILON = 0.0000000001; - - /** - * Some tests to verify that Java does what I think it does! - */ - @Test - public void testJava() { - assertTrue(Double.isNaN(Math.min(1.0, Double.NaN))); - assertTrue(Double.isNaN(Math.max(1.0, Double.NaN))); - } - - /** - * Some tests for the calculatePieDatasetTotal() method. - */ - @Test - public void testCalculatePieDatasetTotal() { - DefaultPieDataset d = new DefaultPieDataset(); - assertEquals(0.0, DatasetUtils.calculatePieDatasetTotal(d), - EPSILON); - d.setValue("A", 1.0); - assertEquals(1.0, DatasetUtils.calculatePieDatasetTotal(d), - EPSILON); - d.setValue("B", 3.0); - assertEquals(4.0, DatasetUtils.calculatePieDatasetTotal(d), - EPSILON); - } - - /** - * Some tests for the findDomainBounds() method. - */ - @Test - public void testFindDomainBounds() { - XYDataset dataset = createXYDataset1(); - Range r = DatasetUtils.findDomainBounds(dataset); - assertEquals(1.0, r.getLowerBound(), EPSILON); - assertEquals(3.0, r.getUpperBound(), EPSILON); - } - - /** - * This test checks that the standard method has 'includeInterval' - * defaulting to true. - */ - @Test - public void testFindDomainBounds2() { - DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); - double[] x1 = new double[] {1.0, 2.0, 3.0}; - double[] x1Start = new double[] {0.9, 1.9, 2.9}; - double[] x1End = new double[] {1.1, 2.1, 3.1}; - double[] y1 = new double[] {4.0, 5.0, 6.0}; - double[] y1Start = new double[] {1.09, 2.09, 3.09}; - double[] y1End = new double[] {1.11, 2.11, 3.11}; - double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, - y1End}; - dataset.addSeries("S1", data1); - Range r = DatasetUtils.findDomainBounds(dataset); - assertEquals(0.9, r.getLowerBound(), EPSILON); - assertEquals(3.1, r.getUpperBound(), EPSILON); - } - - /** - * This test checks that when the 'includeInterval' flag is false, the - * bounds come from the regular x-values. - */ - @Test - public void testFindDomainBounds3() { - DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); - double[] x1 = new double[] {1.0, 2.0, 3.0}; - double[] x1Start = new double[] {0.9, 1.9, 2.9}; - double[] x1End = new double[] {1.1, 2.1, 3.1}; - double[] y1 = new double[] {4.0, 5.0, 6.0}; - double[] y1Start = new double[] {1.09, 2.09, 3.09}; - double[] y1End = new double[] {1.11, 2.11, 3.11}; - double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, - y1End}; - dataset.addSeries("S1", data1); - Range r = DatasetUtils.findDomainBounds(dataset, false); - assertEquals(1.0, r.getLowerBound(), EPSILON); - assertEquals(3.0, r.getUpperBound(), EPSILON); - } - - /** - * This test checks that the correct values are returned if the x and - * y values fall outside the intervals. - */ - @Test - public void testFindDomainBounds4() { - DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); - double[] x1 = new double[] {0.8, 3.2, 3.0}; - double[] x1Start = new double[] {0.9, 1.9, 2.9}; - double[] x1End = new double[] {1.1, 2.1, 3.1}; - double[] y1 = new double[] {4.0, 5.0, 6.0}; - double[] y1Start = new double[] {1.09, 2.09, 3.09}; - double[] y1End = new double[] {1.11, 2.11, 3.11}; - double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, - y1End}; - dataset.addSeries("S1", data1); - Range r = DatasetUtils.findDomainBounds(dataset); - assertEquals(0.8, r.getLowerBound(), EPSILON); - assertEquals(3.2, r.getUpperBound(), EPSILON); - } - - /** - * This test checks that NaN values are ignored. - */ - @Test - public void testFindDomainBounds_NaN() { - DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); - double[] x1 = new double[] {1.0, 2.0, Double.NaN}; - double[] x1Start = new double[] {0.9, 1.9, Double.NaN}; - double[] x1End = new double[] {1.1, 2.1, Double.NaN}; - double[] y1 = new double[] {4.0, 5.0, 6.0}; - double[] y1Start = new double[] {1.09, 2.09, 3.09}; - double[] y1End = new double[] {1.11, 2.11, 3.11}; - double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, - y1End}; - dataset.addSeries("S1", data1); - Range r = DatasetUtils.findDomainBounds(dataset); - assertEquals(0.9, r.getLowerBound(), EPSILON); - assertEquals(2.1, r.getUpperBound(), EPSILON); - - r = DatasetUtils.findDomainBounds(dataset, false); - assertEquals(1.0, r.getLowerBound(), EPSILON); - assertEquals(2.0, r.getUpperBound(), EPSILON); - } - - /** - * Some tests for the iterateDomainBounds() method. - */ - @Test - public void testIterateDomainBounds() { - XYDataset dataset = createXYDataset1(); - Range r = DatasetUtils.iterateDomainBounds(dataset); - assertEquals(1.0, r.getLowerBound(), EPSILON); - assertEquals(3.0, r.getUpperBound(), EPSILON); - } - - /** - * Check that NaN values in the dataset are ignored. - */ - @Test - public void testIterateDomainBounds_NaN() { - DefaultXYDataset dataset = new DefaultXYDataset(); - double[] x = new double[] {1.0, 2.0, Double.NaN, 3.0}; - double[] y = new double[] {9.0, 8.0, 7.0, 6.0}; - dataset.addSeries("S1", new double[][] {x, y}); - Range r = DatasetUtils.iterateDomainBounds(dataset); - assertEquals(1.0, r.getLowerBound(), EPSILON); - assertEquals(3.0, r.getUpperBound(), EPSILON); - } - - /** - * Check that NaN values in the IntervalXYDataset are ignored. - */ - @Test - public void testIterateDomainBounds_NaN2() { - DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); - double[] x1 = new double[] {Double.NaN, 2.0, 3.0}; - double[] x1Start = new double[] {0.9, Double.NaN, 2.9}; - double[] x1End = new double[] {1.1, Double.NaN, 3.1}; - double[] y1 = new double[] {4.0, 5.0, 6.0}; - double[] y1Start = new double[] {1.09, 2.09, 3.09}; - double[] y1End = new double[] {1.11, 2.11, 3.11}; - double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, - y1End}; - dataset.addSeries("S1", data1); - Range r = DatasetUtils.iterateDomainBounds(dataset, false); - assertEquals(2.0, r.getLowerBound(), EPSILON); - assertEquals(3.0, r.getUpperBound(), EPSILON); - r = DatasetUtils.iterateDomainBounds(dataset, true); - assertEquals(0.9, r.getLowerBound(), EPSILON); - assertEquals(3.1, r.getUpperBound(), EPSILON); - } - - /** - * Some tests for the findRangeBounds() for a CategoryDataset method. - */ - @Test - public void testFindRangeBounds_CategoryDataset() { - CategoryDataset dataset = createCategoryDataset1(); - Range r = DatasetUtils.findRangeBounds(dataset); - assertEquals(1.0, r.getLowerBound(), EPSILON); - assertEquals(6.0, r.getUpperBound(), EPSILON); - } - - /** - * Some tests for the findRangeBounds() method on an XYDataset. - */ - @Test - public void testFindRangeBounds() { - XYDataset dataset = createXYDataset1(); - Range r = DatasetUtils.findRangeBounds(dataset); - assertEquals(100.0, r.getLowerBound(), EPSILON); - assertEquals(105.0, r.getUpperBound(), EPSILON); - } - - /** - * A test for the findRangeBounds(XYDataset) method using - * an IntervalXYDataset. - */ - @Test - public void testFindRangeBounds2() { - YIntervalSeriesCollection dataset = new YIntervalSeriesCollection(); - Range r = DatasetUtils.findRangeBounds(dataset); - assertNull(r); - YIntervalSeries s1 = new YIntervalSeries("S1"); - dataset.addSeries(s1); - r = DatasetUtils.findRangeBounds(dataset); - assertNull(r); - - // try a single item - s1.add(1.0, 2.0, 1.5, 2.5); - r = DatasetUtils.findRangeBounds(dataset); - assertEquals(1.5, r.getLowerBound(), EPSILON); - assertEquals(2.5, r.getUpperBound(), EPSILON); - - r = DatasetUtils.findRangeBounds(dataset, false); - assertEquals(2.0, r.getLowerBound(), EPSILON); - assertEquals(2.0, r.getUpperBound(), EPSILON); - - // another item - s1.add(2.0, 2.0, 1.4, 2.1); - r = DatasetUtils.findRangeBounds(dataset); - assertEquals(1.4, r.getLowerBound(), EPSILON); - assertEquals(2.5, r.getUpperBound(), EPSILON); - - // another empty series - YIntervalSeries s2 = new YIntervalSeries("S2"); - dataset.addSeries(s2); - r = DatasetUtils.findRangeBounds(dataset); - assertEquals(1.4, r.getLowerBound(), EPSILON); - assertEquals(2.5, r.getUpperBound(), EPSILON); - - // an item in series 2 - s2.add(1.0, 2.0, 1.9, 2.6); - r = DatasetUtils.findRangeBounds(dataset); - assertEquals(1.4, r.getLowerBound(), EPSILON); - assertEquals(2.6, r.getUpperBound(), EPSILON); - - // what if we don't want the interval? - r = DatasetUtils.findRangeBounds(dataset, false); - assertEquals(2.0, r.getLowerBound(), EPSILON); - assertEquals(2.0, r.getUpperBound(), EPSILON); - } - - /** - * Some tests for the iterateRangeBounds() method. - */ - @Test - public void testIterateRangeBounds_CategoryDataset() { - CategoryDataset dataset = createCategoryDataset1(); - Range r = DatasetUtils.iterateRangeBounds(dataset, false); - assertEquals(1.0, r.getLowerBound(), EPSILON); - assertEquals(6.0, r.getUpperBound(), EPSILON); - } - - /** - * Some checks for the iterateRangeBounds() method. - */ - @Test - public void testIterateRangeBounds2_CategoryDataset() { - // an empty dataset should return a null range - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - Range r = DatasetUtils.iterateRangeBounds(dataset, false); - assertNull(r); - - // a dataset with a single value - dataset.addValue(1.23, "R1", "C1"); - r = DatasetUtils.iterateRangeBounds(dataset, false); - assertEquals(1.23, r.getLowerBound(), EPSILON); - assertEquals(1.23, r.getUpperBound(), EPSILON); - - // null is ignored - dataset.addValue(null, "R2", "C1"); - r = DatasetUtils.iterateRangeBounds(dataset, false); - assertEquals(1.23, r.getLowerBound(), EPSILON); - assertEquals(1.23, r.getUpperBound(), EPSILON); - - // a Double.NaN should be ignored - dataset.addValue(Double.NaN, "R2", "C1"); - r = DatasetUtils.iterateRangeBounds(dataset, false); - assertEquals(1.23, r.getLowerBound(), EPSILON); - assertEquals(1.23, r.getUpperBound(), EPSILON); - } - - /** - * Some checks for the iterateRangeBounds() method using an - * IntervalCategoryDataset. - */ - @Test - public void testIterateRangeBounds3_CategoryDataset() { - Number[][] starts = new Double[2][3]; - Number[][] ends = new Double[2][3]; - starts[0][0] = 1.0; - starts[0][1] = 2.0; - starts[0][2] = 3.0; - starts[1][0] = 11.0; - starts[1][1] = 12.0; - starts[1][2] = 13.0; - ends[0][0] = 4.0; - ends[0][1] = 5.0; - ends[0][2] = 6.0; - ends[1][0] = 16.0; - ends[1][1] = 15.0; - ends[1][2] = 14.0; - - DefaultIntervalCategoryDataset d = new DefaultIntervalCategoryDataset( - starts, ends); - Range r = DatasetUtils.iterateRangeBounds(d, false); - assertEquals(4.0, r.getLowerBound(), EPSILON); - assertEquals(16.0, r.getUpperBound(), EPSILON); - r = DatasetUtils.iterateRangeBounds(d, true); - assertEquals(1.0, r.getLowerBound(), EPSILON); - assertEquals(16.0, r.getUpperBound(), EPSILON); - } - - /** - * Some tests for the iterateRangeBounds() method. - */ - @Test - public void testIterateRangeBounds() { - XYDataset dataset = createXYDataset1(); - Range r = DatasetUtils.iterateRangeBounds(dataset); - assertEquals(100.0, r.getLowerBound(), EPSILON); - assertEquals(105.0, r.getUpperBound(), EPSILON); - } - - /** - * Check the range returned when a series contains a null value. - */ - @Test - public void testIterateRangeBounds2() { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, 1.1); - s1.add(2.0, null); - s1.add(3.0, 3.3); - XYSeriesCollection dataset = new XYSeriesCollection(s1); - Range r = DatasetUtils.iterateRangeBounds(dataset); - assertEquals(1.1, r.getLowerBound(), EPSILON); - assertEquals(3.3, r.getUpperBound(), EPSILON); - } - - /** - * Some checks for the iterateRangeBounds() method. - */ - @Test - public void testIterateRangeBounds3() { - // an empty dataset should return a null range - XYSeriesCollection dataset = new XYSeriesCollection(); - Range r = DatasetUtils.iterateRangeBounds(dataset); - assertNull(r); - XYSeries s1 = new XYSeries("S1"); - dataset.addSeries(s1); - r = DatasetUtils.iterateRangeBounds(dataset); - assertNull(r); - - // a dataset with a single value - s1.add(1.0, 1.23); - r = DatasetUtils.iterateRangeBounds(dataset); - assertEquals(1.23, r.getLowerBound(), EPSILON); - assertEquals(1.23, r.getUpperBound(), EPSILON); - - // null is ignored - s1.add(2.0, null); - r = DatasetUtils.iterateRangeBounds(dataset); - assertEquals(1.23, r.getLowerBound(), EPSILON); - assertEquals(1.23, r.getUpperBound(), EPSILON); - - // Double.NaN DOESN'T mess things up - s1.add(3.0, Double.NaN); - r = DatasetUtils.iterateRangeBounds(dataset); - assertEquals(1.23, r.getLowerBound(), EPSILON); - assertEquals(1.23, r.getUpperBound(), EPSILON); - } - - /** - * Some checks for the range bounds of a dataset that implements the - * {@link IntervalXYDataset} interface. - */ - @Test - public void testIterateRangeBounds4() { - YIntervalSeriesCollection dataset = new YIntervalSeriesCollection(); - Range r = DatasetUtils.iterateRangeBounds(dataset); - assertNull(r); - YIntervalSeries s1 = new YIntervalSeries("S1"); - dataset.addSeries(s1); - r = DatasetUtils.iterateRangeBounds(dataset); - assertNull(r); - - // try a single item - s1.add(1.0, 2.0, 1.5, 2.5); - r = DatasetUtils.iterateRangeBounds(dataset); - assertEquals(1.5, r.getLowerBound(), EPSILON); - assertEquals(2.5, r.getUpperBound(), EPSILON); - - // another item - s1.add(2.0, 2.0, 1.4, 2.1); - r = DatasetUtils.iterateRangeBounds(dataset); - assertEquals(1.4, r.getLowerBound(), EPSILON); - assertEquals(2.5, r.getUpperBound(), EPSILON); - - // another empty series - YIntervalSeries s2 = new YIntervalSeries("S2"); - dataset.addSeries(s2); - r = DatasetUtils.iterateRangeBounds(dataset); - assertEquals(1.4, r.getLowerBound(), EPSILON); - assertEquals(2.5, r.getUpperBound(), EPSILON); - - // an item in series 2 - s2.add(1.0, 2.0, 1.9, 2.6); - r = DatasetUtils.iterateRangeBounds(dataset); - assertEquals(1.4, r.getLowerBound(), EPSILON); - assertEquals(2.6, r.getUpperBound(), EPSILON); - } - - /** - * Some tests for the findMinimumDomainValue() method. - */ - @Test - public void testFindMinimumDomainValue() { - XYDataset dataset = createXYDataset1(); - Number minimum = DatasetUtils.findMinimumDomainValue(dataset); - assertEquals(1.0, minimum); - } - - /** - * Some tests for the findMaximumDomainValue() method. - */ - @Test - public void testFindMaximumDomainValue() { - XYDataset dataset = createXYDataset1(); - Number maximum = DatasetUtils.findMaximumDomainValue(dataset); - assertEquals(3.0, maximum); - } - - /** - * Some tests for the findMinimumRangeValue() method. - */ - @Test - public void testFindMinimumRangeValue() { - CategoryDataset d1 = createCategoryDataset1(); - Number min1 = DatasetUtils.findMinimumRangeValue(d1); - assertEquals(1.0, min1); - - XYDataset d2 = createXYDataset1(); - Number min2 = DatasetUtils.findMinimumRangeValue(d2); - assertEquals(100.0, min2); - } - - /** - * Some tests for the findMaximumRangeValue() method. - */ - @Test - public void testFindMaximumRangeValue() { - CategoryDataset d1 = createCategoryDataset1(); - Number max1 = DatasetUtils.findMaximumRangeValue(d1); - assertEquals(6.0, max1); - - XYDataset dataset = createXYDataset1(); - Number maximum = DatasetUtils.findMaximumRangeValue(dataset); - assertEquals(105.0, maximum); - } - - /** - * A quick test of the min and max range value methods. - */ - @Test - public void testMinMaxRange() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - dataset.addValue(100.0, "Series 1", "Type 1"); - dataset.addValue(101.1, "Series 1", "Type 2"); - Number min = DatasetUtils.findMinimumRangeValue(dataset); - assertTrue(min.doubleValue() < 100.1); - Number max = DatasetUtils.findMaximumRangeValue(dataset); - assertTrue(max.doubleValue() > 101.0); - } - - /** - * A test to reproduce bug report 803660. - */ - @Test - public void test803660() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - dataset.addValue(100.0, "Series 1", "Type 1"); - dataset.addValue(101.1, "Series 1", "Type 2"); - Number n = DatasetUtils.findMaximumRangeValue(dataset); - assertTrue(n.doubleValue() > 101.0); - } - - /** - * A simple test for the cumulative range calculation. The sequence of - * "cumulative" values are considered to be { 0.0, 10.0, 25.0, 18.0 } so - * the range should be 0.0 -> 25.0. - */ - @Test - public void testCumulativeRange1() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - dataset.addValue(10.0, "Series 1", "Start"); - dataset.addValue(15.0, "Series 1", "Delta 1"); - dataset.addValue(-7.0, "Series 1", "Delta 2"); - Range range = DatasetUtils.findCumulativeRangeBounds(dataset); - assertEquals(0.0, range.getLowerBound(), 0.00000001); - assertEquals(25.0, range.getUpperBound(), 0.00000001); - } - - /** - * A further test for the cumulative range calculation. - */ - @Test - public void testCumulativeRange2() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - dataset.addValue(-21.4, "Series 1", "Start Value"); - dataset.addValue(11.57, "Series 1", "Delta 1"); - dataset.addValue(3.51, "Series 1", "Delta 2"); - dataset.addValue(-12.36, "Series 1", "Delta 3"); - dataset.addValue(3.39, "Series 1", "Delta 4"); - dataset.addValue(38.68, "Series 1", "Delta 5"); - dataset.addValue(-43.31, "Series 1", "Delta 6"); - dataset.addValue(-29.59, "Series 1", "Delta 7"); - dataset.addValue(35.30, "Series 1", "Delta 8"); - dataset.addValue(5.0, "Series 1", "Delta 9"); - Range range = DatasetUtils.findCumulativeRangeBounds(dataset); - assertEquals(-49.51, range.getLowerBound(), 0.00000001); - assertEquals(23.39, range.getUpperBound(), 0.00000001); - } - - /** - * A further test for the cumulative range calculation. - */ - @Test - public void testCumulativeRange3() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - dataset.addValue(15.76, "Product 1", "Labour"); - dataset.addValue(8.66, "Product 1", "Administration"); - dataset.addValue(4.71, "Product 1", "Marketing"); - dataset.addValue(3.51, "Product 1", "Distribution"); - dataset.addValue(32.64, "Product 1", "Total Expense"); - Range range = DatasetUtils.findCumulativeRangeBounds(dataset); - assertEquals(0.0, range.getLowerBound(), EPSILON); - assertEquals(65.28, range.getUpperBound(), EPSILON); - } - - /** - * Check that the findCumulativeRangeBounds() method ignores Double.NaN - * values. - */ - @Test - public void testCumulativeRange_NaN() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - dataset.addValue(10.0, "Series 1", "Start"); - dataset.addValue(15.0, "Series 1", "Delta 1"); - dataset.addValue(Double.NaN, "Series 1", "Delta 2"); - Range range = DatasetUtils.findCumulativeRangeBounds(dataset); - assertEquals(0.0, range.getLowerBound(), EPSILON); - assertEquals(25.0, range.getUpperBound(), EPSILON); - } - - /** - * Test the creation of a dataset from an array. - */ - @Test - public void testCreateCategoryDataset1() { - String[] rowKeys = {"R1", "R2", "R3"}; - String[] columnKeys = {"C1", "C2"}; - double[][] data = new double[3][]; - data[0] = new double[] {1.1, 1.2}; - data[1] = new double[] {2.1, 2.2}; - data[2] = new double[] {3.1, 3.2}; - CategoryDataset dataset = DatasetUtils.createCategoryDataset( - rowKeys, columnKeys, data); - assertEquals(3, dataset.getRowCount()); - assertEquals(2, dataset.getColumnCount()); - } - - /** - * Test the creation of a dataset from an array. This time is should fail - * because the array dimensions are around the wrong way. - */ - @Test - public void testCreateCategoryDataset2() { - boolean pass = false; - String[] rowKeys = {"R1", "R2", "R3"}; - String[] columnKeys = {"C1", "C2"}; - double[][] data = new double[2][]; - data[0] = new double[] {1.1, 1.2, 1.3}; - data[1] = new double[] {2.1, 2.2, 2.3}; - CategoryDataset dataset = null; - try { - dataset = DatasetUtils.createCategoryDataset(rowKeys, - columnKeys, data); - } - catch (IllegalArgumentException e) { - pass = true; // got it! - } - assertTrue(pass); - assertNull(dataset); - } - - /** - * Test for a bug reported in the forum: - * - * http://www.jfree.org/phpBB2/viewtopic.php?t=7903 - */ - @Test - public void testMaximumStackedRangeValue() { - double v1 = 24.3; - double v2 = 14.2; - double v3 = 33.2; - double v4 = 32.4; - double v5 = 26.3; - double v6 = 22.6; - Number answer = Math.max(v1 + v2 + v3, v4 + v5 + v6); - DefaultCategoryDataset d = new DefaultCategoryDataset(); - d.addValue(v1, "Row 0", "Column 0"); - d.addValue(v2, "Row 1", "Column 0"); - d.addValue(v3, "Row 2", "Column 0"); - d.addValue(v4, "Row 0", "Column 1"); - d.addValue(v5, "Row 1", "Column 1"); - d.addValue(v6, "Row 2", "Column 1"); - Number max = DatasetUtils.findMaximumStackedRangeValue(d); - assertEquals(max, answer); - } - - /** - * Some checks for the findStackedRangeBounds() method. - */ - @Test - public void testFindStackedRangeBounds_CategoryDataset1() { - CategoryDataset d1 = createCategoryDataset1(); - Range r = DatasetUtils.findStackedRangeBounds(d1); - assertEquals(0.0, r.getLowerBound(), EPSILON); - assertEquals(15.0, r.getUpperBound(), EPSILON); - - d1 = createCategoryDataset2(); - r = DatasetUtils.findStackedRangeBounds(d1); - assertEquals(-2.0, r.getLowerBound(), EPSILON); - assertEquals(2.0, r.getUpperBound(), EPSILON); - } - - /** - * Some checks for the findStackedRangeBounds() method. - */ - @Test - public void testFindStackedRangeBounds_CategoryDataset2() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - Range r = DatasetUtils.findStackedRangeBounds(dataset); - assertNull(r); - - dataset.addValue(5.0, "R1", "C1"); - r = DatasetUtils.findStackedRangeBounds(dataset, 3.0); - assertEquals(3.0, r.getLowerBound(), EPSILON); - assertEquals(8.0, r.getUpperBound(), EPSILON); - - dataset.addValue(-1.0, "R2", "C1"); - r = DatasetUtils.findStackedRangeBounds(dataset, 3.0); - assertEquals(2.0, r.getLowerBound(), EPSILON); - assertEquals(8.0, r.getUpperBound(), EPSILON); - - dataset.addValue(null, "R3", "C1"); - r = DatasetUtils.findStackedRangeBounds(dataset, 3.0); - assertEquals(2.0, r.getLowerBound(), EPSILON); - assertEquals(8.0, r.getUpperBound(), EPSILON); - - dataset.addValue(Double.NaN, "R4", "C1"); - r = DatasetUtils.findStackedRangeBounds(dataset, 3.0); - assertEquals(2.0, r.getLowerBound(), EPSILON); - assertEquals(8.0, r.getUpperBound(), EPSILON); - } - - /** - * Some checks for the findStackedRangeBounds(CategoryDataset, - * KeyToGroupMap) method. - */ - @Test - public void testFindStackedRangeBounds_CategoryDataset3() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - KeyToGroupMap map = new KeyToGroupMap("Group A"); - Range r = DatasetUtils.findStackedRangeBounds(dataset, map); - assertNull(r); - - dataset.addValue(1.0, "R1", "C1"); - dataset.addValue(2.0, "R2", "C1"); - dataset.addValue(3.0, "R3", "C1"); - dataset.addValue(4.0, "R4", "C1"); - - map.mapKeyToGroup("R1", "Group A"); - map.mapKeyToGroup("R2", "Group A"); - map.mapKeyToGroup("R3", "Group B"); - map.mapKeyToGroup("R4", "Group B"); - - r = DatasetUtils.findStackedRangeBounds(dataset, map); - assertEquals(0.0, r.getLowerBound(), EPSILON); - assertEquals(7.0, r.getUpperBound(), EPSILON); - - dataset.addValue(null, "R5", "C1"); - r = DatasetUtils.findStackedRangeBounds(dataset, map); - assertEquals(0.0, r.getLowerBound(), EPSILON); - assertEquals(7.0, r.getUpperBound(), EPSILON); - - dataset.addValue(Double.NaN, "R6", "C1"); - r = DatasetUtils.findStackedRangeBounds(dataset, map); - assertEquals(0.0, r.getLowerBound(), EPSILON); - assertEquals(7.0, r.getUpperBound(), EPSILON); - } - - /** - * Some checks for the findStackedRangeBounds() method. - */ - @Test - public void testFindStackedRangeBoundsForTableXYDataset1() { - TableXYDataset d2 = createTableXYDataset1(); - Range r = DatasetUtils.findStackedRangeBounds(d2); - assertEquals(-2.0, r.getLowerBound(), EPSILON); - assertEquals(2.0, r.getUpperBound(), EPSILON); - } - - /** - * Some checks for the findStackedRangeBounds() method. - */ - @Test - public void testFindStackedRangeBoundsForTableXYDataset2() { - DefaultTableXYDataset d = new DefaultTableXYDataset(); - Range r = DatasetUtils.findStackedRangeBounds(d); - assertEquals(r, new Range(0.0, 0.0)); - } - - /** - * Tests the stacked range extent calculation. - */ - @Test - public void testStackedRangeWithMap() { - CategoryDataset d = createCategoryDataset1(); - KeyToGroupMap map = new KeyToGroupMap("G0"); - map.mapKeyToGroup("R2", "G1"); - Range r = DatasetUtils.findStackedRangeBounds(d, map); - assertEquals(0.0, r.getLowerBound(), EPSILON); - assertEquals(9.0, r.getUpperBound(), EPSILON); - } - - /** - * Some checks for the isEmptyOrNull(XYDataset) method. - */ - @Test - public void testIsEmptyOrNullXYDataset() { - XYSeriesCollection dataset = null; - assertTrue(DatasetUtils.isEmptyOrNull(dataset)); - dataset = new XYSeriesCollection(); - assertTrue(DatasetUtils.isEmptyOrNull(dataset)); - XYSeries s1 = new XYSeries("S1"); - dataset.addSeries(s1); - assertTrue(DatasetUtils.isEmptyOrNull(dataset)); - s1.add(1.0, 2.0); - assertFalse(DatasetUtils.isEmptyOrNull(dataset)); - s1.clear(); - assertTrue(DatasetUtils.isEmptyOrNull(dataset)); - } - - /** - * Some checks for the limitPieDataset() methods. - */ - @Test - public void testLimitPieDataset() { - - // check that empty dataset is handled OK - DefaultPieDataset d1 = new DefaultPieDataset(); - PieDataset d2 = DatasetUtils.createConsolidatedPieDataset(d1, - "Other", 0.05); - assertEquals(0, d2.getItemCount()); - - // check that minItem limit is observed - d1.setValue("Item 1", 1.0); - d1.setValue("Item 2", 49.50); - d1.setValue("Item 3", 49.50); - d2 = DatasetUtils.createConsolidatedPieDataset(d1, "Other", 0.05); - assertEquals(3, d2.getItemCount()); - assertEquals("Item 1", d2.getKey(0)); - assertEquals("Item 2", d2.getKey(1)); - assertEquals("Item 3", d2.getKey(2)); - - // check that minItem limit is observed - d1.setValue("Item 4", 1.0); - d2 = DatasetUtils.createConsolidatedPieDataset(d1, "Other", 0.05, - 2); - - // and that simple aggregation works - assertEquals(3, d2.getItemCount()); - assertEquals("Item 2", d2.getKey(0)); - assertEquals("Item 3", d2.getKey(1)); - assertEquals("Other", d2.getKey(2)); - assertEquals(2.0, d2.getValue("Other")); - - } - - /** - * Some checks for the sampleFunction2D() method. - */ - @Test - public void testSampleFunction2D() { - Function2D f = new LineFunction2D(0, 1); - XYDataset dataset = DatasetUtils.sampleFunction2D(f, 0.0, 1.0, 2, - "S1"); - assertEquals(1, dataset.getSeriesCount()); - assertEquals("S1", dataset.getSeriesKey(0)); - assertEquals(2, dataset.getItemCount(0)); - assertEquals(0.0, dataset.getXValue(0, 0), EPSILON); - assertEquals(0.0, dataset.getYValue(0, 0), EPSILON); - assertEquals(1.0, dataset.getXValue(0, 1), EPSILON); - assertEquals(1.0, dataset.getYValue(0, 1), EPSILON); - } - - /** - * A simple check for the findMinimumStackedRangeValue() method. - */ - @Test - public void testFindMinimumStackedRangeValue() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - - // an empty dataset should return a null max - Number min = DatasetUtils.findMinimumStackedRangeValue(dataset); - assertNull(min); - - dataset.addValue(1.0, "R1", "C1"); - min = DatasetUtils.findMinimumStackedRangeValue(dataset); - assertEquals(0.0, min.doubleValue(), EPSILON); - - dataset.addValue(2.0, "R2", "C1"); - min = DatasetUtils.findMinimumStackedRangeValue(dataset); - assertEquals(0.0, min.doubleValue(), EPSILON); - - dataset.addValue(-3.0, "R3", "C1"); - min = DatasetUtils.findMinimumStackedRangeValue(dataset); - assertEquals(-3.0, min.doubleValue(), EPSILON); - - dataset.addValue(Double.NaN, "R4", "C1"); - min = DatasetUtils.findMinimumStackedRangeValue(dataset); - assertEquals(-3.0, min.doubleValue(), EPSILON); - } - - /** - * A simple check for the findMaximumStackedRangeValue() method. - */ - @Test - public void testFindMinimumStackedRangeValue2() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - dataset.addValue(-1.0, "R1", "C1"); - Number min = DatasetUtils.findMinimumStackedRangeValue(dataset); - assertEquals(-1.0, min.doubleValue(), EPSILON); - - dataset.addValue(-2.0, "R2", "C1"); - min = DatasetUtils.findMinimumStackedRangeValue(dataset); - assertEquals(-3.0, min.doubleValue(), EPSILON); - } - - /** - * A simple check for the findMaximumStackedRangeValue() method. - */ - @Test - public void testFindMaximumStackedRangeValue() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - - // an empty dataset should return a null max - Number max = DatasetUtils.findMaximumStackedRangeValue(dataset); - assertNull(max); - - dataset.addValue(1.0, "R1", "C1"); - max = DatasetUtils.findMaximumStackedRangeValue(dataset); - assertEquals(1.0, max.doubleValue(), EPSILON); - - dataset.addValue(2.0, "R2", "C1"); - max = DatasetUtils.findMaximumStackedRangeValue(dataset); - assertEquals(3.0, max.doubleValue(), EPSILON); - - dataset.addValue(-3.0, "R3", "C1"); - max = DatasetUtils.findMaximumStackedRangeValue(dataset); - assertEquals(3.0, max.doubleValue(), EPSILON); - - dataset.addValue(Double.NaN, "R4", "C1"); - max = DatasetUtils.findMaximumStackedRangeValue(dataset); - assertEquals(3.0, max.doubleValue(), EPSILON); - } - - /** - * A simple check for the findMaximumStackedRangeValue() method. - */ - @Test - public void testFindMaximumStackedRangeValue2() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - dataset.addValue(-1.0, "R1", "C1"); - Number max = DatasetUtils.findMaximumStackedRangeValue(dataset); - assertEquals(0.0, max.doubleValue(), EPSILON); - - dataset.addValue(-2.0, "R2", "C1"); - max = DatasetUtils.findMaximumStackedRangeValue(dataset); - assertEquals(0.0, max.doubleValue(), EPSILON); - } - - /** - * Creates a dataset for testing. - * - * @return A dataset. - */ - private CategoryDataset createCategoryDataset1() { - DefaultCategoryDataset result = new DefaultCategoryDataset(); - result.addValue(1.0, "R0", "C0"); - result.addValue(1.0, "R1", "C0"); - result.addValue(1.0, "R2", "C0"); - result.addValue(4.0, "R0", "C1"); - result.addValue(5.0, "R1", "C1"); - result.addValue(6.0, "R2", "C1"); - return result; - } - - /** - * Creates a dataset for testing. - * - * @return A dataset. - */ - private CategoryDataset createCategoryDataset2() { - DefaultCategoryDataset result = new DefaultCategoryDataset(); - result.addValue(1.0, "R0", "C0"); - result.addValue(-2.0, "R1", "C0"); - result.addValue(2.0, "R0", "C1"); - result.addValue(-1.0, "R1", "C1"); - return result; - } - - - /** - * Creates a dataset for testing. - * - * @return A dataset. - */ - private XYDataset createXYDataset1() { - XYSeries series1 = new XYSeries("S1"); - series1.add(1.0, 100.0); - series1.add(2.0, 101.0); - series1.add(3.0, 102.0); - XYSeries series2 = new XYSeries("S2"); - series2.add(1.0, 103.0); - series2.add(2.0, null); - series2.add(3.0, 105.0); - XYSeriesCollection result = new XYSeriesCollection(); - result.addSeries(series1); - result.addSeries(series2); - result.setIntervalWidth(0.0); - return result; - } - - /** - * Creates a sample dataset for testing purposes. - * - * @return A sample dataset. - */ - private TableXYDataset createTableXYDataset1() { - DefaultTableXYDataset dataset = new DefaultTableXYDataset(); - - XYSeries s1 = new XYSeries("Series 1", true, false); - s1.add(1.0, 1.0); - s1.add(2.0, 2.0); - dataset.addSeries(s1); - - XYSeries s2 = new XYSeries("Series 2", true, false); - s2.add(1.0, -2.0); - s2.add(2.0, -1.0); - dataset.addSeries(s2); - - return dataset; - } - - /** - * This test checks that the correct values are returned if the x-values - * fall outside the intervals (it is not required that they do). - */ - @Test - public void testIterateToFindDomainBounds_IntervalXYDataset() { - DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); - double[] x1 = new double[] {0.8, 3.2, 3.0}; - double[] x1Start = new double[] {0.9, 1.9, 2.9}; - double[] x1End = new double[] {1.1, 2.1, 3.1}; - double[] y1 = new double[] {4.0, 5.0, 6.0}; - double[] y1Start = new double[] {1.09, 2.09, 3.09}; - double[] y1End = new double[] {1.11, 2.11, 3.11}; - double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, - y1End}; - dataset.addSeries("S1", data1); - Range r = DatasetUtils.iterateToFindDomainBounds(dataset, - Collections.singletonList("S1"), true); - assertEquals(0.8, r.getLowerBound(), EPSILON); - assertEquals(3.2, r.getUpperBound(), EPSILON); - } - - /** - * This test checks that the correct values are returned if the y-values - * fall outside the intervals (it is not required that they do). - */ - @Test - public void testIterateToFindRangeBounds_IntervalXYDataset() { - DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); - double[] x1 = new double[] {0.8, 3.2, 3.0}; - double[] x1Start = new double[] {0.9, 1.9, 2.9}; - double[] x1End = new double[] {1.1, 2.1, 3.1}; - double[] y1 = new double[] {4.0, -5.0, 6.0}; - double[] y1Start = new double[] {1.09, 2.09, 3.09}; - double[] y1End = new double[] {1.11, 2.11, 3.11}; - double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, - y1End}; - dataset.addSeries("S1", data1); - Range r = DatasetUtils.iterateToFindRangeBounds(dataset, - Collections.singletonList("S1"), new Range(0.0, 4.0), true); - assertEquals(-5.0, r.getLowerBound(), EPSILON); - assertEquals(6.0, r.getUpperBound(), EPSILON); - } - - /** - * Some checks for the iteratorToFindRangeBounds(XYDataset...) method. - */ - @Test - public void testIterateToFindRangeBounds1_XYDataset() { - // null dataset throws IllegalArgumentException - boolean pass = false; - try { - DatasetUtils.iterateToFindRangeBounds(null, new ArrayList(), - new Range(0.0, 1.0), true); - } - catch (IllegalArgumentException e) { - pass = true; - } - assertTrue(pass); - - // null list throws IllegalArgumentException - pass = false; - try { - DatasetUtils.iterateToFindRangeBounds(new XYSeriesCollection(), - null, new Range(0.0, 1.0), true); - } - catch (IllegalArgumentException e) { - pass = true; - } - assertTrue(pass); - - // null range throws IllegalArgumentException - pass = false; - try { - DatasetUtils.iterateToFindRangeBounds(new XYSeriesCollection(), - new ArrayList(), null, true); - } - catch (IllegalArgumentException e) { - pass = true; - } - assertTrue(pass); - } - - /** - * Some tests for the iterateToFindRangeBounds() method. - */ - @Test - public void testIterateToFindRangeBounds2_XYDataset() { - List visibleSeriesKeys = new ArrayList<>(); - Range xRange = new Range(0.0, 10.0); - - // empty dataset returns null - XYSeriesCollection dataset = new XYSeriesCollection(); - Range r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false); - assertNull(r); - - // add an empty series - XYSeries s1 = new XYSeries("A"); - dataset.addSeries(s1); - visibleSeriesKeys.add("A"); - r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false); - assertNull(r); - - // check a null value - s1.add(1.0, null); - r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false); - assertNull(r); - - // check a NaN - s1.add(2.0, Double.NaN); - r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false); - assertNull(r); - - // check a regular value - s1.add(3.0, 5.0); - r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false); - assertEquals(new Range(5.0, 5.0), r); - - // check another regular value - s1.add(4.0, 6.0); - r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false); - assertEquals(new Range(5.0, 6.0), r); - - // add a second series - XYSeries s2 = new XYSeries("B"); - dataset.addSeries(s2); - r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false); - assertEquals(new Range(5.0, 6.0), r); - visibleSeriesKeys.add("B"); - r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false); - assertEquals(new Range(5.0, 6.0), r); - - // add a value to the second series - s2.add(5.0, 15.0); - r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false); - assertEquals(new Range(5.0, 15.0), r); - - // add a value that isn't in the xRange - s2.add(15.0, 150.0); - r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false); - assertEquals(new Range(5.0, 15.0), r); - - r = DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, new Range(0.0, 20.0), false); - assertEquals(new Range(5.0, 150.0), r); - } - - /** - * Some checks for the iterateToFindRangeBounds() method when applied to - * a BoxAndWhiskerXYDataset. - */ - @Test - public void testIterateToFindRangeBounds_BoxAndWhiskerXYDataset() { - DefaultBoxAndWhiskerXYDataset dataset - = new DefaultBoxAndWhiskerXYDataset("Series 1"); - List visibleSeriesKeys = new ArrayList<>(); - visibleSeriesKeys.add("Series 1"); - Range xRange = new Range(Double.NEGATIVE_INFINITY, - Double.POSITIVE_INFINITY); - assertNull(DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false)); - - dataset.add(new Date(50L), new BoxAndWhiskerItem(5.0, 4.9, 2.0, 8.0, - 1.0, 9.0, 0.0, 10.0, new ArrayList())); - assertEquals(new Range(5.0, 5.0), - DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, false)); - assertEquals(new Range(1.0, 9.0), - DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, xRange, true)); - } - - /** - * Some checks for the iterateToFindRangeBounds(CategoryDataset...) - * method. - */ - @Test - public void testIterateToFindRangeBounds_StatisticalCategoryDataset() { - DefaultStatisticalCategoryDataset dataset - = new DefaultStatisticalCategoryDataset(); - List visibleSeriesKeys = new ArrayList<>(); - assertNull(DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, false)); - dataset.add(1.0, 0.5, "R1", "C1"); - visibleSeriesKeys.add("R1"); - assertEquals(new Range(1.0, 1.0), - DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, false)); - assertEquals(new Range(0.5, 1.5), - DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, true)); - } - - /** - * Some checks for the iterateToFindRangeBounds(CategoryDataset...) method - * with a {@link MultiValueCategoryDataset}. - */ - @Test - public void testIterateToFindRangeBounds_MultiValueCategoryDataset() { - DefaultMultiValueCategoryDataset dataset - = new DefaultMultiValueCategoryDataset(); - List visibleSeriesKeys = new ArrayList<>(); - assertNull(DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, true)); - List values = Collections.singletonList(1.0); - dataset.add(values, "R1", "C1"); - visibleSeriesKeys.add("R1"); - assertEquals(new Range(1.0, 1.0), - DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, true)); - - values = Arrays.asList(2.0, 3.0); - dataset.add(values, "R1", "C2"); - assertEquals(new Range(1.0, 3.0), - DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, true)); - - values = Arrays.asList(-1.0, -2.0); - dataset.add(values, "R2", "C1"); - assertEquals(new Range(1.0, 3.0), - DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, true)); - visibleSeriesKeys.add("R2"); - assertEquals(new Range(-2.0, 3.0), - DatasetUtils.iterateToFindRangeBounds(dataset, - visibleSeriesKeys, true)); - } - - /** - * Some checks for the iterateRangeBounds() method when passed an - * IntervalCategoryDataset. - */ - @Test - public void testIterateRangeBounds_IntervalCategoryDataset() { - TestIntervalCategoryDataset d = new TestIntervalCategoryDataset(); - d.addItem(1.0, 2.0, 3.0, "R1", "C1"); - assertEquals(new Range(1.0, 3.0), - DatasetUtils.iterateRangeBounds(d)); - - d = new TestIntervalCategoryDataset(); - d.addItem(2.5, 2.0, 3.0, "R1", "C1"); - assertEquals(new Range(2.0, 3.0), - DatasetUtils.iterateRangeBounds(d)); - - d = new TestIntervalCategoryDataset(); - d.addItem(4.0, 2.0, 3.0, "R1", "C1"); - assertEquals(new Range(2.0, 4.0), - DatasetUtils.iterateRangeBounds(d)); - - d = new TestIntervalCategoryDataset(); - d.addItem(null, 2.0, 3.0, "R1", "C1"); - assertEquals(new Range(2.0, 3.0), - DatasetUtils.iterateRangeBounds(d)); - - // try some nulls - d = new TestIntervalCategoryDataset(); - d.addItem(null, null, null, "R1", "C1"); - assertNull(DatasetUtils.iterateRangeBounds(d)); - - d = new TestIntervalCategoryDataset(); - d.addItem(1.0, null, null, "R1", "C1"); - assertEquals(new Range(1.0, 1.0), - DatasetUtils.iterateRangeBounds(d)); - - d = new TestIntervalCategoryDataset(); - d.addItem(null, 1.0, null, "R1", "C1"); - assertEquals(new Range(1.0, 1.0), - DatasetUtils.iterateRangeBounds(d)); - - d = new TestIntervalCategoryDataset(); - d.addItem(null, null, 1.0, "R1", "C1"); - assertEquals(new Range(1.0, 1.0), - DatasetUtils.iterateRangeBounds(d)); - } - - /** - * A test for bug 2849731. - */ - @Test - public void testBug2849731() { - TestIntervalCategoryDataset d = new TestIntervalCategoryDataset(); - d.addItem(2.5, 2.0, 3.0, "R1", "C1"); - d.addItem(4.0, null, null, "R2", "C1"); - assertEquals(new Range(2.0, 4.0), - DatasetUtils.iterateRangeBounds(d)); - } - - /** - * Another test for bug 2849731. - */ - @Test - public void testBug2849731_2() { - XYIntervalSeriesCollection d = new XYIntervalSeriesCollection(); - XYIntervalSeries s = new XYIntervalSeries("S1"); - s.add(1.0, Double.NaN, Double.NaN, Double.NaN, 1.5, Double.NaN); - d.addSeries(s); - Range r = DatasetUtils.iterateDomainBounds(d); - assertEquals(1.0, r.getLowerBound(), EPSILON); - assertEquals(1.0, r.getUpperBound(), EPSILON); - - s.add(1.0, 1.5, Double.NaN, Double.NaN, 1.5, Double.NaN); - r = DatasetUtils.iterateDomainBounds(d); - assertEquals(1.0, r.getLowerBound(), EPSILON); - assertEquals(1.5, r.getUpperBound(), EPSILON); - - s.add(1.0, Double.NaN, 0.5, Double.NaN, 1.5, Double.NaN); - r = DatasetUtils.iterateDomainBounds(d); - assertEquals(0.5, r.getLowerBound(), EPSILON); - assertEquals(1.5, r.getUpperBound(), EPSILON); - } - /** - * Yet another test for bug 2849731. - */ - @Test - public void testBug2849731_3() { - XYIntervalSeriesCollection d = new XYIntervalSeriesCollection(); - XYIntervalSeries s = new XYIntervalSeries("S1"); - s.add(1.0, Double.NaN, Double.NaN, 1.5, Double.NaN, Double.NaN); - d.addSeries(s); - Range r = DatasetUtils.iterateRangeBounds(d); - assertEquals(1.5, r.getLowerBound(), EPSILON); - assertEquals(1.5, r.getUpperBound(), EPSILON); - - s.add(1.0, 1.5, Double.NaN, Double.NaN, Double.NaN, 2.5); - r = DatasetUtils.iterateRangeBounds(d); - assertEquals(1.5, r.getLowerBound(), EPSILON); - assertEquals(2.5, r.getUpperBound(), EPSILON); - - s.add(1.0, Double.NaN, 0.5, Double.NaN, 3.5, Double.NaN); - r = DatasetUtils.iterateRangeBounds(d); - assertEquals(1.5, r.getLowerBound(), EPSILON); - assertEquals(3.5, r.getUpperBound(), EPSILON); - } - - /** - * Check the findYValue() method with a dataset that is in ascending order - * of x-values. - */ - @Test - public void testFindYValue() { - XYSeries series = new XYSeries("S1"); - XYSeriesCollection dataset = new XYSeriesCollection(series); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 100.0))); - - series.add(1.0, 5.0); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); - assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 2.0))); - - series.add(2.0, 10.0); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); - assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); - assertEquals(6.25, DatasetUtils.findYValue(dataset, 0, 1.25), EPSILON); - assertEquals(7.5, DatasetUtils.findYValue(dataset, 0, 1.5), EPSILON); - assertEquals(10.0, DatasetUtils.findYValue(dataset, 0, 2.0), EPSILON); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 3.0))); - } - - /** - * Check the findYValue() method with a dataset that is not sorted. - */ - @Test - public void testFindYValueNonSorted() { - XYSeries series = new XYSeries("S1", false); - XYSeriesCollection dataset = new XYSeriesCollection(series); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 100.0))); - - series.add(1.0, 5.0); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); - assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 2.0))); - - series.add(0.0, 10.0); - series.add(4.0, 20.0); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, -0.5))); - assertEquals(10.0, DatasetUtils.findYValue(dataset, 0, 0.0), EPSILON); - assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); - assertEquals(15.0, DatasetUtils.findYValue(dataset, 0, 2.0), EPSILON); - assertEquals(20.0, DatasetUtils.findYValue(dataset, 0, 4.0), EPSILON); - assertEquals(17.5, DatasetUtils.findYValue(dataset, 0, 3.0), EPSILON); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 5.0))); - } - - /** - * Check the findYValue() method with a dataset that allows duplicate - * values. - */ - @Test - public void testFindYValueWithDuplicates() { - XYSeries series = new XYSeries("S1", true, true); - XYSeriesCollection dataset = new XYSeriesCollection(series); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 100.0))); - - series.add(1.0, 5.0); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); - assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 2.0))); - - series.add(1.0, 10.0); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); - assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 2.0))); - - series.add(2.0, 10.0); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); - assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); - assertEquals(10.0, DatasetUtils.findYValue(dataset, 0, 1.25), EPSILON); - assertEquals(10.0, DatasetUtils.findYValue(dataset, 0, 1.5), EPSILON); - assertEquals(10.0, DatasetUtils.findYValue(dataset, 0, 2.0), EPSILON); - assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 3.0))); - } - -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * --------------------- + * DatasetUtilsTest.java + * --------------------- + * (C) Copyright 2003-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.data.general; + +import java.util.*; + +import org.jfree.data.KeyToGroupMap; +import org.jfree.data.Range; +import org.jfree.data.category.CategoryDataset; +import org.jfree.data.category.DefaultCategoryDataset; +import org.jfree.data.category.DefaultIntervalCategoryDataset; +import org.jfree.data.function.Function2D; +import org.jfree.data.function.LineFunction2D; +import org.jfree.data.statistics.BoxAndWhiskerItem; +import org.jfree.data.statistics.DefaultBoxAndWhiskerXYDataset; +import org.jfree.data.statistics.DefaultMultiValueCategoryDataset; +import org.jfree.data.statistics.DefaultStatisticalCategoryDataset; +import org.jfree.data.xy.DefaultIntervalXYDataset; +import org.jfree.data.xy.DefaultTableXYDataset; +import org.jfree.data.xy.DefaultXYDataset; +import org.jfree.data.xy.TableXYDataset; +import org.jfree.data.xy.XYDataset; +import org.jfree.data.xy.XYIntervalSeries; +import org.jfree.data.xy.XYIntervalSeriesCollection; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; +import org.jfree.data.xy.YIntervalSeries; +import org.jfree.data.xy.YIntervalSeriesCollection; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Tests for the {@link DatasetUtils} class. + */ +public class DatasetUtilsTest { + + private static final double EPSILON = 0.0000000001; + + /** + * Some tests to verify that Java does what I think it does! + */ + @Test + public void testJava() { + assertTrue(Double.isNaN(Math.min(1.0, Double.NaN))); + assertTrue(Double.isNaN(Math.max(1.0, Double.NaN))); + } + + /** + * Some tests for the calculatePieDatasetTotal() method. + */ + @Test + public void testCalculatePieDatasetTotal() { + DefaultPieDataset d = new DefaultPieDataset(); + assertEquals(0.0, DatasetUtils.calculatePieDatasetTotal(d), + EPSILON); + d.setValue("A", 1.0); + assertEquals(1.0, DatasetUtils.calculatePieDatasetTotal(d), + EPSILON); + d.setValue("B", 3.0); + assertEquals(4.0, DatasetUtils.calculatePieDatasetTotal(d), + EPSILON); + } + + /** + * Some tests for the findDomainBounds() method. + */ + @Test + public void testFindDomainBounds() { + XYDataset dataset = createXYDataset1(); + Range r = DatasetUtils.findDomainBounds(dataset); + assertEquals(1.0, r.getLowerBound(), EPSILON); + assertEquals(3.0, r.getUpperBound(), EPSILON); + } + + /** + * This test checks that the standard method has 'includeInterval' + * defaulting to true. + */ + @Test + public void testFindDomainBounds2() { + DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); + double[] x1 = new double[] {1.0, 2.0, 3.0}; + double[] x1Start = new double[] {0.9, 1.9, 2.9}; + double[] x1End = new double[] {1.1, 2.1, 3.1}; + double[] y1 = new double[] {4.0, 5.0, 6.0}; + double[] y1Start = new double[] {1.09, 2.09, 3.09}; + double[] y1End = new double[] {1.11, 2.11, 3.11}; + double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, + y1End}; + dataset.addSeries("S1", data1); + Range r = DatasetUtils.findDomainBounds(dataset); + assertEquals(0.9, r.getLowerBound(), EPSILON); + assertEquals(3.1, r.getUpperBound(), EPSILON); + } + + /** + * This test checks that when the 'includeInterval' flag is false, the + * bounds come from the regular x-values. + */ + @Test + public void testFindDomainBounds3() { + DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); + double[] x1 = new double[] {1.0, 2.0, 3.0}; + double[] x1Start = new double[] {0.9, 1.9, 2.9}; + double[] x1End = new double[] {1.1, 2.1, 3.1}; + double[] y1 = new double[] {4.0, 5.0, 6.0}; + double[] y1Start = new double[] {1.09, 2.09, 3.09}; + double[] y1End = new double[] {1.11, 2.11, 3.11}; + double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, + y1End}; + dataset.addSeries("S1", data1); + Range r = DatasetUtils.findDomainBounds(dataset, false); + assertEquals(1.0, r.getLowerBound(), EPSILON); + assertEquals(3.0, r.getUpperBound(), EPSILON); + } + + /** + * This test checks that the correct values are returned if the x and + * y values fall outside the intervals. + */ + @Test + public void testFindDomainBounds4() { + DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); + double[] x1 = new double[] {0.8, 3.2, 3.0}; + double[] x1Start = new double[] {0.9, 1.9, 2.9}; + double[] x1End = new double[] {1.1, 2.1, 3.1}; + double[] y1 = new double[] {4.0, 5.0, 6.0}; + double[] y1Start = new double[] {1.09, 2.09, 3.09}; + double[] y1End = new double[] {1.11, 2.11, 3.11}; + double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, + y1End}; + dataset.addSeries("S1", data1); + Range r = DatasetUtils.findDomainBounds(dataset); + assertEquals(0.8, r.getLowerBound(), EPSILON); + assertEquals(3.2, r.getUpperBound(), EPSILON); + } + + /** + * This test checks that NaN values are ignored. + */ + @Test + public void testFindDomainBounds_NaN() { + DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); + double[] x1 = new double[] {1.0, 2.0, Double.NaN}; + double[] x1Start = new double[] {0.9, 1.9, Double.NaN}; + double[] x1End = new double[] {1.1, 2.1, Double.NaN}; + double[] y1 = new double[] {4.0, 5.0, 6.0}; + double[] y1Start = new double[] {1.09, 2.09, 3.09}; + double[] y1End = new double[] {1.11, 2.11, 3.11}; + double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, + y1End}; + dataset.addSeries("S1", data1); + Range r = DatasetUtils.findDomainBounds(dataset); + assertEquals(0.9, r.getLowerBound(), EPSILON); + assertEquals(2.1, r.getUpperBound(), EPSILON); + + r = DatasetUtils.findDomainBounds(dataset, false); + assertEquals(1.0, r.getLowerBound(), EPSILON); + assertEquals(2.0, r.getUpperBound(), EPSILON); + } + + /** + * Some tests for the iterateDomainBounds() method. + */ + @Test + public void testIterateDomainBounds() { + XYDataset dataset = createXYDataset1(); + Range r = DatasetUtils.iterateDomainBounds(dataset); + assertEquals(1.0, r.getLowerBound(), EPSILON); + assertEquals(3.0, r.getUpperBound(), EPSILON); + } + + /** + * Check that NaN values in the dataset are ignored. + */ + @Test + public void testIterateDomainBounds_NaN() { + DefaultXYDataset dataset = new DefaultXYDataset(); + double[] x = new double[] {1.0, 2.0, Double.NaN, 3.0}; + double[] y = new double[] {9.0, 8.0, 7.0, 6.0}; + dataset.addSeries("S1", new double[][] {x, y}); + Range r = DatasetUtils.iterateDomainBounds(dataset); + assertEquals(1.0, r.getLowerBound(), EPSILON); + assertEquals(3.0, r.getUpperBound(), EPSILON); + } + + /** + * Check that NaN values in the IntervalXYDataset are ignored. + */ + @Test + public void testIterateDomainBounds_NaN2() { + DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); + double[] x1 = new double[] {Double.NaN, 2.0, 3.0}; + double[] x1Start = new double[] {0.9, Double.NaN, 2.9}; + double[] x1End = new double[] {1.1, Double.NaN, 3.1}; + double[] y1 = new double[] {4.0, 5.0, 6.0}; + double[] y1Start = new double[] {1.09, 2.09, 3.09}; + double[] y1End = new double[] {1.11, 2.11, 3.11}; + double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, + y1End}; + dataset.addSeries("S1", data1); + Range r = DatasetUtils.iterateDomainBounds(dataset, false); + assertEquals(2.0, r.getLowerBound(), EPSILON); + assertEquals(3.0, r.getUpperBound(), EPSILON); + r = DatasetUtils.iterateDomainBounds(dataset, true); + assertEquals(0.9, r.getLowerBound(), EPSILON); + assertEquals(3.1, r.getUpperBound(), EPSILON); + } + + /** + * Some tests for the findRangeBounds() for a CategoryDataset method. + */ + @Test + public void testFindRangeBounds_CategoryDataset() { + CategoryDataset dataset = createCategoryDataset1(); + Range r = DatasetUtils.findRangeBounds(dataset); + assertEquals(1.0, r.getLowerBound(), EPSILON); + assertEquals(6.0, r.getUpperBound(), EPSILON); + } + + /** + * Some tests for the findRangeBounds() method on an XYDataset. + */ + @Test + public void testFindRangeBounds() { + XYDataset dataset = createXYDataset1(); + Range r = DatasetUtils.findRangeBounds(dataset); + assertEquals(100.0, r.getLowerBound(), EPSILON); + assertEquals(105.0, r.getUpperBound(), EPSILON); + } + + /** + * A test for the findRangeBounds(XYDataset) method using + * an IntervalXYDataset. + */ + @Test + public void testFindRangeBounds2() { + YIntervalSeriesCollection dataset = new YIntervalSeriesCollection(); + Range r = DatasetUtils.findRangeBounds(dataset); + assertNull(r); + YIntervalSeries s1 = new YIntervalSeries("S1"); + dataset.addSeries(s1); + r = DatasetUtils.findRangeBounds(dataset); + assertNull(r); + + // try a single item + s1.add(1.0, 2.0, 1.5, 2.5); + r = DatasetUtils.findRangeBounds(dataset); + assertEquals(1.5, r.getLowerBound(), EPSILON); + assertEquals(2.5, r.getUpperBound(), EPSILON); + + r = DatasetUtils.findRangeBounds(dataset, false); + assertEquals(2.0, r.getLowerBound(), EPSILON); + assertEquals(2.0, r.getUpperBound(), EPSILON); + + // another item + s1.add(2.0, 2.0, 1.4, 2.1); + r = DatasetUtils.findRangeBounds(dataset); + assertEquals(1.4, r.getLowerBound(), EPSILON); + assertEquals(2.5, r.getUpperBound(), EPSILON); + + // another empty series + YIntervalSeries s2 = new YIntervalSeries("S2"); + dataset.addSeries(s2); + r = DatasetUtils.findRangeBounds(dataset); + assertEquals(1.4, r.getLowerBound(), EPSILON); + assertEquals(2.5, r.getUpperBound(), EPSILON); + + // an item in series 2 + s2.add(1.0, 2.0, 1.9, 2.6); + r = DatasetUtils.findRangeBounds(dataset); + assertEquals(1.4, r.getLowerBound(), EPSILON); + assertEquals(2.6, r.getUpperBound(), EPSILON); + + // what if we don't want the interval? + r = DatasetUtils.findRangeBounds(dataset, false); + assertEquals(2.0, r.getLowerBound(), EPSILON); + assertEquals(2.0, r.getUpperBound(), EPSILON); + } + + /** + * Some tests for the iterateRangeBounds() method. + */ + @Test + public void testIterateRangeBounds_CategoryDataset() { + CategoryDataset dataset = createCategoryDataset1(); + Range r = DatasetUtils.iterateRangeBounds(dataset, false); + assertEquals(1.0, r.getLowerBound(), EPSILON); + assertEquals(6.0, r.getUpperBound(), EPSILON); + } + + /** + * Some checks for the iterateRangeBounds() method. + */ + @Test + public void testIterateRangeBounds2_CategoryDataset() { + // an empty dataset should return a null range + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + Range r = DatasetUtils.iterateRangeBounds(dataset, false); + assertNull(r); + + // a dataset with a single value + dataset.addValue(1.23, "R1", "C1"); + r = DatasetUtils.iterateRangeBounds(dataset, false); + assertEquals(1.23, r.getLowerBound(), EPSILON); + assertEquals(1.23, r.getUpperBound(), EPSILON); + + // null is ignored + dataset.addValue(null, "R2", "C1"); + r = DatasetUtils.iterateRangeBounds(dataset, false); + assertEquals(1.23, r.getLowerBound(), EPSILON); + assertEquals(1.23, r.getUpperBound(), EPSILON); + + // a Double.NaN should be ignored + dataset.addValue(Double.NaN, "R2", "C1"); + r = DatasetUtils.iterateRangeBounds(dataset, false); + assertEquals(1.23, r.getLowerBound(), EPSILON); + assertEquals(1.23, r.getUpperBound(), EPSILON); + } + + /** + * Some checks for the iterateRangeBounds() method using an + * IntervalCategoryDataset. + */ + @Test + public void testIterateRangeBounds3_CategoryDataset() { + Number[][] starts = new Double[2][3]; + Number[][] ends = new Double[2][3]; + starts[0][0] = 1.0; + starts[0][1] = 2.0; + starts[0][2] = 3.0; + starts[1][0] = 11.0; + starts[1][1] = 12.0; + starts[1][2] = 13.0; + ends[0][0] = 4.0; + ends[0][1] = 5.0; + ends[0][2] = 6.0; + ends[1][0] = 16.0; + ends[1][1] = 15.0; + ends[1][2] = 14.0; + + DefaultIntervalCategoryDataset d = new DefaultIntervalCategoryDataset( + starts, ends); + Range r = DatasetUtils.iterateRangeBounds(d, false); + assertEquals(4.0, r.getLowerBound(), EPSILON); + assertEquals(16.0, r.getUpperBound(), EPSILON); + r = DatasetUtils.iterateRangeBounds(d, true); + assertEquals(1.0, r.getLowerBound(), EPSILON); + assertEquals(16.0, r.getUpperBound(), EPSILON); + } + + /** + * Some tests for the iterateRangeBounds() method. + */ + @Test + public void testIterateRangeBounds() { + XYDataset dataset = createXYDataset1(); + Range r = DatasetUtils.iterateRangeBounds(dataset); + assertEquals(100.0, r.getLowerBound(), EPSILON); + assertEquals(105.0, r.getUpperBound(), EPSILON); + } + + /** + * Check the range returned when a series contains a null value. + */ + @Test + public void testIterateRangeBounds2() { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, 1.1); + s1.add(2.0, null); + s1.add(3.0, 3.3); + XYSeriesCollection dataset = new XYSeriesCollection(s1); + Range r = DatasetUtils.iterateRangeBounds(dataset); + assertEquals(1.1, r.getLowerBound(), EPSILON); + assertEquals(3.3, r.getUpperBound(), EPSILON); + } + + /** + * Some checks for the iterateRangeBounds() method. + */ + @Test + public void testIterateRangeBounds3() { + // an empty dataset should return a null range + XYSeriesCollection dataset = new XYSeriesCollection(); + Range r = DatasetUtils.iterateRangeBounds(dataset); + assertNull(r); + XYSeries s1 = new XYSeries("S1"); + dataset.addSeries(s1); + r = DatasetUtils.iterateRangeBounds(dataset); + assertNull(r); + + // a dataset with a single value + s1.add(1.0, 1.23); + r = DatasetUtils.iterateRangeBounds(dataset); + assertEquals(1.23, r.getLowerBound(), EPSILON); + assertEquals(1.23, r.getUpperBound(), EPSILON); + + // null is ignored + s1.add(2.0, null); + r = DatasetUtils.iterateRangeBounds(dataset); + assertEquals(1.23, r.getLowerBound(), EPSILON); + assertEquals(1.23, r.getUpperBound(), EPSILON); + + // Double.NaN DOESN'T mess things up + s1.add(3.0, Double.NaN); + r = DatasetUtils.iterateRangeBounds(dataset); + assertEquals(1.23, r.getLowerBound(), EPSILON); + assertEquals(1.23, r.getUpperBound(), EPSILON); + } + + /** + * Some checks for the range bounds of a dataset that implements the + * {@link org.jfree.data.xy.IntervalXYDataset} interface. + */ + @Test + public void testIterateRangeBounds4() { + YIntervalSeriesCollection dataset = new YIntervalSeriesCollection(); + Range r = DatasetUtils.iterateRangeBounds(dataset); + assertNull(r); + YIntervalSeries s1 = new YIntervalSeries("S1"); + dataset.addSeries(s1); + r = DatasetUtils.iterateRangeBounds(dataset); + assertNull(r); + + // try a single item + s1.add(1.0, 2.0, 1.5, 2.5); + r = DatasetUtils.iterateRangeBounds(dataset); + assertEquals(1.5, r.getLowerBound(), EPSILON); + assertEquals(2.5, r.getUpperBound(), EPSILON); + + // another item + s1.add(2.0, 2.0, 1.4, 2.1); + r = DatasetUtils.iterateRangeBounds(dataset); + assertEquals(1.4, r.getLowerBound(), EPSILON); + assertEquals(2.5, r.getUpperBound(), EPSILON); + + // another empty series + YIntervalSeries s2 = new YIntervalSeries("S2"); + dataset.addSeries(s2); + r = DatasetUtils.iterateRangeBounds(dataset); + assertEquals(1.4, r.getLowerBound(), EPSILON); + assertEquals(2.5, r.getUpperBound(), EPSILON); + + // an item in series 2 + s2.add(1.0, 2.0, 1.9, 2.6); + r = DatasetUtils.iterateRangeBounds(dataset); + assertEquals(1.4, r.getLowerBound(), EPSILON); + assertEquals(2.6, r.getUpperBound(), EPSILON); + } + + /** + * Some tests for the findMinimumDomainValue() method. + */ + @Test + public void testFindMinimumDomainValue() { + XYDataset dataset = createXYDataset1(); + Number minimum = DatasetUtils.findMinimumDomainValue(dataset); + assertEquals(1.0, minimum); + } + + /** + * Some tests for the findMaximumDomainValue() method. + */ + @Test + public void testFindMaximumDomainValue() { + XYDataset dataset = createXYDataset1(); + Number maximum = DatasetUtils.findMaximumDomainValue(dataset); + assertEquals(3.0, maximum); + } + + /** + * Some tests for the findMinimumRangeValue() method. + */ + @Test + public void testFindMinimumRangeValue() { + CategoryDataset d1 = createCategoryDataset1(); + Number min1 = DatasetUtils.findMinimumRangeValue(d1); + assertEquals(1.0, min1); + + XYDataset d2 = createXYDataset1(); + Number min2 = DatasetUtils.findMinimumRangeValue(d2); + assertEquals(100.0, min2); + } + + /** + * Some tests for the findMaximumRangeValue() method. + */ + @Test + public void testFindMaximumRangeValue() { + CategoryDataset d1 = createCategoryDataset1(); + Number max1 = DatasetUtils.findMaximumRangeValue(d1); + assertEquals(6.0, max1); + + XYDataset dataset = createXYDataset1(); + Number maximum = DatasetUtils.findMaximumRangeValue(dataset); + assertEquals(105.0, maximum); + } + + /** + * A quick test of the min and max range value methods. + */ + @Test + public void testMinMaxRange() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + dataset.addValue(100.0, "Series 1", "Type 1"); + dataset.addValue(101.1, "Series 1", "Type 2"); + Number min = DatasetUtils.findMinimumRangeValue(dataset); + assertTrue(min.doubleValue() < 100.1); + Number max = DatasetUtils.findMaximumRangeValue(dataset); + assertTrue(max.doubleValue() > 101.0); + } + + /** + * A test to reproduce bug report 803660. + */ + @Test + public void test803660() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + dataset.addValue(100.0, "Series 1", "Type 1"); + dataset.addValue(101.1, "Series 1", "Type 2"); + Number n = DatasetUtils.findMaximumRangeValue(dataset); + assertTrue(n.doubleValue() > 101.0); + } + + /** + * A simple test for the cumulative range calculation. The sequence of + * "cumulative" values are considered to be { 0.0, 10.0, 25.0, 18.0 } so + * the range should be 0.0 -> 25.0. + */ + @Test + public void testCumulativeRange1() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + dataset.addValue(10.0, "Series 1", "Start"); + dataset.addValue(15.0, "Series 1", "Delta 1"); + dataset.addValue(-7.0, "Series 1", "Delta 2"); + Range range = DatasetUtils.findCumulativeRangeBounds(dataset); + assertEquals(0.0, range.getLowerBound(), 0.00000001); + assertEquals(25.0, range.getUpperBound(), 0.00000001); + } + + /** + * A further test for the cumulative range calculation. + */ + @Test + public void testCumulativeRange2() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + dataset.addValue(-21.4, "Series 1", "Start Value"); + dataset.addValue(11.57, "Series 1", "Delta 1"); + dataset.addValue(3.51, "Series 1", "Delta 2"); + dataset.addValue(-12.36, "Series 1", "Delta 3"); + dataset.addValue(3.39, "Series 1", "Delta 4"); + dataset.addValue(38.68, "Series 1", "Delta 5"); + dataset.addValue(-43.31, "Series 1", "Delta 6"); + dataset.addValue(-29.59, "Series 1", "Delta 7"); + dataset.addValue(35.30, "Series 1", "Delta 8"); + dataset.addValue(5.0, "Series 1", "Delta 9"); + Range range = DatasetUtils.findCumulativeRangeBounds(dataset); + assertEquals(-49.51, range.getLowerBound(), 0.00000001); + assertEquals(23.39, range.getUpperBound(), 0.00000001); + } + + /** + * A further test for the cumulative range calculation. + */ + @Test + public void testCumulativeRange3() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + dataset.addValue(15.76, "Product 1", "Labour"); + dataset.addValue(8.66, "Product 1", "Administration"); + dataset.addValue(4.71, "Product 1", "Marketing"); + dataset.addValue(3.51, "Product 1", "Distribution"); + dataset.addValue(32.64, "Product 1", "Total Expense"); + Range range = DatasetUtils.findCumulativeRangeBounds(dataset); + assertEquals(0.0, range.getLowerBound(), EPSILON); + assertEquals(65.28, range.getUpperBound(), EPSILON); + } + + /** + * Check that the findCumulativeRangeBounds() method ignores Double.NaN + * values. + */ + @Test + public void testCumulativeRange_NaN() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + dataset.addValue(10.0, "Series 1", "Start"); + dataset.addValue(15.0, "Series 1", "Delta 1"); + dataset.addValue(Double.NaN, "Series 1", "Delta 2"); + Range range = DatasetUtils.findCumulativeRangeBounds(dataset); + assertEquals(0.0, range.getLowerBound(), EPSILON); + assertEquals(25.0, range.getUpperBound(), EPSILON); + } + + /** + * Test the creation of a dataset from an array. + */ + @Test + public void testCreateCategoryDataset1() { + String[] rowKeys = {"R1", "R2", "R3"}; + String[] columnKeys = {"C1", "C2"}; + double[][] data = new double[3][]; + data[0] = new double[] {1.1, 1.2}; + data[1] = new double[] {2.1, 2.2}; + data[2] = new double[] {3.1, 3.2}; + CategoryDataset dataset = DatasetUtils.createCategoryDataset( + rowKeys, columnKeys, data); + assertEquals(3, dataset.getRowCount()); + assertEquals(2, dataset.getColumnCount()); + } + + /** + * Test the creation of a dataset from an array. This time is should fail + * because the array dimensions are around the wrong way. + */ + @Test + public void testCreateCategoryDataset2() { + boolean pass = false; + String[] rowKeys = {"R1", "R2", "R3"}; + String[] columnKeys = {"C1", "C2"}; + double[][] data = new double[2][]; + data[0] = new double[] {1.1, 1.2, 1.3}; + data[1] = new double[] {2.1, 2.2, 2.3}; + CategoryDataset dataset = null; + try { + dataset = DatasetUtils.createCategoryDataset(rowKeys, + columnKeys, data); + } + catch (IllegalArgumentException e) { + pass = true; // got it! + } + assertTrue(pass); + assertNull(dataset); + } + + /** + * Test for a bug reported in the forum: + * + * http://www.jfree.org/phpBB2/viewtopic.php?t=7903 + */ + @Test + public void testMaximumStackedRangeValue() { + double v1 = 24.3; + double v2 = 14.2; + double v3 = 33.2; + double v4 = 32.4; + double v5 = 26.3; + double v6 = 22.6; + Number answer = Math.max(v1 + v2 + v3, v4 + v5 + v6); + DefaultCategoryDataset d = new DefaultCategoryDataset(); + d.addValue(v1, "Row 0", "Column 0"); + d.addValue(v2, "Row 1", "Column 0"); + d.addValue(v3, "Row 2", "Column 0"); + d.addValue(v4, "Row 0", "Column 1"); + d.addValue(v5, "Row 1", "Column 1"); + d.addValue(v6, "Row 2", "Column 1"); + Number max = DatasetUtils.findMaximumStackedRangeValue(d); + assertEquals(max, answer); + } + + /** + * Some checks for the findStackedRangeBounds() method. + */ + @Test + public void testFindStackedRangeBounds_CategoryDataset1() { + CategoryDataset d1 = createCategoryDataset1(); + Range r = DatasetUtils.findStackedRangeBounds(d1); + assertEquals(0.0, r.getLowerBound(), EPSILON); + assertEquals(15.0, r.getUpperBound(), EPSILON); + + d1 = createCategoryDataset2(); + r = DatasetUtils.findStackedRangeBounds(d1); + assertEquals(-2.0, r.getLowerBound(), EPSILON); + assertEquals(2.0, r.getUpperBound(), EPSILON); + } + + /** + * Some checks for the findStackedRangeBounds() method. + */ + @Test + public void testFindStackedRangeBounds_CategoryDataset2() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + Range r = DatasetUtils.findStackedRangeBounds(dataset); + assertNull(r); + + dataset.addValue(5.0, "R1", "C1"); + r = DatasetUtils.findStackedRangeBounds(dataset, 3.0); + assertEquals(3.0, r.getLowerBound(), EPSILON); + assertEquals(8.0, r.getUpperBound(), EPSILON); + + dataset.addValue(-1.0, "R2", "C1"); + r = DatasetUtils.findStackedRangeBounds(dataset, 3.0); + assertEquals(2.0, r.getLowerBound(), EPSILON); + assertEquals(8.0, r.getUpperBound(), EPSILON); + + dataset.addValue(null, "R3", "C1"); + r = DatasetUtils.findStackedRangeBounds(dataset, 3.0); + assertEquals(2.0, r.getLowerBound(), EPSILON); + assertEquals(8.0, r.getUpperBound(), EPSILON); + + dataset.addValue(Double.NaN, "R4", "C1"); + r = DatasetUtils.findStackedRangeBounds(dataset, 3.0); + assertEquals(2.0, r.getLowerBound(), EPSILON); + assertEquals(8.0, r.getUpperBound(), EPSILON); + } + + /** + * Some checks for the findStackedRangeBounds(CategoryDataset, + * KeyToGroupMap) method. + */ + @Test + public void testFindStackedRangeBounds_CategoryDataset3() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + KeyToGroupMap map = new KeyToGroupMap("Group A"); + Range r = DatasetUtils.findStackedRangeBounds(dataset, map); + assertNull(r); + + dataset.addValue(1.0, "R1", "C1"); + dataset.addValue(2.0, "R2", "C1"); + dataset.addValue(3.0, "R3", "C1"); + dataset.addValue(4.0, "R4", "C1"); + + map.mapKeyToGroup("R1", "Group A"); + map.mapKeyToGroup("R2", "Group A"); + map.mapKeyToGroup("R3", "Group B"); + map.mapKeyToGroup("R4", "Group B"); + + r = DatasetUtils.findStackedRangeBounds(dataset, map); + assertEquals(0.0, r.getLowerBound(), EPSILON); + assertEquals(7.0, r.getUpperBound(), EPSILON); + + dataset.addValue(null, "R5", "C1"); + r = DatasetUtils.findStackedRangeBounds(dataset, map); + assertEquals(0.0, r.getLowerBound(), EPSILON); + assertEquals(7.0, r.getUpperBound(), EPSILON); + + dataset.addValue(Double.NaN, "R6", "C1"); + r = DatasetUtils.findStackedRangeBounds(dataset, map); + assertEquals(0.0, r.getLowerBound(), EPSILON); + assertEquals(7.0, r.getUpperBound(), EPSILON); + } + + /** + * Some checks for the findStackedRangeBounds() method. + */ + @Test + public void testFindStackedRangeBoundsForTableXYDataset1() { + TableXYDataset d2 = createTableXYDataset1(); + Range r = DatasetUtils.findStackedRangeBounds(d2); + assertEquals(-2.0, r.getLowerBound(), EPSILON); + assertEquals(2.0, r.getUpperBound(), EPSILON); + } + + /** + * Some checks for the findStackedRangeBounds() method. + */ + @Test + public void testFindStackedRangeBoundsForTableXYDataset2() { + DefaultTableXYDataset d = new DefaultTableXYDataset(); + Range r = DatasetUtils.findStackedRangeBounds(d); + assertEquals(r, new Range(0.0, 0.0)); + } + + /** + * Tests the stacked range extent calculation. + */ + @Test + public void testStackedRangeWithMap() { + CategoryDataset d = createCategoryDataset1(); + KeyToGroupMap map = new KeyToGroupMap("G0"); + map.mapKeyToGroup("R2", "G1"); + Range r = DatasetUtils.findStackedRangeBounds(d, map); + assertEquals(0.0, r.getLowerBound(), EPSILON); + assertEquals(9.0, r.getUpperBound(), EPSILON); + } + + /** + * Some checks for the isEmptyOrNull(XYDataset) method. + */ + @Test + public void testIsEmptyOrNullXYDataset() { + XYSeriesCollection dataset = null; + assertTrue(DatasetUtils.isEmptyOrNull(dataset)); + dataset = new XYSeriesCollection(); + assertTrue(DatasetUtils.isEmptyOrNull(dataset)); + XYSeries s1 = new XYSeries("S1"); + dataset.addSeries(s1); + assertTrue(DatasetUtils.isEmptyOrNull(dataset)); + s1.add(1.0, 2.0); + assertFalse(DatasetUtils.isEmptyOrNull(dataset)); + s1.clear(); + assertTrue(DatasetUtils.isEmptyOrNull(dataset)); + } + + /** + * Some checks for the limitPieDataset() methods. + */ + @Test + public void testLimitPieDataset() { + + // check that empty dataset is handled OK + DefaultPieDataset d1 = new DefaultPieDataset(); + PieDataset d2 = DatasetUtils.createConsolidatedPieDataset(d1, + "Other", 0.05); + assertEquals(0, d2.getItemCount()); + + // check that minItem limit is observed + d1.setValue("Item 1", 1.0); + d1.setValue("Item 2", 49.50); + d1.setValue("Item 3", 49.50); + d2 = DatasetUtils.createConsolidatedPieDataset(d1, "Other", 0.05); + assertEquals(3, d2.getItemCount()); + assertEquals("Item 1", d2.getKey(0)); + assertEquals("Item 2", d2.getKey(1)); + assertEquals("Item 3", d2.getKey(2)); + + // check that minItem limit is observed + d1.setValue("Item 4", 1.0); + d2 = DatasetUtils.createConsolidatedPieDataset(d1, "Other", 0.05, + 2); + + // and that simple aggregation works + assertEquals(3, d2.getItemCount()); + assertEquals("Item 2", d2.getKey(0)); + assertEquals("Item 3", d2.getKey(1)); + assertEquals("Other", d2.getKey(2)); + assertEquals(2.0, d2.getValue("Other")); + + } + + /** + * Some checks for the sampleFunction2D() method. + */ + @Test + public void testSampleFunction2D() { + Function2D f = new LineFunction2D(0, 1); + XYDataset dataset = DatasetUtils.sampleFunction2D(f, 0.0, 1.0, 2, + "S1"); + assertEquals(1, dataset.getSeriesCount()); + assertEquals("S1", dataset.getSeriesKey(0)); + assertEquals(2, dataset.getItemCount(0)); + assertEquals(0.0, dataset.getXValue(0, 0), EPSILON); + assertEquals(0.0, dataset.getYValue(0, 0), EPSILON); + assertEquals(1.0, dataset.getXValue(0, 1), EPSILON); + assertEquals(1.0, dataset.getYValue(0, 1), EPSILON); + } + + /** + * A simple check for the findMinimumStackedRangeValue() method. + */ + @Test + public void testFindMinimumStackedRangeValue() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + + // an empty dataset should return a null max + Number min = DatasetUtils.findMinimumStackedRangeValue(dataset); + assertNull(min); + + dataset.addValue(1.0, "R1", "C1"); + min = DatasetUtils.findMinimumStackedRangeValue(dataset); + assertEquals(0.0, min.doubleValue(), EPSILON); + + dataset.addValue(2.0, "R2", "C1"); + min = DatasetUtils.findMinimumStackedRangeValue(dataset); + assertEquals(0.0, min.doubleValue(), EPSILON); + + dataset.addValue(-3.0, "R3", "C1"); + min = DatasetUtils.findMinimumStackedRangeValue(dataset); + assertEquals(-3.0, min.doubleValue(), EPSILON); + + dataset.addValue(Double.NaN, "R4", "C1"); + min = DatasetUtils.findMinimumStackedRangeValue(dataset); + assertEquals(-3.0, min.doubleValue(), EPSILON); + } + + /** + * A simple check for the findMaximumStackedRangeValue() method. + */ + @Test + public void testFindMinimumStackedRangeValue2() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + dataset.addValue(-1.0, "R1", "C1"); + Number min = DatasetUtils.findMinimumStackedRangeValue(dataset); + assertEquals(-1.0, min.doubleValue(), EPSILON); + + dataset.addValue(-2.0, "R2", "C1"); + min = DatasetUtils.findMinimumStackedRangeValue(dataset); + assertEquals(-3.0, min.doubleValue(), EPSILON); + } + + /** + * A simple check for the findMaximumStackedRangeValue() method. + */ + @Test + public void testFindMaximumStackedRangeValue() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + + // an empty dataset should return a null max + Number max = DatasetUtils.findMaximumStackedRangeValue(dataset); + assertNull(max); + + dataset.addValue(1.0, "R1", "C1"); + max = DatasetUtils.findMaximumStackedRangeValue(dataset); + assertEquals(1.0, max.doubleValue(), EPSILON); + + dataset.addValue(2.0, "R2", "C1"); + max = DatasetUtils.findMaximumStackedRangeValue(dataset); + assertEquals(3.0, max.doubleValue(), EPSILON); + + dataset.addValue(-3.0, "R3", "C1"); + max = DatasetUtils.findMaximumStackedRangeValue(dataset); + assertEquals(3.0, max.doubleValue(), EPSILON); + + dataset.addValue(Double.NaN, "R4", "C1"); + max = DatasetUtils.findMaximumStackedRangeValue(dataset); + assertEquals(3.0, max.doubleValue(), EPSILON); + } + + /** + * A simple check for the findMaximumStackedRangeValue() method. + */ + @Test + public void testFindMaximumStackedRangeValue2() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + dataset.addValue(-1.0, "R1", "C1"); + Number max = DatasetUtils.findMaximumStackedRangeValue(dataset); + assertEquals(0.0, max.doubleValue(), EPSILON); + + dataset.addValue(-2.0, "R2", "C1"); + max = DatasetUtils.findMaximumStackedRangeValue(dataset); + assertEquals(0.0, max.doubleValue(), EPSILON); + } + + /** + * Creates a dataset for testing. + * + * @return A dataset. + */ + private CategoryDataset createCategoryDataset1() { + DefaultCategoryDataset result = new DefaultCategoryDataset(); + result.addValue(1.0, "R0", "C0"); + result.addValue(1.0, "R1", "C0"); + result.addValue(1.0, "R2", "C0"); + result.addValue(4.0, "R0", "C1"); + result.addValue(5.0, "R1", "C1"); + result.addValue(6.0, "R2", "C1"); + return result; + } + + /** + * Creates a dataset for testing. + * + * @return A dataset. + */ + private CategoryDataset createCategoryDataset2() { + DefaultCategoryDataset result = new DefaultCategoryDataset(); + result.addValue(1.0, "R0", "C0"); + result.addValue(-2.0, "R1", "C0"); + result.addValue(2.0, "R0", "C1"); + result.addValue(-1.0, "R1", "C1"); + return result; + } + + + /** + * Creates a dataset for testing. + * + * @return A dataset. + */ + private XYDataset createXYDataset1() { + XYSeries series1 = new XYSeries("S1"); + series1.add(1.0, 100.0); + series1.add(2.0, 101.0); + series1.add(3.0, 102.0); + XYSeries series2 = new XYSeries("S2"); + series2.add(1.0, 103.0); + series2.add(2.0, null); + series2.add(3.0, 105.0); + XYSeriesCollection result = new XYSeriesCollection(); + result.addSeries(series1); + result.addSeries(series2); + result.setIntervalWidth(0.0); + return result; + } + + /** + * Creates a sample dataset for testing purposes. + * + * @return A sample dataset. + */ + private TableXYDataset createTableXYDataset1() { + DefaultTableXYDataset dataset = new DefaultTableXYDataset(); + + XYSeries s1 = new XYSeries("Series 1", true, false); + s1.add(1.0, 1.0); + s1.add(2.0, 2.0); + dataset.addSeries(s1); + + XYSeries s2 = new XYSeries("Series 2", true, false); + s2.add(1.0, -2.0); + s2.add(2.0, -1.0); + dataset.addSeries(s2); + + return dataset; + } + + /** + * This test checks that the correct values are returned if the x-values + * fall outside the intervals (it is not required that they do). + */ + @Test + public void testIterateToFindDomainBounds_IntervalXYDataset() { + DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); + double[] x1 = new double[] {0.8, 3.2, 3.0}; + double[] x1Start = new double[] {0.9, 1.9, 2.9}; + double[] x1End = new double[] {1.1, 2.1, 3.1}; + double[] y1 = new double[] {4.0, 5.0, 6.0}; + double[] y1Start = new double[] {1.09, 2.09, 3.09}; + double[] y1End = new double[] {1.11, 2.11, 3.11}; + double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, + y1End}; + dataset.addSeries("S1", data1); + Range r = DatasetUtils.iterateToFindDomainBounds(dataset, + Collections.singletonList("S1"), true); + assertEquals(0.8, r.getLowerBound(), EPSILON); + assertEquals(3.2, r.getUpperBound(), EPSILON); + } + + /** + * This test checks that the correct values are returned if the y-values + * fall outside the intervals (it is not required that they do). + */ + @Test + public void testIterateToFindRangeBounds_IntervalXYDataset() { + DefaultIntervalXYDataset dataset = new DefaultIntervalXYDataset(); + double[] x1 = new double[] {0.8, 3.2, 3.0}; + double[] x1Start = new double[] {0.9, 1.9, 2.9}; + double[] x1End = new double[] {1.1, 2.1, 3.1}; + double[] y1 = new double[] {4.0, -5.0, 6.0}; + double[] y1Start = new double[] {1.09, 2.09, 3.09}; + double[] y1End = new double[] {1.11, 2.11, 3.11}; + double[][] data1 = new double[][] {x1, x1Start, x1End, y1, y1Start, + y1End}; + dataset.addSeries("S1", data1); + Range r = DatasetUtils.iterateToFindRangeBounds(dataset, + Collections.singletonList("S1"), new Range(0.0, 4.0), true); + assertEquals(-5.0, r.getLowerBound(), EPSILON); + assertEquals(6.0, r.getUpperBound(), EPSILON); + } + + /** + * Some checks for the iteratorToFindRangeBounds(XYDataset...) method. + */ + @Test + public void testIterateToFindRangeBounds1_XYDataset() { + // null dataset throws IllegalArgumentException + boolean pass = false; + try { + DatasetUtils.iterateToFindRangeBounds(null, new ArrayList(), + new Range(0.0, 1.0), true); + } + catch (IllegalArgumentException e) { + pass = true; + } + assertTrue(pass); + + // null list throws IllegalArgumentException + pass = false; + try { + DatasetUtils.iterateToFindRangeBounds(new XYSeriesCollection(), + null, new Range(0.0, 1.0), true); + } + catch (IllegalArgumentException e) { + pass = true; + } + assertTrue(pass); + + // null range throws IllegalArgumentException + pass = false; + try { + DatasetUtils.iterateToFindRangeBounds(new XYSeriesCollection(), + new ArrayList(), null, true); + } + catch (IllegalArgumentException e) { + pass = true; + } + assertTrue(pass); + } + + /** + * Some tests for the iterateToFindRangeBounds() method. + */ + @Test + public void testIterateToFindRangeBounds2_XYDataset() { + List visibleSeriesKeys = new ArrayList<>(); + Range xRange = new Range(0.0, 10.0); + + // empty dataset returns null + XYSeriesCollection dataset = new XYSeriesCollection(); + Range r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false); + assertNull(r); + + // add an empty series + XYSeries s1 = new XYSeries("A"); + dataset.addSeries(s1); + visibleSeriesKeys.add("A"); + r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false); + assertNull(r); + + // check a null value + s1.add(1.0, null); + r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false); + assertNull(r); + + // check a NaN + s1.add(2.0, Double.NaN); + r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false); + assertNull(r); + + // check a regular value + s1.add(3.0, 5.0); + r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false); + assertEquals(new Range(5.0, 5.0), r); + + // check another regular value + s1.add(4.0, 6.0); + r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false); + assertEquals(new Range(5.0, 6.0), r); + + // add a second series + XYSeries s2 = new XYSeries("B"); + dataset.addSeries(s2); + r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false); + assertEquals(new Range(5.0, 6.0), r); + visibleSeriesKeys.add("B"); + r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false); + assertEquals(new Range(5.0, 6.0), r); + + // add a value to the second series + s2.add(5.0, 15.0); + r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false); + assertEquals(new Range(5.0, 15.0), r); + + // add a value that isn't in the xRange + s2.add(15.0, 150.0); + r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false); + assertEquals(new Range(5.0, 15.0), r); + + r = DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, new Range(0.0, 20.0), false); + assertEquals(new Range(5.0, 150.0), r); + } + + /** + * Some checks for the iterateToFindRangeBounds() method when applied to + * a BoxAndWhiskerXYDataset. + */ + @Test + public void testIterateToFindRangeBounds_BoxAndWhiskerXYDataset() { + DefaultBoxAndWhiskerXYDataset dataset + = new DefaultBoxAndWhiskerXYDataset("Series 1"); + List visibleSeriesKeys = new ArrayList<>(); + visibleSeriesKeys.add("Series 1"); + Range xRange = new Range(Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY); + assertNull(DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false)); + + dataset.add(new Date(50L), new BoxAndWhiskerItem(5.0, 4.9, 2.0, 8.0, + 1.0, 9.0, 0.0, 10.0, new ArrayList())); + assertEquals(new Range(5.0, 5.0), + DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, false)); + assertEquals(new Range(1.0, 9.0), + DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, xRange, true)); + } + + /** + * Some checks for the iterateToFindRangeBounds(CategoryDataset...) + * method. + */ + @Test + public void testIterateToFindRangeBounds_StatisticalCategoryDataset() { + DefaultStatisticalCategoryDataset dataset + = new DefaultStatisticalCategoryDataset(); + List visibleSeriesKeys = new ArrayList<>(); + assertNull(DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, false)); + dataset.add(1.0, 0.5, "R1", "C1"); + visibleSeriesKeys.add("R1"); + assertEquals(new Range(1.0, 1.0), + DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, false)); + assertEquals(new Range(0.5, 1.5), + DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, true)); + } + + /** + * Some checks for the iterateToFindRangeBounds(CategoryDataset...) method + * with a {@link org.jfree.data.statistics.MultiValueCategoryDataset}. + */ + @Test + public void testIterateToFindRangeBounds_MultiValueCategoryDataset() { + DefaultMultiValueCategoryDataset dataset + = new DefaultMultiValueCategoryDataset(); + List visibleSeriesKeys = new ArrayList<>(); + assertNull(DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, true)); + List values = Collections.singletonList(1.0); + dataset.add(values, "R1", "C1"); + visibleSeriesKeys.add("R1"); + assertEquals(new Range(1.0, 1.0), + DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, true)); + + values = Arrays.asList(2.0, 3.0); + dataset.add(values, "R1", "C2"); + assertEquals(new Range(1.0, 3.0), + DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, true)); + + values = Arrays.asList(-1.0, -2.0); + dataset.add(values, "R2", "C1"); + assertEquals(new Range(1.0, 3.0), + DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, true)); + visibleSeriesKeys.add("R2"); + assertEquals(new Range(-2.0, 3.0), + DatasetUtils.iterateToFindRangeBounds(dataset, + visibleSeriesKeys, true)); + } + + /** + * Some checks for the iterateRangeBounds() method when passed an + * IntervalCategoryDataset. + */ + @Test + public void testIterateRangeBounds_IntervalCategoryDataset() { + TestIntervalCategoryDataset d = new TestIntervalCategoryDataset(); + d.addItem(1.0, 2.0, 3.0, "R1", "C1"); + assertEquals(new Range(1.0, 3.0), + DatasetUtils.iterateRangeBounds(d)); + + d = new TestIntervalCategoryDataset(); + d.addItem(2.5, 2.0, 3.0, "R1", "C1"); + assertEquals(new Range(2.0, 3.0), + DatasetUtils.iterateRangeBounds(d)); + + d = new TestIntervalCategoryDataset(); + d.addItem(4.0, 2.0, 3.0, "R1", "C1"); + assertEquals(new Range(2.0, 4.0), + DatasetUtils.iterateRangeBounds(d)); + + d = new TestIntervalCategoryDataset(); + d.addItem(null, 2.0, 3.0, "R1", "C1"); + assertEquals(new Range(2.0, 3.0), + DatasetUtils.iterateRangeBounds(d)); + + // try some nulls + d = new TestIntervalCategoryDataset(); + d.addItem(null, null, null, "R1", "C1"); + assertNull(DatasetUtils.iterateRangeBounds(d)); + + d = new TestIntervalCategoryDataset(); + d.addItem(1.0, null, null, "R1", "C1"); + assertEquals(new Range(1.0, 1.0), + DatasetUtils.iterateRangeBounds(d)); + + d = new TestIntervalCategoryDataset(); + d.addItem(null, 1.0, null, "R1", "C1"); + assertEquals(new Range(1.0, 1.0), + DatasetUtils.iterateRangeBounds(d)); + + d = new TestIntervalCategoryDataset(); + d.addItem(null, null, 1.0, "R1", "C1"); + assertEquals(new Range(1.0, 1.0), + DatasetUtils.iterateRangeBounds(d)); + } + + /** + * A test for bug 2849731. + */ + @Test + public void testBug2849731() { + TestIntervalCategoryDataset d = new TestIntervalCategoryDataset(); + d.addItem(2.5, 2.0, 3.0, "R1", "C1"); + d.addItem(4.0, null, null, "R2", "C1"); + assertEquals(new Range(2.0, 4.0), + DatasetUtils.iterateRangeBounds(d)); + } + + /** + * Another test for bug 2849731. + */ + @Test + public void testBug2849731_2() { + XYIntervalSeriesCollection d = new XYIntervalSeriesCollection(); + XYIntervalSeries s = new XYIntervalSeries("S1"); + s.add(1.0, Double.NaN, Double.NaN, Double.NaN, 1.5, Double.NaN); + d.addSeries(s); + Range r = DatasetUtils.iterateDomainBounds(d); + assertEquals(1.0, r.getLowerBound(), EPSILON); + assertEquals(1.0, r.getUpperBound(), EPSILON); + + s.add(1.0, 1.5, Double.NaN, Double.NaN, 1.5, Double.NaN); + r = DatasetUtils.iterateDomainBounds(d); + assertEquals(1.0, r.getLowerBound(), EPSILON); + assertEquals(1.5, r.getUpperBound(), EPSILON); + + s.add(1.0, Double.NaN, 0.5, Double.NaN, 1.5, Double.NaN); + r = DatasetUtils.iterateDomainBounds(d); + assertEquals(0.5, r.getLowerBound(), EPSILON); + assertEquals(1.5, r.getUpperBound(), EPSILON); + } + /** + * Yet another test for bug 2849731. + */ + @Test + public void testBug2849731_3() { + XYIntervalSeriesCollection d = new XYIntervalSeriesCollection(); + XYIntervalSeries s = new XYIntervalSeries("S1"); + s.add(1.0, Double.NaN, Double.NaN, 1.5, Double.NaN, Double.NaN); + d.addSeries(s); + Range r = DatasetUtils.iterateRangeBounds(d); + assertEquals(1.5, r.getLowerBound(), EPSILON); + assertEquals(1.5, r.getUpperBound(), EPSILON); + + s.add(1.0, 1.5, Double.NaN, Double.NaN, Double.NaN, 2.5); + r = DatasetUtils.iterateRangeBounds(d); + assertEquals(1.5, r.getLowerBound(), EPSILON); + assertEquals(2.5, r.getUpperBound(), EPSILON); + + s.add(1.0, Double.NaN, 0.5, Double.NaN, 3.5, Double.NaN); + r = DatasetUtils.iterateRangeBounds(d); + assertEquals(1.5, r.getLowerBound(), EPSILON); + assertEquals(3.5, r.getUpperBound(), EPSILON); + } + + /** + * Check the findYValue() method with a dataset that is in ascending order + * of x-values. + */ + @Test + public void testFindYValue() { + XYSeries series = new XYSeries("S1"); + XYSeriesCollection dataset = new XYSeriesCollection(series); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 100.0))); + + series.add(1.0, 5.0); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); + assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 2.0))); + + series.add(2.0, 10.0); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); + assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); + assertEquals(6.25, DatasetUtils.findYValue(dataset, 0, 1.25), EPSILON); + assertEquals(7.5, DatasetUtils.findYValue(dataset, 0, 1.5), EPSILON); + assertEquals(10.0, DatasetUtils.findYValue(dataset, 0, 2.0), EPSILON); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 3.0))); + } + + /** + * Check the findYValue() method with a dataset that is not sorted. + */ + @Test + public void testFindYValueNonSorted() { + XYSeries series = new XYSeries("S1", false); + XYSeriesCollection dataset = new XYSeriesCollection(series); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 100.0))); + + series.add(1.0, 5.0); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); + assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 2.0))); + + series.add(0.0, 10.0); + series.add(4.0, 20.0); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, -0.5))); + assertEquals(10.0, DatasetUtils.findYValue(dataset, 0, 0.0), EPSILON); + assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); + assertEquals(15.0, DatasetUtils.findYValue(dataset, 0, 2.0), EPSILON); + assertEquals(20.0, DatasetUtils.findYValue(dataset, 0, 4.0), EPSILON); + assertEquals(17.5, DatasetUtils.findYValue(dataset, 0, 3.0), EPSILON); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 5.0))); + } + + /** + * Check the findYValue() method with a dataset that allows duplicate + * values. + */ + @Test + public void testFindYValueWithDuplicates() { + XYSeries series = new XYSeries("S1", true, true); + XYSeriesCollection dataset = new XYSeriesCollection(series); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 100.0))); + + series.add(1.0, 5.0); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); + assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 2.0))); + + series.add(1.0, 10.0); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); + assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 2.0))); + + series.add(2.0, 10.0); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 0.0))); + assertEquals(5.0, DatasetUtils.findYValue(dataset, 0, 1.0), EPSILON); + assertEquals(10.0, DatasetUtils.findYValue(dataset, 0, 1.25), EPSILON); + assertEquals(10.0, DatasetUtils.findYValue(dataset, 0, 1.5), EPSILON); + assertEquals(10.0, DatasetUtils.findYValue(dataset, 0, 2.0), EPSILON); + assertTrue(Double.isNaN(DatasetUtils.findYValue(dataset, 0, 3.0))); + } + +} diff --git a/src/test/java/org/jfree/data/general/TestIntervalCategoryDataset.java b/src/test/java/org/jfree/data/general/TestIntervalCategoryDataset.java index 56f11c870..953a63782 100644 --- a/src/test/java/org/jfree/data/general/TestIntervalCategoryDataset.java +++ b/src/test/java/org/jfree/data/general/TestIntervalCategoryDataset.java @@ -1,428 +1,428 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * -------------------------------- - * TestIntervalCategoryDataset.java - * -------------------------------- - * (C) Copyright 2009-present, by David Gilbert. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.data.general; - -import java.io.Serializable; -import java.util.List; -import org.jfree.chart.util.PublicCloneable; - -import org.jfree.data.KeyedObjects2D; -import org.jfree.data.UnknownKeyException; -import org.jfree.data.category.IntervalCategoryDataset; -/** - * A test implementation of the {@link IntervalCategoryDataset} interface. - */ -public class TestIntervalCategoryDataset extends AbstractDataset - implements IntervalCategoryDataset, PublicCloneable, Serializable { - - /** For serialization. */ - private static final long serialVersionUID = -8168173757291644622L; - - /** A storage structure for the data. */ - private KeyedObjects2D data; - - /** - * Creates a new (empty) dataset. - */ - public TestIntervalCategoryDataset() { - this.data = new KeyedObjects2D(); - } - - /** - * Returns the number of rows in the table. - * - * @return The row count. - * - * @see #getColumnCount() - */ - @Override - public int getRowCount() { - return this.data.getRowCount(); - } - - /** - * Returns the number of columns in the table. - * - * @return The column count. - * - * @see #getRowCount() - */ - @Override - public int getColumnCount() { - return this.data.getColumnCount(); - } - - /** - * Returns a value from the table. - * - * @param row the row index (zero-based). - * @param column the column index (zero-based). - * - * @return The value (possibly {@code null}). - */ - @Override - public Number getValue(int row, int column) { - IntervalDataItem item = (IntervalDataItem) this.data.getObject(row, - column); - if (item == null) { - return null; - } - return item.getValue(); - } - - /** - * Returns the key for the specified row. - * - * @param row the row index (zero-based). - * - * @return The row key. - * - * @see #getRowIndex(Comparable) - * @see #getRowKeys() - * @see #getColumnKey(int) - */ - @Override - public Comparable getRowKey(int row) { - return this.data.getRowKey(row); - } - - /** - * Returns the row index for a given key. - * - * @param key the row key ({@code null} not permitted). - * - * @return The row index. - * - * @see #getRowKey(int) - */ - @Override - public int getRowIndex(Comparable key) { - // defer null argument check - return this.data.getRowIndex(key); - } - - /** - * Returns the row keys. - * - * @return The keys. - * - * @see #getRowKey(int) - */ - @Override - public List getRowKeys() { - return this.data.getRowKeys(); - } - - /** - * Returns a column key. - * - * @param column the column index (zero-based). - * - * @return The column key. - * - * @see #getColumnIndex(Comparable) - */ - @Override - public Comparable getColumnKey(int column) { - return this.data.getColumnKey(column); - } - - /** - * Returns the column index for a given key. - * - * @param key the column key ({@code null} not permitted). - * - * @return The column index. - * - * @see #getColumnKey(int) - */ - @Override - public int getColumnIndex(Comparable key) { - // defer null argument check - return this.data.getColumnIndex(key); - } - - /** - * Returns the column keys. - * - * @return The keys. - * - * @see #getColumnKey(int) - */ - @Override - public List getColumnKeys() { - return this.data.getColumnKeys(); - } - - /** - * Returns the value for a pair of keys. - * - * @param rowKey the row key ({@code null} not permitted). - * @param columnKey the column key ({@code null} not permitted). - * - * @return The value (possibly {@code null}). - * - * @throws UnknownKeyException if either key is not defined in the dataset. - */ - @Override - public Number getValue(Comparable rowKey, Comparable columnKey) { - IntervalDataItem item = (IntervalDataItem) this.data.getObject(rowKey, - columnKey); - if (item == null) { - return null; - } - return item.getValue(); - } - - /** - * Adds a value to the table. Performs the same function as setValue(). - * - * @param value the value. - * @param rowKey the row key. - * @param columnKey the column key. - * - * @see #getValue(Comparable, Comparable) - */ - public void addItem(Number value, Number lower, Number upper, - Comparable rowKey, Comparable columnKey) { - IntervalDataItem item = new IntervalDataItem(value, lower, upper); - this.data.addObject(item, rowKey, columnKey); - fireDatasetChanged(); - } - - /** - * Adds or updates a value in the table and sends a - * {@link DatasetChangeEvent} to all registered listeners. - * - * @param value the value ({@code null} permitted). - * @param rowKey the row key ({@code null} not permitted). - * @param columnKey the column key ({@code null} not permitted). - * - * @see #getValue(Comparable, Comparable) - */ - public void setItem(Number value, Number lower, Number upper, - Comparable rowKey, Comparable columnKey) { - IntervalDataItem item = new IntervalDataItem(value, lower, upper); - this.data.addObject(item, rowKey, columnKey); - fireDatasetChanged(); - } - - /** - * Removes a value from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. - * - * @param rowKey the row key. - * @param columnKey the column key. - */ - public void removeItem(Comparable rowKey, Comparable columnKey) { - this.data.removeObject(rowKey, columnKey); - fireDatasetChanged(); - } - - /** - * Removes a row from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. - * - * @param rowIndex the row index. - * - * @see #removeColumn(int) - */ - public void removeRow(int rowIndex) { - this.data.removeRow(rowIndex); - fireDatasetChanged(); - } - - /** - * Removes a row from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. - * - * @param rowKey the row key. - * - * @see #removeColumn(Comparable) - */ - public void removeRow(Comparable rowKey) { - this.data.removeRow(rowKey); - fireDatasetChanged(); - } - - /** - * Removes a column from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. - * - * @param columnIndex the column index. - * - * @see #removeRow(int) - */ - public void removeColumn(int columnIndex) { - this.data.removeColumn(columnIndex); - fireDatasetChanged(); - } - - /** - * Removes a column from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. - * - * @param columnKey the column key ({@code null} not permitted). - * - * @see #removeRow(Comparable) - * - * @throws UnknownKeyException if {@code columnKey} is not defined - * in the dataset. - */ - public void removeColumn(Comparable columnKey) { - this.data.removeColumn(columnKey); - fireDatasetChanged(); - } - - /** - * Clears all data from the dataset and sends a {@link DatasetChangeEvent} - * to all registered listeners. - */ - public void clear() { - this.data.clear(); - fireDatasetChanged(); - } - - /** - * Tests this dataset for equality with an arbitrary object. - * - * @param obj the object ({@code null} permitted). - * - * @return A boolean. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof TestIntervalCategoryDataset)) { - return false; - } - TestIntervalCategoryDataset that = (TestIntervalCategoryDataset) obj; - if (!getRowKeys().equals(that.getRowKeys())) { - return false; - } - if (!getColumnKeys().equals(that.getColumnKeys())) { - return false; - } - int rowCount = getRowCount(); - int colCount = getColumnCount(); - for (int r = 0; r < rowCount; r++) { - for (int c = 0; c < colCount; c++) { - Number v1 = getValue(r, c); - Number v2 = that.getValue(r, c); - if (v1 == null) { - if (v2 != null) { - return false; - } - } - else if (!v1.equals(v2)) { - return false; - } - } - } - return true; - } - - /** - * Returns a hash code for the dataset. - * - * @return A hash code. - */ - @Override - public int hashCode() { - return this.data.hashCode(); - } - - /** - * Returns a clone of the dataset. - * - * @return A clone. - * - * @throws CloneNotSupportedException if there is a problem cloning the - * dataset. - */ - @Override - public Object clone() throws CloneNotSupportedException { - TestIntervalCategoryDataset clone = (TestIntervalCategoryDataset) - super.clone(); - clone.data = (KeyedObjects2D) this.data.clone(); - return clone; - } - - @Override - public Number getStartValue(int series, int category) { - IntervalDataItem item = (IntervalDataItem) this.data.getObject(series, - category); - if (item == null) { - return null; - } - return item.getLowerBound(); - } - - @Override - public Number getStartValue(Comparable series, Comparable category) { - IntervalDataItem item = (IntervalDataItem) this.data.getObject(series, - category); - if (item == null) { - return null; - } - return item.getLowerBound(); - } - - @Override - public Number getEndValue(int series, int category) { - IntervalDataItem item = (IntervalDataItem) this.data.getObject(series, - category); - if (item == null) { - return null; - } - return item.getUpperBound(); - } - - @Override - public Number getEndValue(Comparable series, Comparable category) { - IntervalDataItem item = (IntervalDataItem) this.data.getObject(series, - category); - if (item == null) { - return null; - } - return item.getUpperBound(); - } - -} - +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * -------------------------------- + * TestIntervalCategoryDataset.java + * -------------------------------- + * (C) Copyright 2009-present, by David Gilbert. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.data.general; + +import java.io.Serializable; +import java.util.List; +import org.jfree.chart.util.PublicCloneable; + +import org.jfree.data.KeyedObjects2D; +import org.jfree.data.category.IntervalCategoryDataset; +/** + * A test implementation of the {@link IntervalCategoryDataset} interface. + */ +public class TestIntervalCategoryDataset extends AbstractDataset + implements IntervalCategoryDataset, PublicCloneable, Serializable { + + /** For serialization. */ + private static final long serialVersionUID = -8168173757291644622L; + + /** A storage structure for the data. */ + private KeyedObjects2D data; + + /** + * Creates a new (empty) dataset. + */ + public TestIntervalCategoryDataset() { + this.data = new KeyedObjects2D(); + } + + /** + * Returns the number of rows in the table. + * + * @return The row count. + * + * @see #getColumnCount() + */ + @Override + public int getRowCount() { + return this.data.getRowCount(); + } + + /** + * Returns the number of columns in the table. + * + * @return The column count. + * + * @see #getRowCount() + */ + @Override + public int getColumnCount() { + return this.data.getColumnCount(); + } + + /** + * Returns a value from the table. + * + * @param row the row index (zero-based). + * @param column the column index (zero-based). + * + * @return The value (possibly {@code null}). + */ + @Override + public Number getValue(int row, int column) { + IntervalDataItem item = (IntervalDataItem) this.data.getObject(row, + column); + if (item == null) { + return null; + } + return item.getValue(); + } + + /** + * Returns the key for the specified row. + * + * @param row the row index (zero-based). + * + * @return The row key. + * + * @see #getRowIndex(Comparable) + * @see #getRowKeys() + * @see #getColumnKey(int) + */ + @Override + public Comparable getRowKey(int row) { + return this.data.getRowKey(row); + } + + /** + * Returns the row index for a given key. + * + * @param key the row key ({@code null} not permitted). + * + * @return The row index. + * + * @see #getRowKey(int) + */ + @Override + public int getRowIndex(Comparable key) { + // defer null argument check + return this.data.getRowIndex(key); + } + + /** + * Returns the row keys. + * + * @return The keys. + * + * @see #getRowKey(int) + */ + @Override + public List getRowKeys() { + return this.data.getRowKeys(); + } + + /** + * Returns a column key. + * + * @param column the column index (zero-based). + * + * @return The column key. + * + * @see #getColumnIndex(Comparable) + */ + @Override + public Comparable getColumnKey(int column) { + return this.data.getColumnKey(column); + } + + /** + * Returns the column index for a given key. + * + * @param key the column key ({@code null} not permitted). + * + * @return The column index. + * + * @see #getColumnKey(int) + */ + @Override + public int getColumnIndex(Comparable key) { + // defer null argument check + return this.data.getColumnIndex(key); + } + + /** + * Returns the column keys. + * + * @return The keys. + * + * @see #getColumnKey(int) + */ + @Override + public List getColumnKeys() { + return this.data.getColumnKeys(); + } + + /** + * Returns the value for a pair of keys. + * + * @param rowKey the row key ({@code null} not permitted). + * @param columnKey the column key ({@code null} not permitted). + * + * @return The value (possibly {@code null}). + * + * @throws org.jfree.data.UnknownKeyException if either key is not defined + * in the dataset. + */ + @Override + public Number getValue(Comparable rowKey, Comparable columnKey) { + IntervalDataItem item = (IntervalDataItem) this.data.getObject(rowKey, + columnKey); + if (item == null) { + return null; + } + return item.getValue(); + } + + /** + * Adds a value to the table. Performs the same function as setValue(). + * + * @param value the value. + * @param rowKey the row key. + * @param columnKey the column key. + * + * @see #getValue(Comparable, Comparable) + */ + public void addItem(Number value, Number lower, Number upper, + Comparable rowKey, Comparable columnKey) { + IntervalDataItem item = new IntervalDataItem(value, lower, upper); + this.data.addObject(item, rowKey, columnKey); + fireDatasetChanged(); + } + + /** + * Adds or updates a value in the table and sends a + * {@link DatasetChangeEvent} to all registered listeners. + * + * @param value the value ({@code null} permitted). + * @param rowKey the row key ({@code null} not permitted). + * @param columnKey the column key ({@code null} not permitted). + * + * @see #getValue(Comparable, Comparable) + */ + public void setItem(Number value, Number lower, Number upper, + Comparable rowKey, Comparable columnKey) { + IntervalDataItem item = new IntervalDataItem(value, lower, upper); + this.data.addObject(item, rowKey, columnKey); + fireDatasetChanged(); + } + + /** + * Removes a value from the dataset and sends a {@link DatasetChangeEvent} + * to all registered listeners. + * + * @param rowKey the row key. + * @param columnKey the column key. + */ + public void removeItem(Comparable rowKey, Comparable columnKey) { + this.data.removeObject(rowKey, columnKey); + fireDatasetChanged(); + } + + /** + * Removes a row from the dataset and sends a {@link DatasetChangeEvent} + * to all registered listeners. + * + * @param rowIndex the row index. + * + * @see #removeColumn(int) + */ + public void removeRow(int rowIndex) { + this.data.removeRow(rowIndex); + fireDatasetChanged(); + } + + /** + * Removes a row from the dataset and sends a {@link DatasetChangeEvent} + * to all registered listeners. + * + * @param rowKey the row key. + * + * @see #removeColumn(Comparable) + */ + public void removeRow(Comparable rowKey) { + this.data.removeRow(rowKey); + fireDatasetChanged(); + } + + /** + * Removes a column from the dataset and sends a {@link DatasetChangeEvent} + * to all registered listeners. + * + * @param columnIndex the column index. + * + * @see #removeRow(int) + */ + public void removeColumn(int columnIndex) { + this.data.removeColumn(columnIndex); + fireDatasetChanged(); + } + + /** + * Removes a column from the dataset and sends a {@link DatasetChangeEvent} to + * all registered listeners. + * + * @param columnKey the column key ({@code null} not permitted). + * + * @see #removeRow(Comparable) + * + * @throws org.jfree.data.UnknownKeyException if {@code columnKey} is not + * defined in the dataset. + */ + public void removeColumn(Comparable columnKey) { + this.data.removeColumn(columnKey); + fireDatasetChanged(); + } + + /** + * Clears all data from the dataset and sends a {@link DatasetChangeEvent} + * to all registered listeners. + */ + public void clear() { + this.data.clear(); + fireDatasetChanged(); + } + + /** + * Tests this dataset for equality with an arbitrary object. + * + * @param obj the object ({@code null} permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof TestIntervalCategoryDataset)) { + return false; + } + TestIntervalCategoryDataset that = (TestIntervalCategoryDataset) obj; + if (!getRowKeys().equals(that.getRowKeys())) { + return false; + } + if (!getColumnKeys().equals(that.getColumnKeys())) { + return false; + } + int rowCount = getRowCount(); + int colCount = getColumnCount(); + for (int r = 0; r < rowCount; r++) { + for (int c = 0; c < colCount; c++) { + Number v1 = getValue(r, c); + Number v2 = that.getValue(r, c); + if (v1 == null) { + if (v2 != null) { + return false; + } + } + else if (!v1.equals(v2)) { + return false; + } + } + } + return true; + } + + /** + * Returns a hash code for the dataset. + * + * @return A hash code. + */ + @Override + public int hashCode() { + return this.data.hashCode(); + } + + /** + * Returns a clone of the dataset. + * + * @return A clone. + * + * @throws CloneNotSupportedException if there is a problem cloning the + * dataset. + */ + @Override + public Object clone() throws CloneNotSupportedException { + TestIntervalCategoryDataset clone = (TestIntervalCategoryDataset) + super.clone(); + clone.data = (KeyedObjects2D) this.data.clone(); + return clone; + } + + @Override + public Number getStartValue(int series, int category) { + IntervalDataItem item = (IntervalDataItem) this.data.getObject(series, + category); + if (item == null) { + return null; + } + return item.getLowerBound(); + } + + @Override + public Number getStartValue(Comparable series, Comparable category) { + IntervalDataItem item = (IntervalDataItem) this.data.getObject(series, + category); + if (item == null) { + return null; + } + return item.getLowerBound(); + } + + @Override + public Number getEndValue(int series, int category) { + IntervalDataItem item = (IntervalDataItem) this.data.getObject(series, + category); + if (item == null) { + return null; + } + return item.getUpperBound(); + } + + @Override + public Number getEndValue(Comparable series, Comparable category) { + IntervalDataItem item = (IntervalDataItem) this.data.getObject(series, + category); + if (item == null) { + return null; + } + return item.getUpperBound(); + } + +} + diff --git a/src/test/java/org/jfree/data/xy/XYSeriesTest.java b/src/test/java/org/jfree/data/xy/XYSeriesTest.java index ebcd9ba42..53a237def 100644 --- a/src/test/java/org/jfree/data/xy/XYSeriesTest.java +++ b/src/test/java/org/jfree/data/xy/XYSeriesTest.java @@ -1,808 +1,808 @@ -/* =========================================================== - * JFreeChart : a free chart library for the Java(tm) platform - * =========================================================== - * - * (C) Copyright 2000-present, by David Gilbert and Contributors. - * - * Project Info: http://www.jfree.org/jfreechart/index.html - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners.] - * - * ----------------- - * XYSeriesTest.java - * ----------------- - * (C) Copyright 2003-present, by David Gilbert and Contributors. - * - * Original Author: David Gilbert; - * Contributor(s): -; - * - */ - -package org.jfree.data.xy; - -import org.jfree.chart.TestUtils; - -import org.jfree.data.general.SeriesException; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests for the {@link XYSeries} class. - */ -public class XYSeriesTest { - - /** - * Confirm that the equals method can distinguish all the required fields. - */ - @Test - public void testEquals() { - XYSeries s1 = new XYSeries("Series"); - s1.add(1.0, 1.1); - XYSeries s2 = new XYSeries("Series"); - s2.add(1.0, 1.1); - assertEquals(s1, s2); - assertEquals(s2, s1); - - s1.setKey("Series X"); - assertNotEquals(s1, s2); - s2.setKey("Series X"); - assertEquals(s1, s2); - - s1.add(2.0, 2.2); - assertNotEquals(s1, s2); - s2.add(2.0, 2.2); - assertEquals(s1, s2); - } - - /** - * Some simple checks for the hashCode() method. - */ - @Test - public void testHashCode() { - XYSeries s1 = new XYSeries("Test"); - XYSeries s2 = new XYSeries("Test"); - assertEquals(s1, s2); - assertEquals(s1.hashCode(), s2.hashCode()); - - s1.add(1.0, 500.0); - s2.add(1.0, 500.0); - assertEquals(s1, s2); - assertEquals(s1.hashCode(), s2.hashCode()); - - s1.add(2.0, null); - s2.add(2.0, null); - assertEquals(s1, s2); - assertEquals(s1.hashCode(), s2.hashCode()); - - s1.add(5.0, 111.0); - s2.add(5.0, 111.0); - assertEquals(s1, s2); - assertEquals(s1.hashCode(), s2.hashCode()); - - s1.add(9.0, 1.0); - s2.add(9.0, 1.0); - assertEquals(s1, s2); - assertEquals(s1.hashCode(), s2.hashCode()); - } - - /** - * Confirm that cloning works. - * - * @throws java.lang.CloneNotSupportedException if there is a problem cloning. - */ - @Test - public void testCloning() throws CloneNotSupportedException { - XYSeries s1 = new XYSeries("Series"); - s1.add(1.0, 1.1); - XYSeries s2 = (XYSeries) s1.clone(); - assertNotSame(s1, s2); - assertSame(s1.getClass(), s2.getClass()); - assertEquals(s1, s2); - } - - /** - * Another test of the clone() method. - * - * @throws java.lang.CloneNotSupportedException if there is a problem cloning. - */ - @Test - public void testCloning2() throws CloneNotSupportedException { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, 100.0); - s1.add(2.0, null); - s1.add(3.0, 200.0); - XYSeries s2 = (XYSeries) s1.clone(); - assertEquals(s1, s2); - - // check independence - s2.add(4.0, 300.0); - assertNotEquals(s1, s2); - s1.add(4.0, 300.0); - assertEquals(s1, s2); - } - - /** - * Another test of the clone() method. - * - * @throws java.lang.CloneNotSupportedException if there is a problem cloning. - */ - @Test - public void testCloning3() throws CloneNotSupportedException { - XYSeries s1 = new XYSeries("S1"); - XYSeries s2 = (XYSeries) s1.clone(); - assertEquals(s1, s2); - - // check independence - s2.add(4.0, 300.0); - assertNotEquals(s1, s2); - s1.add(4.0, 300.0); - assertEquals(s1, s2); - } - - /** - * Serialize an instance, restore it, and check for equality. - */ - @Test - public void testSerialization() { - XYSeries s1 = new XYSeries("Series"); - s1.add(1.0, 1.1); - XYSeries s2 = TestUtils.serialised(s1); - assertEquals(s1, s2); - - // check independence (and also serialization mechanism for change listeners) - s2.setKey("New Series Key"); - assertNotEquals(s1, s2); - s1.setKey("New Series Key"); - assertEquals(s1, s2); - } - - /** - * Simple test for the indexOf() method. - */ - @Test - public void testIndexOf() { - XYSeries s1 = new XYSeries("Series 1"); - s1.add(1.0, 1.0); - s1.add(2.0, 2.0); - s1.add(3.0, 3.0); - assertEquals(0, s1.indexOf(1.0)); - assertEquals(1, s1.indexOf(2.0)); - assertEquals(2, s1.indexOf(3.0)); - assertEquals(-4, s1.indexOf(99.9)); - } - - /** - * A check for the indexOf() method for an unsorted series. - */ - @Test - public void testIndexOf2() { - XYSeries s1 = new XYSeries("Series 1", false, true); - s1.add(1.0, 1.0); - s1.add(3.0, 3.0); - s1.add(2.0, 2.0); - assertEquals(0, s1.indexOf(1.0)); - assertEquals(1, s1.indexOf(3.0)); - assertEquals(2, s1.indexOf(2.0)); - } - - /** - * A check for the indexOf(Number) method when the series has duplicate - * x-values. - */ - @Test - public void testIndexOf3() { - XYSeries s1 = new XYSeries("Series 1"); - s1.add(1.0, 1.0); - s1.add(2.0, 2.0); - s1.add(2.0, 3.0); - assertEquals(0, s1.indexOf(1.0)); - assertEquals(1, s1.indexOf(2.0)); - } - - /** - * Simple test for the remove() method. - */ - @Test - public void testRemove() { - XYSeries s1 = new XYSeries("Series 1"); - s1.add(1.0, 1.0); - s1.add(2.0, 2.0); - s1.add(3.0, 3.0); - assertEquals(3, s1.getItemCount()); - - s1.remove(2.0); - assertEquals(3.0, s1.getX(1)); - - s1.remove(0); - assertEquals(3.0, s1.getX(0)); - } - - /** - * Some checks for the remove(int) method. - */ - @Test - public void testRemove2() { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, 1.1); - s1.add(2.0, 2.2); - s1.add(3.0, 3.3); - s1.add(4.0, 4.4); - s1.add(5.0, 5.5); - s1.add(6.0, 6.6); - assertEquals(6, s1.getItemCount()); - assertEquals(1.0, s1.getMinX(), EPSILON); - assertEquals(6.0, s1.getMaxX(), EPSILON); - assertEquals(1.1, s1.getMinY(), EPSILON); - assertEquals(6.6, s1.getMaxY(), EPSILON); - - s1.remove(5); - assertEquals(5, s1.getItemCount()); - assertEquals(1.0, s1.getMinX(), EPSILON); - assertEquals(5.0, s1.getMaxX(), EPSILON); - assertEquals(1.1, s1.getMinY(), EPSILON); - assertEquals(5.5, s1.getMaxY(), EPSILON); - } - - private static final double EPSILON = 0.0000000001; - - /** - * When items are added with duplicate x-values, we expect them to remain - * in the order they were added. - */ - @Test - public void testAdditionOfDuplicateXValues() { - XYSeries s1 = new XYSeries("Series 1"); - s1.add(1.0, 1.0); - s1.add(2.0, 2.0); - s1.add(2.0, 3.0); - s1.add(2.0, 4.0); - s1.add(3.0, 5.0); - assertEquals(1.0, s1.getY(0).doubleValue(), EPSILON); - assertEquals(2.0, s1.getY(1).doubleValue(), EPSILON); - assertEquals(3.0, s1.getY(2).doubleValue(), EPSILON); - assertEquals(4.0, s1.getY(3).doubleValue(), EPSILON); - assertEquals(5.0, s1.getY(4).doubleValue(), EPSILON); - } - - /** - * Some checks for the update(Number, Number) method. - */ - @Test - public void testUpdate() { - XYSeries series = new XYSeries("S1"); - series.add(Integer.valueOf(1), Integer.valueOf(2)); - assertEquals(2, series.getY(0)); - series.update(1, 3); - assertEquals(3, series.getY(0)); - try { - series.update(2, 99); - fail(); - } - catch (SeriesException e) { - // got the required exception - } - } - - /** - * Some checks for the update() method for an unsorted series. - */ - @Test - public void testUpdate2() { - XYSeries series = new XYSeries("Series", false, true); - series.add(5.0, 55.0); - series.add(4.0, 44.0); - series.add(6.0, 66.0); - series.update(4.0, 99.0); - assertEquals(99.0, series.getY(1)); - } - - /** - * Some checks for the addOrUpdate() method. - */ - @Test - public void testAddOrUpdate() { - XYSeries series = new XYSeries("S1", true, false); - XYDataItem old = series.addOrUpdate(Long.valueOf(1), Long.valueOf(2)); - assertNull(old); - assertEquals(1, series.getItemCount()); - assertEquals(2L, series.getY(0)); - - old = series.addOrUpdate(Long.valueOf(2), Long.valueOf(3)); - assertNull(old); - assertEquals(2, series.getItemCount()); - assertEquals(3L, series.getY(1)); - - old = series.addOrUpdate(Long.valueOf(1), Long.valueOf(99)); - assertEquals(new XYDataItem(Long.valueOf(1), Long.valueOf(2)), old); - assertEquals(2, series.getItemCount()); - assertEquals(99L, series.getY(0)); - assertEquals(3L, series.getY(1)); - } - - /** - * Some checks for the addOrUpdate() method for an UNSORTED series. - */ - @Test - public void testAddOrUpdate2() { - XYSeries series = new XYSeries("Series", false, false); - series.add(5.0, 5.5); - series.add(6.0, 6.6); - series.add(3.0, 3.3); - series.add(4.0, 4.4); - series.add(2.0, 2.2); - series.add(1.0, 1.1); - series.addOrUpdate(3.0, 33.3); - series.addOrUpdate(2.0, 22.2); - assertEquals(33.3, series.getY(2).doubleValue(), EPSILON); - assertEquals(22.2, series.getY(4).doubleValue(), EPSILON); - } - - /** - * Another test for the addOrUpdate() method. - */ - @Test - public void testAddOrUpdate3() { - XYSeries series = new XYSeries("Series", false, true); - series.addOrUpdate(1.0, 1.0); - series.addOrUpdate(1.0, 2.0); - series.addOrUpdate(1.0, 3.0); - assertEquals(1.0, series.getY(0)); - assertEquals(2.0, series.getY(1)); - assertEquals(3.0, series.getY(2)); - assertEquals(3, series.getItemCount()); - } - - /** - * Some checks for the add() method for an UNSORTED series. - */ - @Test - public void testAdd() { - XYSeries series = new XYSeries("Series", false, true); - series.add(5.0, 5.50); - series.add(5.1, 5.51); - series.add(6.0, 6.6); - series.add(3.0, 3.3); - series.add(4.0, 4.4); - series.add(2.0, 2.2); - series.add(1.0, 1.1); - assertEquals(5.5, series.getY(0).doubleValue(), EPSILON); - assertEquals(5.51, series.getY(1).doubleValue(), EPSILON); - assertEquals(6.6, series.getY(2).doubleValue(), EPSILON); - assertEquals(3.3, series.getY(3).doubleValue(), EPSILON); - assertEquals(4.4, series.getY(4).doubleValue(), EPSILON); - assertEquals(2.2, series.getY(5).doubleValue(), EPSILON); - assertEquals(1.1, series.getY(6).doubleValue(), EPSILON); - } - - /** - * A simple check that the maximumItemCount attribute is working. - */ - @Test - public void testSetMaximumItemCount() { - XYSeries s1 = new XYSeries("S1"); - assertEquals(Integer.MAX_VALUE, s1.getMaximumItemCount()); - s1.setMaximumItemCount(2); - assertEquals(2, s1.getMaximumItemCount()); - s1.add(1.0, 1.1); - s1.add(2.0, 2.2); - s1.add(3.0, 3.3); - assertEquals(2.0, s1.getX(0).doubleValue(), EPSILON); - assertEquals(3.0, s1.getX(1).doubleValue(), EPSILON); - } - - /** - * Check that the maximum item count can be applied retrospectively. - */ - @Test - public void testSetMaximumItemCount2() { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, 1.1); - s1.add(2.0, 2.2); - s1.add(3.0, 3.3); - s1.setMaximumItemCount(2); - assertEquals(2.0, s1.getX(0).doubleValue(), EPSILON); - assertEquals(3.0, s1.getX(1).doubleValue(), EPSILON); - } - - /** - * Check that the item bounds are determined correctly when there is a - * maximum item count. - */ - @Test - public void testSetMaximumItemCount3() { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, 1.1); - s1.add(2.0, 2.2); - s1.add(3.0, 3.3); - s1.add(4.0, 4.4); - s1.add(5.0, 5.5); - s1.add(6.0, 6.6); - s1.setMaximumItemCount(2); - assertEquals(5.0, s1.getX(0).doubleValue(), EPSILON); - assertEquals(6.0, s1.getX(1).doubleValue(), EPSILON); - assertEquals(5.0, s1.getMinX(), EPSILON); - assertEquals(6.0, s1.getMaxX(), EPSILON); - assertEquals(5.5, s1.getMinY(), EPSILON); - assertEquals(6.6, s1.getMaxY(), EPSILON); - } - - /** - * Check that the item bounds are determined correctly when there is a - * maximum item count. - */ - @Test - public void testSetMaximumItemCount4() { - XYSeries s1 = new XYSeries("S1"); - s1.setMaximumItemCount(2); - s1.add(1.0, 1.1); - s1.add(2.0, 2.2); - s1.add(3.0, 3.3); - assertEquals(2.0, s1.getX(0).doubleValue(), EPSILON); - assertEquals(3.0, s1.getX(1).doubleValue(), EPSILON); - assertEquals(2.0, s1.getMinX(), EPSILON); - assertEquals(3.0, s1.getMaxX(), EPSILON); - assertEquals(2.2, s1.getMinY(), EPSILON); - assertEquals(3.3, s1.getMaxY(), EPSILON); - } - - /** - * Some checks for the toArray() method. - */ - @Test - public void testToArray() { - XYSeries s = new XYSeries("S1"); - double[][] array = s.toArray(); - assertEquals(2, array.length); - assertEquals(0, array[0].length); - assertEquals(0, array[1].length); - - s.add(1.0, 2.0); - array = s.toArray(); - assertEquals(1, array[0].length); - assertEquals(1, array[1].length); - assertEquals(2, array.length); - assertEquals(1.0, array[0][0], EPSILON); - assertEquals(2.0, array[1][0], EPSILON); - - s.add(2.0, null); - array = s.toArray(); - assertEquals(2, array.length); - assertEquals(2, array[0].length); - assertEquals(2, array[1].length); - assertEquals(2.0, array[0][1], EPSILON); - assertTrue(Double.isNaN(array[1][1])); - } - - /** - * Some checks for an example using the toArray() method. - */ - @Test - public void testToArrayExample() { - XYSeries s = new XYSeries("S"); - s.add(1.0, 11.0); - s.add(2.0, 22.0); - s.add(3.5, 35.0); - s.add(5.0, null); - DefaultXYDataset dataset = new DefaultXYDataset(); - dataset.addSeries("S", s.toArray()); - assertEquals(1, dataset.getSeriesCount()); - assertEquals(4, dataset.getItemCount(0)); - assertEquals("S", dataset.getSeriesKey(0)); - assertEquals(1.0, dataset.getXValue(0, 0), EPSILON); - assertEquals(2.0, dataset.getXValue(0, 1), EPSILON); - assertEquals(3.5, dataset.getXValue(0, 2), EPSILON); - assertEquals(5.0, dataset.getXValue(0, 3), EPSILON); - assertEquals(11.0, dataset.getYValue(0, 0), EPSILON); - assertEquals(22.0, dataset.getYValue(0, 1), EPSILON); - assertEquals(35.0, dataset.getYValue(0, 2), EPSILON); - assertTrue(Double.isNaN(dataset.getYValue(0, 3))); - } - - /** - * Another test for the addOrUpdate() method. - */ - @Test - public void testBug1955483() { - XYSeries series = new XYSeries("Series", true, true); - series.addOrUpdate(1.0, 1.0); - series.addOrUpdate(1.0, 2.0); - assertEquals(1.0, series.getY(0)); - assertEquals(2.0, series.getY(1)); - assertEquals(2, series.getItemCount()); - } - - /** - * Some checks for the delete(int, int) method. - */ - @Test - public void testDelete() { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, 1.1); - s1.add(2.0, 2.2); - s1.add(3.0, 3.3); - s1.add(4.0, 4.4); - s1.add(5.0, 5.5); - s1.add(6.0, 6.6); - s1.delete(2, 5); - assertEquals(2, s1.getItemCount()); - assertEquals(1.0, s1.getX(0).doubleValue(), EPSILON); - assertEquals(2.0, s1.getX(1).doubleValue(), EPSILON); - assertEquals(1.0, s1.getMinX(), EPSILON); - assertEquals(2.0, s1.getMaxX(), EPSILON); - assertEquals(1.1, s1.getMinY(), EPSILON); - assertEquals(2.2, s1.getMaxY(), EPSILON); - } - - /** - * Some checks for the getMinX() method. - */ - @Test - public void testGetMinX() { - XYSeries s1 = new XYSeries("S1"); - assertTrue(Double.isNaN(s1.getMinX())); - - s1.add(1.0, 1.1); - assertEquals(1.0, s1.getMinX(), EPSILON); - - s1.add(2.0, 2.2); - assertEquals(1.0, s1.getMinX(), EPSILON); - - s1.add(Double.NaN, 99.9); - assertEquals(1.0, s1.getMinX(), EPSILON); - - s1.add(-1.0, -1.1); - assertEquals(-1.0, s1.getMinX(), EPSILON); - - s1.add(0.0, null); - assertEquals(-1.0, s1.getMinX(), EPSILON); - } - - /** - * Some checks for the getMaxX() method. - */ - @Test - public void testGetMaxX() { - XYSeries s1 = new XYSeries("S1"); - assertTrue(Double.isNaN(s1.getMaxX())); - - s1.add(1.0, 1.1); - assertEquals(1.0, s1.getMaxX(), EPSILON); - - s1.add(2.0, 2.2); - assertEquals(2.0, s1.getMaxX(), EPSILON); - - s1.add(Double.NaN, 99.9); - assertEquals(2.0, s1.getMaxX(), EPSILON); - - s1.add(-1.0, -1.1); - assertEquals(2.0, s1.getMaxX(), EPSILON); - - s1.add(0.0, null); - assertEquals(2.0, s1.getMaxX(), EPSILON); - } - - /** - * Some checks for the getMinY() method. - */ - @Test - public void testGetMinY() { - XYSeries s1 = new XYSeries("S1"); - assertTrue(Double.isNaN(s1.getMinY())); - - s1.add(1.0, 1.1); - assertEquals(1.1, s1.getMinY(), EPSILON); - - s1.add(2.0, 2.2); - assertEquals(1.1, s1.getMinY(), EPSILON); - - s1.add(Double.NaN, 99.9); - assertEquals(1.1, s1.getMinY(), EPSILON); - - s1.add(-1.0, -1.1); - assertEquals(-1.1, s1.getMinY(), EPSILON); - - s1.add(0.0, null); - assertEquals(-1.1, s1.getMinY(), EPSILON); - } - - /** - * Some checks for the getMaxY() method. - */ - @Test - public void testGetMaxY() { - XYSeries s1 = new XYSeries("S1"); - assertTrue(Double.isNaN(s1.getMaxY())); - - s1.add(1.0, 1.1); - assertEquals(1.1, s1.getMaxY(), EPSILON); - - s1.add(2.0, 2.2); - assertEquals(2.2, s1.getMaxY(), EPSILON); - - s1.add(Double.NaN, 99.9); - assertEquals(99.9, s1.getMaxY(), EPSILON); - - s1.add(-1.0, -1.1); - assertEquals(99.9, s1.getMaxY(), EPSILON); - - s1.add(0.0, null); - assertEquals(99.9, s1.getMaxY(), EPSILON); - } - - /** - * A test for a bug reported in the forum: - * - * http://www.jfree.org/forum/viewtopic.php?f=3&t=116601 - */ - @Test - public void testGetMaxY2() { - XYSeries series = new XYSeries(1, true, false); - series.addOrUpdate(1, 20); - series.addOrUpdate(2, 30); - series.addOrUpdate(3, 40); - assertEquals(40.0, series.getMaxY(), EPSILON); - series.addOrUpdate(2, 22); - assertEquals(40.0, series.getMaxY(), EPSILON); - } - - /** - * A test for the clear method. - */ - @Test - public void testClear() { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, 1.1); - s1.add(2.0, 2.2); - s1.add(3.0, 3.3); - - assertEquals(3, s1.getItemCount()); - - s1.clear(); - assertEquals(0, s1.getItemCount()); - assertTrue(Double.isNaN(s1.getMinX())); - assertTrue(Double.isNaN(s1.getMaxX())); - assertTrue(Double.isNaN(s1.getMinY())); - assertTrue(Double.isNaN(s1.getMaxY())); - } - - /** - * Some checks for the updateByIndex() method. - */ - @Test - public void testUpdateByIndex() { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, 1.1); - s1.add(2.0, 2.2); - s1.add(3.0, 3.3); - - assertEquals(1.1, s1.getMinY(), EPSILON); - assertEquals(3.3, s1.getMaxY(), EPSILON); - - s1.updateByIndex(0, -5.0); - assertEquals(-5.0, s1.getMinY(), EPSILON); - assertEquals(3.3, s1.getMaxY(), EPSILON); - - s1.updateByIndex(0, null); - assertEquals(2.2, s1.getMinY(), EPSILON); - assertEquals(3.3, s1.getMaxY(), EPSILON); - - s1.updateByIndex(2, null); - assertEquals(2.2, s1.getMinY(), EPSILON); - assertEquals(2.2, s1.getMaxY(), EPSILON); - - s1.updateByIndex(1, null); - assertTrue(Double.isNaN(s1.getMinY())); - assertTrue(Double.isNaN(s1.getMaxY())); - } - - /** - * Some checks for the updateByIndex() method. - */ - @Test - public void testUpdateByIndex2() { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, Double.NaN); - - assertTrue(Double.isNaN(s1.getMinY())); - assertTrue(Double.isNaN(s1.getMaxY())); - - s1.updateByIndex(0, 1.0); - assertEquals(1.0, s1.getMinY(), EPSILON); - assertEquals(1.0, s1.getMaxY(), EPSILON); - - s1.updateByIndex(0, 2.0); - assertEquals(2.0, s1.getMinY(), EPSILON); - assertEquals(2.0, s1.getMaxY(), EPSILON); - - s1.add(-1.0, -1.0); - s1.updateByIndex(0, 0.0); - assertEquals(0.0, s1.getMinY(), EPSILON); - assertEquals(2.0, s1.getMaxY(), EPSILON); - } - - /** - * Some checks for the updateByIndex() method. - */ - @Test - public void testUpdateByIndex3() { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, 1.1); - s1.add(2.0, 2.2); - s1.add(3.0, 3.3); - - s1.updateByIndex(1, 2.05); - assertEquals(1.1, s1.getMinY(), EPSILON); - assertEquals(3.3, s1.getMaxY(), EPSILON); - } - - /** - * Some checks for the update(Number, Number) method. - */ - @Test - public void testUpdateXY() { - XYSeries s1 = new XYSeries("S1"); - s1.add(1.0, Double.NaN); - - assertTrue(Double.isNaN(s1.getMinY())); - assertTrue(Double.isNaN(s1.getMaxY())); - - s1.update(1.0, 1.0); - assertEquals(1.0, s1.getMinY(), EPSILON); - assertEquals(1.0, s1.getMaxY(), EPSILON); - - s1.update(1.0, 2.0); - assertEquals(2.0, s1.getMinY(), EPSILON); - assertEquals(2.0, s1.getMaxY(), EPSILON); - } - - @Test - public void testSetKey() { - XYSeries s1 = new XYSeries("S"); - s1.setKey("S1"); - assertEquals("S1", s1.getKey()); - - XYSeriesCollection c = new XYSeriesCollection(); - c.addSeries(s1); - XYSeries s2 = new XYSeries("S2"); - c.addSeries(s2); - - // now we should be allowed to change s1's key to anything but "S2" - s1.setKey("OK"); - assertEquals("OK", s1.getKey()); - - try { - s1.setKey("S2"); - fail("Expect an exception here."); - } catch (IllegalArgumentException e) { - // OK - } - - // after s1 is removed from the collection, we should be able to set - // the key to anything we want... - c.removeSeries(s1); - s1.setKey("S2"); - - // check that removing by index also works - s1.setKey("S1"); - c.addSeries(s1); - c.removeSeries(1); - s1.setKey("S2"); - } -} +/* =========================================================== + * JFreeChart : a free chart library for the Java(tm) platform + * =========================================================== + * + * (C) Copyright 2000-present, by David Gilbert and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners.] + * + * ----------------- + * XYSeriesTest.java + * ----------------- + * (C) Copyright 2003-present, by David Gilbert and Contributors. + * + * Original Author: David Gilbert; + * Contributor(s): -; + * + */ + +package org.jfree.data.xy; + +import org.jfree.chart.TestUtils; + +import org.jfree.data.general.SeriesException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the {@link XYSeries} class. + */ +public class XYSeriesTest { + + /** + * Confirm that the equals method can distinguish all the required fields. + */ + @Test + public void testEquals() { + XYSeries s1 = new XYSeries("Series"); + s1.add(1.0, 1.1); + XYSeries s2 = new XYSeries("Series"); + s2.add(1.0, 1.1); + assertEquals(s1, s2); + assertEquals(s2, s1); + + s1.setKey("Series X"); + assertNotEquals(s1, s2); + s2.setKey("Series X"); + assertEquals(s1, s2); + + s1.add(2.0, 2.2); + assertNotEquals(s1, s2); + s2.add(2.0, 2.2); + assertEquals(s1, s2); + } + + /** + * Some simple checks for the hashCode() method. + */ + @Test + public void testHashCode() { + XYSeries s1 = new XYSeries("Test"); + XYSeries s2 = new XYSeries("Test"); + assertEquals(s1, s2); + assertEquals(s1.hashCode(), s2.hashCode()); + + s1.add(1.0, 500.0); + s2.add(1.0, 500.0); + assertEquals(s1, s2); + assertEquals(s1.hashCode(), s2.hashCode()); + + s1.add(2.0, null); + s2.add(2.0, null); + assertEquals(s1, s2); + assertEquals(s1.hashCode(), s2.hashCode()); + + s1.add(5.0, 111.0); + s2.add(5.0, 111.0); + assertEquals(s1, s2); + assertEquals(s1.hashCode(), s2.hashCode()); + + s1.add(9.0, 1.0); + s2.add(9.0, 1.0); + assertEquals(s1, s2); + assertEquals(s1.hashCode(), s2.hashCode()); + } + + /** + * Confirm that cloning works. + * + * @throws java.lang.CloneNotSupportedException if there is a problem cloning. + */ + @Test + public void testCloning() throws CloneNotSupportedException { + XYSeries s1 = new XYSeries("Series"); + s1.add(1.0, 1.1); + XYSeries s2 = (XYSeries) s1.clone(); + assertNotSame(s1, s2); + assertSame(s1.getClass(), s2.getClass()); + assertEquals(s1, s2); + } + + /** + * Another test of the clone() method. + * + * @throws java.lang.CloneNotSupportedException if there is a problem cloning. + */ + @Test + public void testCloning2() throws CloneNotSupportedException { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, 100.0); + s1.add(2.0, null); + s1.add(3.0, 200.0); + XYSeries s2 = (XYSeries) s1.clone(); + assertEquals(s1, s2); + + // check independence + s2.add(4.0, 300.0); + assertNotEquals(s1, s2); + s1.add(4.0, 300.0); + assertEquals(s1, s2); + } + + /** + * Another test of the clone() method. + * + * @throws java.lang.CloneNotSupportedException if there is a problem cloning. + */ + @Test + public void testCloning3() throws CloneNotSupportedException { + XYSeries s1 = new XYSeries("S1"); + XYSeries s2 = (XYSeries) s1.clone(); + assertEquals(s1, s2); + + // check independence + s2.add(4.0, 300.0); + assertNotEquals(s1, s2); + s1.add(4.0, 300.0); + assertEquals(s1, s2); + } + + /** + * Serialize an instance, restore it, and check for equality. + */ + @Test + public void testSerialization() { + XYSeries s1 = new XYSeries("Series"); + s1.add(1.0, 1.1); + XYSeries s2 = TestUtils.serialised(s1); + assertEquals(s1, s2); + + // check independence (and also serialization mechanism for change listeners) + s2.setKey("New Series Key"); + assertNotEquals(s1, s2); + s1.setKey("New Series Key"); + assertEquals(s1, s2); + } + + /** + * Simple test for the indexOf() method. + */ + @Test + public void testIndexOf() { + XYSeries s1 = new XYSeries("Series 1"); + s1.add(1.0, 1.0); + s1.add(2.0, 2.0); + s1.add(3.0, 3.0); + assertEquals(0, s1.indexOf(1.0)); + assertEquals(1, s1.indexOf(2.0)); + assertEquals(2, s1.indexOf(3.0)); + assertEquals(-4, s1.indexOf(99.9)); + } + + /** + * A check for the indexOf() method for an unsorted series. + */ + @Test + public void testIndexOf2() { + XYSeries s1 = new XYSeries("Series 1", false, true); + s1.add(1.0, 1.0); + s1.add(3.0, 3.0); + s1.add(2.0, 2.0); + assertEquals(0, s1.indexOf(1.0)); + assertEquals(1, s1.indexOf(3.0)); + assertEquals(2, s1.indexOf(2.0)); + } + + /** + * A check for the indexOf(Number) method when the series has duplicate + * x-values. + */ + @Test + public void testIndexOf3() { + XYSeries s1 = new XYSeries("Series 1"); + s1.add(1.0, 1.0); + s1.add(2.0, 2.0); + s1.add(2.0, 3.0); + assertEquals(0, s1.indexOf(1.0)); + assertEquals(1, s1.indexOf(2.0)); + } + + /** + * Simple test for the remove() method. + */ + @Test + public void testRemove() { + XYSeries s1 = new XYSeries("Series 1"); + s1.add(1.0, 1.0); + s1.add(2.0, 2.0); + s1.add(3.0, 3.0); + assertEquals(3, s1.getItemCount()); + + s1.remove(2.0); + assertEquals(3.0, s1.getX(1)); + + s1.remove(0); + assertEquals(3.0, s1.getX(0)); + } + + /** + * Some checks for the remove(int) method. + */ + @Test + public void testRemove2() { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, 1.1); + s1.add(2.0, 2.2); + s1.add(3.0, 3.3); + s1.add(4.0, 4.4); + s1.add(5.0, 5.5); + s1.add(6.0, 6.6); + assertEquals(6, s1.getItemCount()); + assertEquals(1.0, s1.getMinX(), EPSILON); + assertEquals(6.0, s1.getMaxX(), EPSILON); + assertEquals(1.1, s1.getMinY(), EPSILON); + assertEquals(6.6, s1.getMaxY(), EPSILON); + + s1.remove(5); + assertEquals(5, s1.getItemCount()); + assertEquals(1.0, s1.getMinX(), EPSILON); + assertEquals(5.0, s1.getMaxX(), EPSILON); + assertEquals(1.1, s1.getMinY(), EPSILON); + assertEquals(5.5, s1.getMaxY(), EPSILON); + } + + private static final double EPSILON = 0.0000000001; + + /** + * When items are added with duplicate x-values, we expect them to remain + * in the order they were added. + */ + @Test + public void testAdditionOfDuplicateXValues() { + XYSeries s1 = new XYSeries("Series 1"); + s1.add(1.0, 1.0); + s1.add(2.0, 2.0); + s1.add(2.0, 3.0); + s1.add(2.0, 4.0); + s1.add(3.0, 5.0); + assertEquals(1.0, s1.getY(0).doubleValue(), EPSILON); + assertEquals(2.0, s1.getY(1).doubleValue(), EPSILON); + assertEquals(3.0, s1.getY(2).doubleValue(), EPSILON); + assertEquals(4.0, s1.getY(3).doubleValue(), EPSILON); + assertEquals(5.0, s1.getY(4).doubleValue(), EPSILON); + } + + /** + * Some checks for the update(Number, Number) method. + */ + @Test + public void testUpdate() { + XYSeries series = new XYSeries("S1"); + series.add(Integer.valueOf(1), Integer.valueOf(2)); + assertEquals(2, series.getY(0)); + series.update(1, 3); + assertEquals(3, series.getY(0)); + try { + series.update(2, 99); + fail(); + } + catch (SeriesException e) { + // got the required exception + } + } + + /** + * Some checks for the update() method for an unsorted series. + */ + @Test + public void testUpdate2() { + XYSeries series = new XYSeries("Series", false, true); + series.add(5.0, 55.0); + series.add(4.0, 44.0); + series.add(6.0, 66.0); + series.update(4.0, 99.0); + assertEquals(99.0, series.getY(1)); + } + + /** + * Some checks for the addOrUpdate() method. + */ + @Test + public void testAddOrUpdate() { + XYSeries series = new XYSeries("S1", true, false); + XYDataItem old = series.addOrUpdate(Long.valueOf(1), Long.valueOf(2)); + assertNull(old); + assertEquals(1, series.getItemCount()); + assertEquals(2L, series.getY(0)); + + old = series.addOrUpdate(Long.valueOf(2), Long.valueOf(3)); + assertNull(old); + assertEquals(2, series.getItemCount()); + assertEquals(3L, series.getY(1)); + + old = series.addOrUpdate(Long.valueOf(1), Long.valueOf(99)); + assertEquals(new XYDataItem(Long.valueOf(1), Long.valueOf(2)), old); + assertEquals(2, series.getItemCount()); + assertEquals(99L, series.getY(0)); + assertEquals(3L, series.getY(1)); + } + + /** + * Some checks for the addOrUpdate() method for an UNSORTED series. + */ + @Test + public void testAddOrUpdate2() { + XYSeries series = new XYSeries("Series", false, false); + series.add(5.0, 5.5); + series.add(6.0, 6.6); + series.add(3.0, 3.3); + series.add(4.0, 4.4); + series.add(2.0, 2.2); + series.add(1.0, 1.1); + series.addOrUpdate(3.0, 33.3); + series.addOrUpdate(2.0, 22.2); + assertEquals(33.3, series.getY(2).doubleValue(), EPSILON); + assertEquals(22.2, series.getY(4).doubleValue(), EPSILON); + } + + /** + * Another test for the addOrUpdate() method. + */ + @Test + public void testAddOrUpdate3() { + XYSeries series = new XYSeries("Series", false, true); + series.addOrUpdate(1.0, 1.0); + series.addOrUpdate(1.0, 2.0); + series.addOrUpdate(1.0, 3.0); + assertEquals(1.0, series.getY(0)); + assertEquals(2.0, series.getY(1)); + assertEquals(3.0, series.getY(2)); + assertEquals(3, series.getItemCount()); + } + + /** + * Some checks for the add() method for an UNSORTED series. + */ + @Test + public void testAdd() { + XYSeries series = new XYSeries("Series", false, true); + series.add(5.0, 5.50); + series.add(5.1, 5.51); + series.add(6.0, 6.6); + series.add(3.0, 3.3); + series.add(4.0, 4.4); + series.add(2.0, 2.2); + series.add(1.0, 1.1); + assertEquals(5.5, series.getY(0).doubleValue(), EPSILON); + assertEquals(5.51, series.getY(1).doubleValue(), EPSILON); + assertEquals(6.6, series.getY(2).doubleValue(), EPSILON); + assertEquals(3.3, series.getY(3).doubleValue(), EPSILON); + assertEquals(4.4, series.getY(4).doubleValue(), EPSILON); + assertEquals(2.2, series.getY(5).doubleValue(), EPSILON); + assertEquals(1.1, series.getY(6).doubleValue(), EPSILON); + } + + /** + * A simple check that the maximumItemCount attribute is working. + */ + @Test + public void testSetMaximumItemCount() { + XYSeries s1 = new XYSeries("S1"); + assertEquals(Integer.MAX_VALUE, s1.getMaximumItemCount()); + s1.setMaximumItemCount(2); + assertEquals(2, s1.getMaximumItemCount()); + s1.add(1.0, 1.1); + s1.add(2.0, 2.2); + s1.add(3.0, 3.3); + assertEquals(2.0, s1.getX(0).doubleValue(), EPSILON); + assertEquals(3.0, s1.getX(1).doubleValue(), EPSILON); + } + + /** + * Check that the maximum item count can be applied retrospectively. + */ + @Test + public void testSetMaximumItemCount2() { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, 1.1); + s1.add(2.0, 2.2); + s1.add(3.0, 3.3); + s1.setMaximumItemCount(2); + assertEquals(2.0, s1.getX(0).doubleValue(), EPSILON); + assertEquals(3.0, s1.getX(1).doubleValue(), EPSILON); + } + + /** + * Check that the item bounds are determined correctly when there is a + * maximum item count. + */ + @Test + public void testSetMaximumItemCount3() { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, 1.1); + s1.add(2.0, 2.2); + s1.add(3.0, 3.3); + s1.add(4.0, 4.4); + s1.add(5.0, 5.5); + s1.add(6.0, 6.6); + s1.setMaximumItemCount(2); + assertEquals(5.0, s1.getX(0).doubleValue(), EPSILON); + assertEquals(6.0, s1.getX(1).doubleValue(), EPSILON); + assertEquals(5.0, s1.getMinX(), EPSILON); + assertEquals(6.0, s1.getMaxX(), EPSILON); + assertEquals(5.5, s1.getMinY(), EPSILON); + assertEquals(6.6, s1.getMaxY(), EPSILON); + } + + /** + * Check that the item bounds are determined correctly when there is a + * maximum item count. + */ + @Test + public void testSetMaximumItemCount4() { + XYSeries s1 = new XYSeries("S1"); + s1.setMaximumItemCount(2); + s1.add(1.0, 1.1); + s1.add(2.0, 2.2); + s1.add(3.0, 3.3); + assertEquals(2.0, s1.getX(0).doubleValue(), EPSILON); + assertEquals(3.0, s1.getX(1).doubleValue(), EPSILON); + assertEquals(2.0, s1.getMinX(), EPSILON); + assertEquals(3.0, s1.getMaxX(), EPSILON); + assertEquals(2.2, s1.getMinY(), EPSILON); + assertEquals(3.3, s1.getMaxY(), EPSILON); + } + + /** + * Some checks for the toArray() method. + */ + @Test + public void testToArray() { + XYSeries s = new XYSeries("S1"); + double[][] array = s.toArray(); + assertEquals(2, array.length); + assertEquals(0, array[0].length); + assertEquals(0, array[1].length); + + s.add(1.0, 2.0); + array = s.toArray(); + assertEquals(1, array[0].length); + assertEquals(1, array[1].length); + assertEquals(2, array.length); + assertEquals(1.0, array[0][0], EPSILON); + assertEquals(2.0, array[1][0], EPSILON); + + s.add(2.0, null); + array = s.toArray(); + assertEquals(2, array.length); + assertEquals(2, array[0].length); + assertEquals(2, array[1].length); + assertEquals(2.0, array[0][1], EPSILON); + assertTrue(Double.isNaN(array[1][1])); + } + + /** + * Some checks for an example using the toArray() method. + */ + @Test + public void testToArrayExample() { + XYSeries s = new XYSeries("S"); + s.add(1.0, 11.0); + s.add(2.0, 22.0); + s.add(3.5, 35.0); + s.add(5.0, null); + DefaultXYDataset dataset = new DefaultXYDataset(); + dataset.addSeries("S", s.toArray()); + assertEquals(1, dataset.getSeriesCount()); + assertEquals(4, dataset.getItemCount(0)); + assertEquals("S", dataset.getSeriesKey(0)); + assertEquals(1.0, dataset.getXValue(0, 0), EPSILON); + assertEquals(2.0, dataset.getXValue(0, 1), EPSILON); + assertEquals(3.5, dataset.getXValue(0, 2), EPSILON); + assertEquals(5.0, dataset.getXValue(0, 3), EPSILON); + assertEquals(11.0, dataset.getYValue(0, 0), EPSILON); + assertEquals(22.0, dataset.getYValue(0, 1), EPSILON); + assertEquals(35.0, dataset.getYValue(0, 2), EPSILON); + assertTrue(Double.isNaN(dataset.getYValue(0, 3))); + } + + /** + * Another test for the addOrUpdate() method. + */ + @Test + public void testBug1955483() { + XYSeries series = new XYSeries("Series", true, true); + series.addOrUpdate(1.0, 1.0); + series.addOrUpdate(1.0, 2.0); + assertEquals(1.0, series.getY(0)); + assertEquals(2.0, series.getY(1)); + assertEquals(2, series.getItemCount()); + } + + /** + * Some checks for the delete(int, int) method. + */ + @Test + public void testDelete() { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, 1.1); + s1.add(2.0, 2.2); + s1.add(3.0, 3.3); + s1.add(4.0, 4.4); + s1.add(5.0, 5.5); + s1.add(6.0, 6.6); + s1.delete(2, 5); + assertEquals(2, s1.getItemCount()); + assertEquals(1.0, s1.getX(0).doubleValue(), EPSILON); + assertEquals(2.0, s1.getX(1).doubleValue(), EPSILON); + assertEquals(1.0, s1.getMinX(), EPSILON); + assertEquals(2.0, s1.getMaxX(), EPSILON); + assertEquals(1.1, s1.getMinY(), EPSILON); + assertEquals(2.2, s1.getMaxY(), EPSILON); + } + + /** + * Some checks for the getMinX() method. + */ + @Test + public void testGetMinX() { + XYSeries s1 = new XYSeries("S1"); + assertTrue(Double.isNaN(s1.getMinX())); + + s1.add(1.0, 1.1); + assertEquals(1.0, s1.getMinX(), EPSILON); + + s1.add(2.0, 2.2); + assertEquals(1.0, s1.getMinX(), EPSILON); + + s1.add(Double.NaN, 99.9); + assertEquals(1.0, s1.getMinX(), EPSILON); + + s1.add(-1.0, -1.1); + assertEquals(-1.0, s1.getMinX(), EPSILON); + + s1.add(0.0, null); + assertEquals(-1.0, s1.getMinX(), EPSILON); + } + + /** + * Some checks for the getMaxX() method. + */ + @Test + public void testGetMaxX() { + XYSeries s1 = new XYSeries("S1"); + assertTrue(Double.isNaN(s1.getMaxX())); + + s1.add(1.0, 1.1); + assertEquals(1.0, s1.getMaxX(), EPSILON); + + s1.add(2.0, 2.2); + assertEquals(2.0, s1.getMaxX(), EPSILON); + + s1.add(Double.NaN, 99.9); + assertEquals(2.0, s1.getMaxX(), EPSILON); + + s1.add(-1.0, -1.1); + assertEquals(2.0, s1.getMaxX(), EPSILON); + + s1.add(0.0, null); + assertEquals(2.0, s1.getMaxX(), EPSILON); + } + + /** + * Some checks for the getMinY() method. + */ + @Test + public void testGetMinY() { + XYSeries s1 = new XYSeries("S1"); + assertTrue(Double.isNaN(s1.getMinY())); + + s1.add(1.0, 1.1); + assertEquals(1.1, s1.getMinY(), EPSILON); + + s1.add(2.0, 2.2); + assertEquals(1.1, s1.getMinY(), EPSILON); + + s1.add(Double.NaN, 99.9); + assertEquals(1.1, s1.getMinY(), EPSILON); + + s1.add(-1.0, -1.1); + assertEquals(-1.1, s1.getMinY(), EPSILON); + + s1.add(0.0, null); + assertEquals(-1.1, s1.getMinY(), EPSILON); + } + + /** + * Some checks for the getMaxY() method. + */ + @Test + public void testGetMaxY() { + XYSeries s1 = new XYSeries("S1"); + assertTrue(Double.isNaN(s1.getMaxY())); + + s1.add(1.0, 1.1); + assertEquals(1.1, s1.getMaxY(), EPSILON); + + s1.add(2.0, 2.2); + assertEquals(2.2, s1.getMaxY(), EPSILON); + + s1.add(Double.NaN, 99.9); + assertEquals(99.9, s1.getMaxY(), EPSILON); + + s1.add(-1.0, -1.1); + assertEquals(99.9, s1.getMaxY(), EPSILON); + + s1.add(0.0, null); + assertEquals(99.9, s1.getMaxY(), EPSILON); + } + + /** + * A test for a bug reported in the forum: + * + * @link{lhttp://www.jfree.org/forum/viewtopic.php?f=3&t=116601} + */ + @Test + public void testGetMaxY2() { + XYSeries series = new XYSeries(1, true, false); + series.addOrUpdate(1, 20); + series.addOrUpdate(2, 30); + series.addOrUpdate(3, 40); + assertEquals(40.0, series.getMaxY(), EPSILON); + series.addOrUpdate(2, 22); + assertEquals(40.0, series.getMaxY(), EPSILON); + } + + /** + * A test for the clear method. + */ + @Test + public void testClear() { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, 1.1); + s1.add(2.0, 2.2); + s1.add(3.0, 3.3); + + assertEquals(3, s1.getItemCount()); + + s1.clear(); + assertEquals(0, s1.getItemCount()); + assertTrue(Double.isNaN(s1.getMinX())); + assertTrue(Double.isNaN(s1.getMaxX())); + assertTrue(Double.isNaN(s1.getMinY())); + assertTrue(Double.isNaN(s1.getMaxY())); + } + + /** + * Some checks for the updateByIndex() method. + */ + @Test + public void testUpdateByIndex() { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, 1.1); + s1.add(2.0, 2.2); + s1.add(3.0, 3.3); + + assertEquals(1.1, s1.getMinY(), EPSILON); + assertEquals(3.3, s1.getMaxY(), EPSILON); + + s1.updateByIndex(0, -5.0); + assertEquals(-5.0, s1.getMinY(), EPSILON); + assertEquals(3.3, s1.getMaxY(), EPSILON); + + s1.updateByIndex(0, null); + assertEquals(2.2, s1.getMinY(), EPSILON); + assertEquals(3.3, s1.getMaxY(), EPSILON); + + s1.updateByIndex(2, null); + assertEquals(2.2, s1.getMinY(), EPSILON); + assertEquals(2.2, s1.getMaxY(), EPSILON); + + s1.updateByIndex(1, null); + assertTrue(Double.isNaN(s1.getMinY())); + assertTrue(Double.isNaN(s1.getMaxY())); + } + + /** + * Some checks for the updateByIndex() method. + */ + @Test + public void testUpdateByIndex2() { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, Double.NaN); + + assertTrue(Double.isNaN(s1.getMinY())); + assertTrue(Double.isNaN(s1.getMaxY())); + + s1.updateByIndex(0, 1.0); + assertEquals(1.0, s1.getMinY(), EPSILON); + assertEquals(1.0, s1.getMaxY(), EPSILON); + + s1.updateByIndex(0, 2.0); + assertEquals(2.0, s1.getMinY(), EPSILON); + assertEquals(2.0, s1.getMaxY(), EPSILON); + + s1.add(-1.0, -1.0); + s1.updateByIndex(0, 0.0); + assertEquals(0.0, s1.getMinY(), EPSILON); + assertEquals(2.0, s1.getMaxY(), EPSILON); + } + + /** + * Some checks for the updateByIndex() method. + */ + @Test + public void testUpdateByIndex3() { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, 1.1); + s1.add(2.0, 2.2); + s1.add(3.0, 3.3); + + s1.updateByIndex(1, 2.05); + assertEquals(1.1, s1.getMinY(), EPSILON); + assertEquals(3.3, s1.getMaxY(), EPSILON); + } + + /** + * Some checks for the update(Number, Number) method. + */ + @Test + public void testUpdateXY() { + XYSeries s1 = new XYSeries("S1"); + s1.add(1.0, Double.NaN); + + assertTrue(Double.isNaN(s1.getMinY())); + assertTrue(Double.isNaN(s1.getMaxY())); + + s1.update(1.0, 1.0); + assertEquals(1.0, s1.getMinY(), EPSILON); + assertEquals(1.0, s1.getMaxY(), EPSILON); + + s1.update(1.0, 2.0); + assertEquals(2.0, s1.getMinY(), EPSILON); + assertEquals(2.0, s1.getMaxY(), EPSILON); + } + + @Test + public void testSetKey() { + XYSeries s1 = new XYSeries("S"); + s1.setKey("S1"); + assertEquals("S1", s1.getKey()); + + XYSeriesCollection c = new XYSeriesCollection(); + c.addSeries(s1); + XYSeries s2 = new XYSeries("S2"); + c.addSeries(s2); + + // now we should be allowed to change s1's key to anything but "S2" + s1.setKey("OK"); + assertEquals("OK", s1.getKey()); + + try { + s1.setKey("S2"); + fail("Expect an exception here."); + } catch (IllegalArgumentException e) { + // OK + } + + // after s1 is removed from the collection, we should be able to set + // the key to anything we want... + c.removeSeries(s1); + s1.setKey("S2"); + + // check that removing by index also works + s1.setKey("S1"); + c.addSeries(s1); + c.removeSeries(1); + s1.setKey("S2"); + } +} From 715ab185f7646e8593b592129d39994902d7ee6e Mon Sep 17 00:00:00 2001 From: tracylynne99 Date: Mon, 20 Mar 2023 13:23:33 -0500 Subject: [PATCH 4/4] Fixed error when generating javadocs - use HTML link to point to forum instead of link tag. --- src/test/java/org/jfree/data/xy/XYSeriesTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jfree/data/xy/XYSeriesTest.java b/src/test/java/org/jfree/data/xy/XYSeriesTest.java index 53a237def..61ab2aae1 100644 --- a/src/test/java/org/jfree/data/xy/XYSeriesTest.java +++ b/src/test/java/org/jfree/data/xy/XYSeriesTest.java @@ -649,7 +649,9 @@ public void testGetMaxY() { /** * A test for a bug reported in the forum: * - * @link{lhttp://www.jfree.org/forum/viewtopic.php?f=3&t=116601} + * + * XYSeries.getMaxY Bug in version 1.0.15 + * */ @Test public void testGetMaxY2() { @@ -772,6 +774,9 @@ public void testUpdateXY() { assertEquals(2.0, s1.getMaxY(), EPSILON); } + /** + * Test for deprecated method + */ @Test public void testSetKey() { XYSeries s1 = new XYSeries("S");