/**
   * Draws the tick labels for one "band" of time periods.
   *
   * @param band the band index (zero-based).
   * @param g2 the graphics device.
   * @param state the axis state.
   * @param dataArea the data area.
   * @param edge the edge where the axis is located.
   * @return The updated axis state.
   */
  protected AxisState drawTickLabels(
      int band, Graphics2D g2, AxisState state, Rectangle2D dataArea, RectangleEdge edge) {

    // work out the initial gap
    double delta1 = 0.0;
    FontMetrics fm = g2.getFontMetrics(this.labelInfo[band].getLabelFont());
    if (edge == RectangleEdge.BOTTOM) {
      delta1 = this.labelInfo[band].getPadding().calculateTopOutset(fm.getHeight());
    } else if (edge == RectangleEdge.TOP) {
      delta1 = this.labelInfo[band].getPadding().calculateBottomOutset(fm.getHeight());
    }
    state.moveCursor(delta1, edge);
    long axisMin = this.first.getFirstMillisecond();
    long axisMax = this.last.getLastMillisecond();
    g2.setFont(this.labelInfo[band].getLabelFont());
    g2.setPaint(this.labelInfo[band].getLabelPaint());

    // work out the number of periods to skip for labelling
    RegularTimePeriod p1 =
        this.labelInfo[band].createInstance(new Date(axisMin), this.timeZone, this.locale);
    RegularTimePeriod p2 =
        this.labelInfo[band].createInstance(new Date(axisMax), this.timeZone, this.locale);
    String label1 =
        this.labelInfo[band].getDateFormat().format(new Date(p1.getMiddleMillisecond()));
    String label2 =
        this.labelInfo[band].getDateFormat().format(new Date(p2.getMiddleMillisecond()));
    Rectangle2D b1 = TextUtilities.getTextBounds(label1, g2, g2.getFontMetrics());
    Rectangle2D b2 = TextUtilities.getTextBounds(label2, g2, g2.getFontMetrics());
    double w = Math.max(b1.getWidth(), b2.getWidth());
    long ww = Math.round(java2DToValue(dataArea.getX() + w + 5.0, dataArea, edge));
    if (isInverted()) {
      ww = axisMax - ww;
    } else {
      ww = ww - axisMin;
    }
    long length = p1.getLastMillisecond() - p1.getFirstMillisecond();
    int periods = (int) (ww / length) + 1;

    RegularTimePeriod p =
        this.labelInfo[band].createInstance(new Date(axisMin), this.timeZone, this.locale);
    Rectangle2D b = null;
    long lastXX = 0L;
    float y = (float) (state.getCursor());
    TextAnchor anchor = TextAnchor.TOP_CENTER;
    float yDelta = (float) b1.getHeight();
    if (edge == RectangleEdge.TOP) {
      anchor = TextAnchor.BOTTOM_CENTER;
      yDelta = -yDelta;
    }
    while (p.getFirstMillisecond() <= axisMax) {
      float x = (float) valueToJava2D(p.getMiddleMillisecond(), dataArea, edge);
      DateFormat df = this.labelInfo[band].getDateFormat();
      String label = df.format(new Date(p.getMiddleMillisecond()));
      long first = p.getFirstMillisecond();
      long last = p.getLastMillisecond();
      if (last > axisMax) {
        // this is the last period, but it is only partially visible
        // so check that the label will fit before displaying it...
        Rectangle2D bb = TextUtilities.getTextBounds(label, g2, g2.getFontMetrics());
        if ((x + bb.getWidth() / 2) > dataArea.getMaxX()) {
          float xstart = (float) valueToJava2D(Math.max(first, axisMin), dataArea, edge);
          if (bb.getWidth() < (dataArea.getMaxX() - xstart)) {
            x = ((float) dataArea.getMaxX() + xstart) / 2.0f;
          } else {
            label = null;
          }
        }
      }
      if (first < axisMin) {
        // this is the first period, but it is only partially visible
        // so check that the label will fit before displaying it...
        Rectangle2D bb = TextUtilities.getTextBounds(label, g2, g2.getFontMetrics());
        if ((x - bb.getWidth() / 2) < dataArea.getX()) {
          float xlast = (float) valueToJava2D(Math.min(last, axisMax), dataArea, edge);
          if (bb.getWidth() < (xlast - dataArea.getX())) {
            x = (xlast + (float) dataArea.getX()) / 2.0f;
          } else {
            label = null;
          }
        }
      }
      if (label != null) {
        g2.setPaint(this.labelInfo[band].getLabelPaint());
        b = TextUtilities.drawAlignedString(label, g2, x, y, anchor);
      }
      if (lastXX > 0L) {
        if (this.labelInfo[band].getDrawDividers()) {
          long nextXX = p.getFirstMillisecond();
          long mid = (lastXX + nextXX) / 2;
          float mid2d = (float) valueToJava2D(mid, dataArea, edge);
          g2.setStroke(this.labelInfo[band].getDividerStroke());
          g2.setPaint(this.labelInfo[band].getDividerPaint());
          g2.draw(new Line2D.Float(mid2d, y, mid2d, y + yDelta));
        }
      }
      lastXX = last;
      for (int i = 0; i < periods; i++) {
        p = p.next();
      }
      p.peg(this.calendar);
    }
    double used = 0.0;
    if (b != null) {
      used = b.getHeight();
      // work out the trailing gap
      if (edge == RectangleEdge.BOTTOM) {
        used += this.labelInfo[band].getPadding().calculateBottomOutset(fm.getHeight());
      } else if (edge == RectangleEdge.TOP) {
        used += this.labelInfo[band].getPadding().calculateTopOutset(fm.getHeight());
      }
    }
    state.moveCursor(used, edge);
    return state;
  }
  /**
   * Tests the correct output of a DataSet to a TimeSeries by outputting it, then iterating through
   * TimeSeries object checking the correct values were stored/output.
   */
  public void testOutput()
      throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
          IllegalAccessException, InvocationTargetException, InstantiationException {
    // Constants used to determine size of test
    int NUMBER_OF_TIME_PERIODS = 10;
    String TIME_VARIABLE = "t";

    // Set up array for expected results
    double expectedValue[] = new double[NUMBER_OF_TIME_PERIODS];

    // We'll set up periods starting from today
    RegularTimePeriod period = new Day();

    // Create a test DataSet for output
    //  - note that only one independent variable (the time variable)
    //    will be output. This is expected.
    DataSet dataSet = new DataSet();
    dataSet.setTimeVariable(TIME_VARIABLE);
    for (int d = 0; d < NUMBER_OF_TIME_PERIODS; d++) {
      double value = (double) d;
      DataPoint obs = new Observation(value);
      obs.setIndependentValue(TIME_VARIABLE, period.getMiddleMillisecond());
      dataSet.add(obs);

      period = period.next();
      expectedValue[d] = value;
    }

    assertEquals(
        "Checking only one independent variable exists in dataSet",
        1,
        dataSet.getIndependentVariables().length);

    assertEquals(
        "Checking dataSet has correct number of entries", NUMBER_OF_TIME_PERIODS, dataSet.size());

    // Create TimeSeriesOutputter and use it to output dataSet
    TimeSeries timeSeries = new TimeSeries("test");
    TimeSeriesOutputter outputter = new TimeSeriesOutputter(timeSeries, period.getClass());
    outputter.output(dataSet);

    assertEquals(
        "Checking number of items in time series",
        NUMBER_OF_TIME_PERIODS,
        timeSeries.getItemCount());

    // Reset period to start checking from today onwards
    period = new Day();
    for (int d = 0; d < NUMBER_OF_TIME_PERIODS; d++) {
      TimeSeriesDataItem dataItem = timeSeries.getDataItem(d);

      period = dataItem.getPeriod();
      assertNotNull("Checking time period", period);

      long timeValue = period.getMiddleMillisecond();
      assertTrue(
          "Checking time periods match",
          (double) timeValue >= period.getFirstMillisecond()
              && (double) timeValue <= period.getLastMillisecond());

      assertEquals(
          "Checking values for period " + dataItem.getPeriod() + " match",
          expectedValue[d],
          dataItem.getValue().doubleValue(),
          TOLERANCE);

      period = period.next();
    }
  }
 /**
  * Draws the major and minor tick marks for an axis that lies at the top or bottom of the plot.
  *
  * @param g2 the graphics device.
  * @param state the axis state.
  * @param dataArea the data area.
  * @param edge the edge.
  */
 protected void drawTickMarksHorizontal(
     Graphics2D g2, AxisState state, Rectangle2D dataArea, RectangleEdge edge) {
   List<ValueTick> ticks = new ArrayList<ValueTick>();
   double x0;
   double y0 = state.getCursor();
   double insideLength = getTickMarkInsideLength();
   double outsideLength = getTickMarkOutsideLength();
   RegularTimePeriod t =
       createInstance(
           this.majorTickTimePeriodClass, this.first.getStart(), getTimeZone(), this.locale);
   long t0 = t.getFirstMillisecond();
   Line2D inside = null;
   Line2D outside = null;
   long firstOnAxis = getFirst().getFirstMillisecond();
   long lastOnAxis = getLast().getLastMillisecond() + 1;
   while (t0 <= lastOnAxis) {
     ticks.add(new NumberTick((double) t0, "", TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
     x0 = valueToJava2D(t0, dataArea, edge);
     if (edge == RectangleEdge.TOP) {
       inside = new Line2D.Double(x0, y0, x0, y0 + insideLength);
       outside = new Line2D.Double(x0, y0, x0, y0 - outsideLength);
     } else if (edge == RectangleEdge.BOTTOM) {
       inside = new Line2D.Double(x0, y0, x0, y0 - insideLength);
       outside = new Line2D.Double(x0, y0, x0, y0 + outsideLength);
     }
     if (t0 >= firstOnAxis) {
       g2.setPaint(getTickMarkPaint());
       g2.setStroke(getTickMarkStroke());
       g2.draw(inside);
       g2.draw(outside);
     }
     // draw minor tick marks
     if (this.minorTickMarksVisible) {
       RegularTimePeriod tminor =
           createInstance(this.minorTickTimePeriodClass, new Date(t0), getTimeZone(), this.locale);
       long tt0 = tminor.getFirstMillisecond();
       while (tt0 < t.getLastMillisecond() && tt0 < lastOnAxis) {
         double xx0 = valueToJava2D(tt0, dataArea, edge);
         if (edge == RectangleEdge.TOP) {
           inside = new Line2D.Double(xx0, y0, xx0, y0 + this.minorTickMarkInsideLength);
           outside = new Line2D.Double(xx0, y0, xx0, y0 - this.minorTickMarkOutsideLength);
         } else if (edge == RectangleEdge.BOTTOM) {
           inside = new Line2D.Double(xx0, y0, xx0, y0 - this.minorTickMarkInsideLength);
           outside = new Line2D.Double(xx0, y0, xx0, y0 + this.minorTickMarkOutsideLength);
         }
         if (tt0 >= firstOnAxis) {
           g2.setPaint(this.minorTickMarkPaint);
           g2.setStroke(this.minorTickMarkStroke);
           g2.draw(inside);
           g2.draw(outside);
         }
         tminor = tminor.next();
         tminor.peg(this.calendar);
         tt0 = tminor.getFirstMillisecond();
       }
     }
     t = t.next();
     t.peg(this.calendar);
     t0 = t.getFirstMillisecond();
   }
   if (edge == RectangleEdge.TOP) {
     state.cursorUp(Math.max(outsideLength, this.minorTickMarkOutsideLength));
   } else if (edge == RectangleEdge.BOTTOM) {
     state.cursorDown(Math.max(outsideLength, this.minorTickMarkOutsideLength));
   }
   state.setTicks(ticks);
 }