content: /* ===========================================================
   * JFreeChart : a free chart library for the Java(tm) platform
   * ===========================================================
   *
   * (C) Copyright 2000-2012, by Object Refinery Limited 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-2012, by Andreas Schroeder and Contributors.
   *
   * Original Author:  Andreas Schroeder;
   * Contributor(s):   David Gilbert (for Object Refinery Limited);
   *
   * Changes
   * -------
   * 01-Apr-2004 : Version 1 (AS);
   * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
   *               getYValue() (DG);
   * 15-Aug-2004 : Added drawBarOutline to control draw/don't-draw bar
   *               outlines (BN);
   * 10-Sep-2004 : drawBarOutline attribute is now inherited from XYBarRenderer
   *               and double primitives are retrieved from the dataset rather
   *               than Number objects (DG);
   * 07-Jan-2005 : Updated for method name change in DatasetUtilities (DG);
   * 25-Jan-2005 : Modified to handle negative values correctly (DG);
   * ------------- JFREECHART 1.0.x ---------------------------------------------
   * 06-Dec-2006 : Added support for GradientPaint (DG);
   * 15-Mar-2007 : Added renderAsPercentages option (DG);
   * 24-Jun-2008 : Added new barPainter mechanism (DG);
   * 23-Sep-2008 : Check shadow visibility before drawing shadow (DG);
   * 28-May-2009 : Fixed bar positioning with inverted domain axis (DG);
   * 07-Act-2011 : Fix for Bug #3035289: Patch #3035325 (MH);
   * 17-Jun-2012 : Removed JCommon dependencies (DG);
   *
   */
  
  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.ui.RectangleEdge;
  import org.jfree.chart.ui.TextAnchor;
  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.data.Range;
  import org.jfree.data.general.DatasetUtilities;
  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</code> program included in the
   * JFreeChart demo collection:
   * <br><br>
   * <img src="../../../../../images/StackedXYBarRendererSample.png"
   * alt="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</code> if the renderer displays each item value as
       * a percentage (so that the stacked bars add to 100%), and
       * <code>false</code> otherwise.
       *
       * @return A boolean.
       *
       * @see #setRenderAsPercentages(boolean)
       *
       * @since 1.0.5
       */
      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()
       *
       * @since 1.0.5
       */
      public void setRenderAsPercentages(boolean asPercentages) {
          this.renderAsPercentages = asPercentages;
          fireChangeEvent();
      }
  
      /**
       * Returns <code>3</code> 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</code>.
       */
      @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</code> permitted).
       *
       * @return The range (<code>null</code> if the dataset is <code>null</code>
       *         or empty).
       */
      @Override
      public Range findRangeBounds(XYDataset dataset) {
          if (dataset != null) {
              if (this.renderAsPercentages) {
                  return new Range(0.0, 1.0);
              }
              else {
                  return DatasetUtilities.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</code> 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 = DatasetUtilities.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);
          }
          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</code> 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;
      }
  
  }
  