Package com.positive.charts.data.xy

Source Code of com.positive.charts.data.xy.IntervalXYDelegate

package com.positive.charts.data.xy;

import java.io.Serializable;

import com.positive.charts.data.DomainInfo;
import com.positive.charts.data.Range;
import com.positive.charts.data.RangeInfo;
import com.positive.charts.data.util.DatasetUtilities;
import com.positive.charts.event.DatasetChangeEvent;
import com.positive.charts.event.DatasetChangeListener;

/**
* A delegate that handles the specification or automatic calculation of the
* interval surrounding the x-values in a dataset. This is used to extend a
* regular {@link XYDataset} to support the {@link IntervalXYDataset} interface.
* <p>
* 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.).
* <p>
* The width can be set manually or calculated automatically. The switch
* autoWidth allows to determine which behavior is used. The auto width
* calculation tries to find the smallest gap between two x-values in the
* dataset. If there is only one item in the series, the auto width calculation
* fails and falls back on the manually set interval width (which is itself
* defaulted to 1.0).
*
* @author andreas.schroeder
*/
public class IntervalXYDelegate implements DatasetChangeListener, DomainInfo,
    Serializable, Cloneable {

  /** For serialization. */
  private static final long serialVersionUID = -685166711639592857L;

  /**
   * The dataset to enhance.
   */
  private final XYDataset dataset;

  /**
   * A flag to indicate whether the width should be calculated automatically.
   */
  private boolean autoWidth;

  /**
   * A value between 0.0 and 1.0 that indicates the position of the x-value
   * within the interval.
   */
  private double intervalPositionFactor;

  /**
   * The fixed interval width (defaults to 1.0).
   */
  private double fixedIntervalWidth;

  /**
   * The automatically calculated interval width.
   */
  private double autoIntervalWidth;

  /**
   * Creates a new delegate that.
   *
   * @param dataset
   *            the underlying dataset (<code>null</code> not permitted).
   */
  public IntervalXYDelegate(final XYDataset dataset) {
    this(dataset, true);
  }

  /**
   * Creates a new delegate for the specified dataset.
   *
   * @param dataset
   *            the underlying dataset (<code>null</code> not permitted).
   * @param autoWidth
   *            a flag that controls whether the interval width is calculated
   *            automatically.
   */
  public IntervalXYDelegate(final XYDataset dataset, final boolean autoWidth) {
    if (dataset == null) {
      throw new IllegalArgumentException("Null 'dataset' argument.");
    }
    this.dataset = dataset;
    this.autoWidth = autoWidth;
    this.intervalPositionFactor = 0.5;
    this.autoIntervalWidth = Double.POSITIVE_INFINITY;
    this.fixedIntervalWidth = 1.0;
  }

  /**
   * Calculates the interval width for a given series.
   *
   * @param series
   *            the series index.
   */
  private double calculateIntervalForSeries(final int series) {
    double result = Double.POSITIVE_INFINITY;
    final int itemCount = this.dataset.getItemCount(series);
    if (itemCount > 1) {
      double prev = this.dataset.getXValue(series, 0);
      for (int item = 1; item < itemCount; item++) {
        final double x = this.dataset.getXValue(series, item);
        result = Math.min(result, x - prev);
        prev = x;
      }
    }
    return result;
  }

  /**
   * @return A clone of this delegate.
   *
   * @throws CloneNotSupportedException
   *             if the object cannot be cloned.
   */
  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }

  /**
   * Handles events from the dataset by recalculating the interval if
   * necessary.
   *
   * @param e
   *            the event.
   */
  public void datasetChanged(final DatasetChangeEvent e) {
    // TODO: by coding the event with some information about what changed
    // in the dataset, we could make the recalculation of the interval
    // more efficient in some cases...
    if (this.autoWidth) {
      this.autoIntervalWidth = this.recalculateInterval();
    }
  }

  /**
   * Tests the delegate for equality with an arbitrary object.
   *
   * @param obj
   *            the object (<code>null</code> permitted).
   *
   * @return A boolean.
   */
  public boolean equals(final Object obj) {
    if (obj == this) {
      return true;
    }
    if (!(obj instanceof IntervalXYDelegate)) {
      return false;
    }
    final IntervalXYDelegate that = (IntervalXYDelegate) obj;
    if (this.autoWidth != that.autoWidth) {
      return false;
    }
    if (this.intervalPositionFactor != that.intervalPositionFactor) {
      return false;
    }
    if (this.fixedIntervalWidth != that.fixedIntervalWidth) {
      return false;
    }
    return true;
  }

  /**
   * Returns the range of the values in the dataset's domain, including or
   * excluding the interval around each x-value as specified.
   *
   * @param includeInterval
   *            a flag that determines whether or not the x-interval should be
   *            taken into account.
   *
   * @return The range.
   */
  public Range getDomainBounds(final boolean includeInterval) {
    // first get the range without the interval, then expand it for the
    // interval width
    Range range = DatasetUtilities.findDomainBounds(this.dataset, false);
    if (includeInterval && (range != null)) {
      final double lowerAdj = this.getIntervalWidth()
          * this.getIntervalPositionFactor();
      final double upperAdj = this.getIntervalWidth() - lowerAdj;
      range = new Range(range.getLowerBound() - lowerAdj, range
          .getUpperBound()
          + upperAdj);
    }
    return range;
  }

  /**
   * 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.
   */
  public double getDomainLowerBound(final boolean includeInterval) {
    double result = Double.NaN;
    final Range r = this.getDomainBounds(includeInterval);
    if (r != null) {
      result = r.getLowerBound();
    }
    return result;
  }

  /**
   * 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.
   */
  public double getDomainUpperBound(final boolean includeInterval) {
    double result = Double.NaN;
    final Range r = this.getDomainBounds(includeInterval);
    if (r != null) {
      result = r.getUpperBound();
    }
    return result;
  }

  /**
   * Returns the end value of the x-interval for an item within a series.
   *
   * @param series
   *            the series index.
   * @param item
   *            the item index.
   *
   * @return The end value of the x-interval (possibly <code>null</code>).
   *
   * @see #getEndXValue(int, int)
   */
  public Number getEndX(final int series, final int item) {
    Number endX = null;
    final Number x = this.dataset.getX(series, item);
    if (x != null) {
      endX = new Double(x.doubleValue()
          + ((1.0 - this.getIntervalPositionFactor()) * this
              .getIntervalWidth()));
    }
    return endX;
  }

  /**
   * Returns the end value of the x-interval for an item within a series.
   *
   * @param series
   *            the series index.
   * @param item
   *            the item index.
   *
   * @return The end value of the x-interval.
   *
   * @see #getEndX(int, int)
   */
  public double getEndXValue(final int series, final int item) {
    return this.dataset.getXValue(series, item)
        + (1.0 - this.getIntervalPositionFactor())
        * this.getIntervalWidth();
  }

  /**
   * Returns the fixed interval width.
   *
   * @return The fixed interval width.
   */
  public double getFixedIntervalWidth() {
    return this.fixedIntervalWidth;
  }

  /**
   * Returns the interval position factor.
   *
   * @return The interval position factor.
   */
  public double getIntervalPositionFactor() {
    return this.intervalPositionFactor;
  }

  /**
   * Returns the interval width. This method will return either the auto
   * calculated interval width or the manually specified interval width,
   * depending on the {@link #isAutoWidth()} result.
   *
   * @return The interval width to use.
   */
  public double getIntervalWidth() {
    if (this.isAutoWidth() && !Double.isInfinite(this.autoIntervalWidth)) {
      // everything is fine: autoWidth is on, and an autoIntervalWidth
      // was set.
      return this.autoIntervalWidth;
    } else {
      // either autoWidth is off or autoIntervalWidth was not set.
      return this.fixedIntervalWidth;
    }
  }

  /**
   * Returns the start value of the x-interval for an item within a series.
   *
   * @param series
   *            the series index.
   * @param item
   *            the item index.
   *
   * @return The start value of the x-interval (possibly <code>null</code>).
   *
   * @see #getStartXValue(int, int)
   */
  public Number getStartX(final int series, final int item) {
    Number startX = null;
    final Number x = this.dataset.getX(series, item);
    if (x != null) {
      startX = new Double(x.doubleValue()
          - (this.getIntervalPositionFactor() * this
              .getIntervalWidth()));
    }
    return startX;
  }

  /**
   * Returns the start value of the x-interval for an item within a series.
   *
   * @param series
   *            the series index.
   * @param item
   *            the item index.
   *
   * @return The start value of the x-interval.
   *
   * @see #getStartX(int, int)
   */
  public double getStartXValue(final int series, final int item) {
    return this.dataset.getXValue(series, item)
        - this.getIntervalPositionFactor() * this.getIntervalWidth();
  }

  /**
   * Returns <code>true</code> if the interval width is automatically
   * calculated, and <code>false</code> otherwise.
   *
   * @return A boolean.
   */
  public boolean isAutoWidth() {
    return this.autoWidth;
  }

  /**
   * Recalculate the minimum width "from scratch".
   */
  private double recalculateInterval() {
    double result = Double.POSITIVE_INFINITY;
    final int seriesCount = this.dataset.getSeriesCount();
    for (int series = 0; series < seriesCount; series++) {
      result = Math.min(result, this.calculateIntervalForSeries(series));
    }
    return result;
  }

  /**
   * Sets the flag that indicates whether the interval width is automatically
   * calculated. If the flag is set to <code>true</code>, the interval is
   * recalculated.
   * <p>
   * Note: recalculating the interval amounts to changing the data values
   * represented by the dataset. The calling dataset must fire an appropriate
   * {@link DatasetChangeEvent}.
   *
   * @param b
   *            a boolean.
   */
  public void setAutoWidth(final boolean b) {
    this.autoWidth = b;
    if (b) {
      this.autoIntervalWidth = this.recalculateInterval();
    }
  }

  /**
   * Sets the fixed interval width and, as a side effect, sets the
   * <code>autoWidth</code> flag to <code>false</code>.
   *
   * Note that changing the interval width amounts to changing the data values
   * represented by the dataset. Therefore, the dataset that is using this
   * delegate is responsible for generating the appropriate
   * {@link DatasetChangeEvent}.
   *
   * @param w
   *            the width (negative values not permitted).
   */
  public void setFixedIntervalWidth(final double w) {
    if (w < 0.0) {
      throw new IllegalArgumentException("Negative 'w' argument.");
    }
    this.fixedIntervalWidth = w;
    this.autoWidth = false;
  }

  /**
   * Sets the interval position factor. This controls how the interval is
   * aligned to the x-value. For a value of 0.5, the interval is aligned with
   * the x-value in the center. For a value of 0.0, the interval is aligned
   * with the x-value at the lower end of the interval, and for a value of
   * 1.0, the interval is aligned with the x-value at the upper end of the
   * interval.
   *
   * Note that changing the interval position factor amounts to changing the
   * data values represented by the dataset. Therefore, the dataset that is
   * using this delegate is responsible for generating the appropriate
   * {@link DatasetChangeEvent}.
   *
   * @param d
   *            the new interval position factor (in the range
   *            <code>0.0</code> to <code>1.0</code> inclusive).
   */
  public void setIntervalPositionFactor(final double d) {
    if ((d < 0.0) || (1.0 < d)) {
      throw new IllegalArgumentException(
          "Argument 'd' outside valid range.");
    }
    this.intervalPositionFactor = d;
  }

}
TOP

Related Classes of com.positive.charts.data.xy.IntervalXYDelegate

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.