private final List<Point2D> getPointsFrom(
      GraphSettings gInfo, Graph g, Year startYear, int nYears, int bottom) {
    // index into our data values for this particular year
    int idx = startYear.diff(g.graph.getStart().add(g.xoffset));

    // make a list of points
    // this is ok, because we know points are continuous
    List<Point2D> points = new ArrayList<Point2D>(nYears);

    // the year the plot starts
    Year plotStartYear = gInfo.getDrawBounds().getStart();
    // the adjusted range of this graph
    Range graphRange = g.getRange();
    int yearWidth = gInfo.getYearWidth();
    float unitScale = gInfo.getHundredUnitHeight() / 100.0f;

    for (int i = 0; i <= nYears; i++) {
      // skip points that don't exist
      if (!graphRange.contains(startYear.add(i))) continue;

      int value = g.graph.getRingWidthData().get(idx + i).intValue();
      int x = getXValue(g, plotStartYear, yearWidth, idx + i);
      int y = getYValue(g, unitScale, value, bottom);

      points.add(new Point2D.Float(x, y));
    }

    return points;
  }
  private final List<Point2D> getPointsFrom(
      GraphSettings gInfo, Graph g, Year startYear, int nYears, int bottom) {

    // make a list of points
    // this is ok, because we know points are continuous
    List<Point2D> points = new ArrayList<Point2D>(nYears);

    int yearWidth = gInfo.getYearWidth();
    float unitScale = gInfo.getHundredUnitHeight() / 100.0f;

    List<? extends Number> data = g.graph.getRingWidthData();
    double[] dataDbl = new double[data.size()];
    for (int i = 0; i < data.size(); i++) {
      Integer intval = (Integer) data.get(i);
      dataDbl[i] = (double) intval;
    }
    int x = yearWidth * (g.graph.getStart().diff(gInfo.getDrawBounds().getStart()) + g.xoffset);
    // int value1 = yTransform((float) stats.getMean());
    int value1 = yTransform((float) baselineY);

    int meanYVal = bottom - (int) (value1 * unitScale) - (int) (g.yoffset * unitScale);
    try {
      // x-position
      int x1 = x;
      int x2 =
          yearWidth
              * (g.graph.getStart().diff(gInfo.getDrawBounds().getStart())
                  + g.xoffset
                  + g.graph.getRingWidthData().size());

      points.add(new Point2D.Float(x1, meanYVal));
      points.add(new Point2D.Float(x2, meanYVal));

    } catch (ClassCastException cce) {

    }

    return points;
  }
  public boolean contact(GraphSettings gInfo, Graph g, Point p, int bottom) {
    // snap to year
    int yearWidth = gInfo.getYearWidth();
    int firstYearIdx = (p.x / yearWidth) - 1;
    int nYears = 3;

    // Check three years' worth of data: the year prior to and after the year the mouse is inside
    Year startYear = gInfo.getDrawBounds().getStart().add(firstYearIdx);
    List<Point2D> points = this.getPointsFrom(gInfo, g, startYear, nYears, bottom);

    int nLines = points.size() - 1;
    for (int i = 0; i < nLines; i++) {
      Line2D line = new Line2D.Float(points.get(i), points.get(i + 1));

      int distance = Math.round((float) line.ptSegDist(p));
      if (distance <= NEAR) return true;
    }

    return false;
  }
  public void draw(
      GraphSettings gInfo, Graphics2D g2, int bottom, Graph g, int thickness, int xscroll) {
    // cache yearsize, we use this a lot
    int yearWidth = gInfo.getYearWidth(); // the size of a year, in pixels
    float unitScale = gInfo.getHundredUnitHeight() / 100.0f; // the size of 1 "unit" in pixels.

    // set pen
    boolean dotted = (gInfo.isDottedIndexes() && (g.graph instanceof Index));
    g2.setStroke(makeStroke(thickness, dotted));

    // left/right
    int l = g2.getClipBounds().x;
    int r = l + g2.getClipBounds().width;

    // Image img1 =
    // Toolkit.getDefaultToolkit().getImage("/home/pwb48/dev/java5/tellervo-desktop/src/main/resources/Icons/256x256/tellervo-application.png");
    // g2.drawImage(img1, 10, 10, null);

    // baseline
    if (gInfo.isShowBaselines()) {
      int y = bottom - (int) (g.yoffset * unitScale);
      g2.drawLine(xscroll, y, xscroll + 10 * yearWidth, y); // 1 decade wide -- ok?
    }

    // hundred percent line
    if (gInfo.isShowHundredpercentlines()
        && (g.graph instanceof Sample)
        && ((Sample) g.graph).isIndexed()) {
      Color oldcolor = g2.getColor();
      g2.setColor(ColorUtils.blend(oldcolor, gInfo.getBackgroundColor()));

      // x is 0 if we aren't drawing graph names...
      // x is the pixel at the end of the empty range if we are.
      int x = (gInfo.isShowGraphNames()) ? yearWidth * (gInfo.getEmptyBounds().getSpan() - 1) : 0;
      int y =
          bottom - (int) (yTransform(1000 * g.scale) * unitScale) - (int) (g.yoffset * unitScale);
      g2.drawLine((x > xscroll) ? x : xscroll, y, r, y);

      g2.setColor(oldcolor);
    }

    // no data?  stop.
    if (g.graph.getRingWidthData().isEmpty()) return;

    // compare g.getClipBounds() to [x,0]..[x+yearSize*data.size(),bottom]
    tempRect.x =
        yearWidth
            * (g.graph.getStart().diff(gInfo.getDrawBounds().getStart())
                + g.xoffset); // REDUNDANT! see x later
    tempRect.y = 0; // - g.yoffset, IF you're sure there are no negative values (but there are)
    tempRect.width = yearWidth * (g.graph.getRingWidthData().size() - 1);
    tempRect.height = bottom;
    // TODO: compute top/bottom as min/max?
    // REFACTOR: will this be obsolete with the start/end stuff below?
    if (!tempRect.intersects(g2.getClipBounds())) {
      // skip this graph, it's off the screen
      return;
    }

    // compute sapwood
    int sapwoodIndex, sapwoodCount = 0;
    if (g.graph instanceof Sample) {
      Sample sample = (Sample) g.graph;

      if (sample.meta().hasSapwood()) sapwoodCount = sample.meta().getNumberOfSapwoodRings();
    }
    sapwoodIndex = g.graph.getRingWidthData().size() - sapwoodCount + 1;

    // my path
    GeneralPath p = new GeneralPath();

    // x-position
    int x = yearWidth * (g.graph.getStart().diff(gInfo.getDrawBounds().getStart()) + g.xoffset);

    // move to the first point -- THIS IS NOT REALLY A SPECIAL CASE!
    int value;
    try {
      value = ((Number) g.graph.getRingWidthData().get(0)).intValue();
      value = yTransform(value * g.scale);
    } catch (ClassCastException cce) {
      value =
          yTransform(0); // BAD!  instead: (1) just continue now, and (2) NEXT point is a move-to.
    }
    p.moveTo(x, bottom - (int) (value * unitScale) - (int) (g.yoffset * unitScale));

    /*
    -- i really want to start at year max(graph.start, bounds.start)
    -- there are 3 things going on:
    ---- x is the pixel position
    ---- i is the index into data[]
    ---- y is the year
    -- y isn't updated each time through the loop, so that's not too bad
    -- but: starting y is easy to compute; from that, i and x are easy
    */

    // connect the lines through the rest of the graph
    int n =
        g.graph
            .getRingWidthData()
            .size(); // THIS is the third time it's called; why not use it above?
    for (int i = 1; i < n; i++) {
      // new x-position for this point
      x += yearWidth;

      // if we're past the end, draw what we've got, and say goodbye
      // (go +_yearsize so the line going off the screen is visible)
      if (x > r + yearWidth) {
        break;
      }

      // sapwood?  draw what we've got, and start a new (thicker) path
      // but only do it if sapwoodThicker() is enabled!
      if (gInfo.isThickerSapwood() && i == sapwoodIndex) {
        g2.draw(p);
        g2.setStroke(makeStroke(2 * thickness, false));
        p = new GeneralPath();
        p.moveTo(
            yearWidth
                * (i - 1 + g.graph.getStart().diff(gInfo.getDrawBounds().getStart()) + g.xoffset),
            bottom - (int) (value * unitScale) - (int) (g.yoffset * unitScale));
      }

      // y-position for this point
      try {
        value = yTransform(((Number) g.graph.getRingWidthData().get(i)).intValue() * g.scale);
      } catch (ClassCastException cce) {
        value = yTransform(0); // e.g., if it's being edited, it's still a string
        // BAD!  instead: (1) draw what i've got so far, and (2) NEXT point is a move-to.
        // -- try to parse String as an integer?
      }

      // Try and paint remark icons
      try {
        List<TridasRemark> remarks = g.graph.getTridasValues().get(i).getRemarks();
        int xcoord = (yearWidth * (i + g.graph.getStart().diff(gInfo.getDrawBounds().getStart())));
        int ycoord = (bottom - (int) (value * unitScale) - (int) (g.yoffset * unitScale));

        Graph.drawRemarkIcons(g2, gInfo, remarks, g.graph.getTridasValues().get(i), xcoord, ycoord);

      } catch (Exception e) {
        log.error("Exception drawing icons to graph: " + e.getClass());
      }

      int y = bottom - (int) (value * unitScale) - (int) (g.yoffset * unitScale);

      // if we're not where this sample starts, don't bother drawing yet
      if (x < l - yearWidth) {
        p.moveTo(x, y);
        continue;
      }

      // if MR, draw a vertical line -- use Sample.MR, for now
      if (g.graph instanceof Sample && !validValue(value)) g2.drawLine(x, y - 20, x, y + 20);

      // draw a line to this point
      p.lineTo(x, y);
    }

    // draw it!
    g2.draw(p);
  }
  public void draw(
      GraphSettings gInfo, Graphics2D g2, int bottom, Graph g, int thickness, int xscroll) {
    // cache yearsize, we use this a lot
    int yearWidth = gInfo.getYearWidth(); // the size of a year, in pixels
    float unitScale = gInfo.getHundredUnitHeight() / 100.0f; // the size of 1 "unit" in pixels.

    // set pen
    boolean dotted = (gInfo.isDottedIndexes() && (g.graph instanceof Index));
    g2.setStroke(makeStroke(thickness, dotted));

    // If it's a chronology, point bones down
    /*if(g.graph instanceof Sample)
    {
    	if (((Sample) g.graph).getSeries() instanceof TridasDerivedSeries)
    	{
    		this.areBonesBelowLine = true;
    	}
    }*/

    // left/right
    int l = g2.getClipBounds().x;
    int r = l + g2.getClipBounds().width;

    // no data?  stop.
    if (g.graph.getRingWidthData().isEmpty()) return;

    // compare g.getClipBounds() to [x,0]..[x+yearSize*data.size(),bottom]
    tempRect.x =
        yearWidth
            * (g.graph.getStart().diff(gInfo.getDrawBounds().getStart())
                + g.xoffset); // REDUNDANT! see x later
    tempRect.y = 0; // - g.yoffset, IF you're sure there are no negative values (but there are)
    tempRect.width = yearWidth * (g.graph.getRingWidthData().size() - 1);
    tempRect.height = bottom;
    // TODO: compute top/bottom as min/max?
    // REFACTOR: will this be obsolete with the start/end stuff below?
    if (!tempRect.intersects(g2.getClipBounds())) {
      // skip this graph, it's off the screen
      return;
    }

    // Draw standard line
    int x = yearWidth * (g.graph.getStart().diff(gInfo.getDrawBounds().getStart()) + g.xoffset);
    // int value1 = yTransform((float) getMeanValue(g));
    int value1 = yTransform((float) baselineY);
    int baseYVal = bottom - (int) (value1 * unitScale) - (int) (g.yoffset * unitScale);

    try {
      // x-position
      int x1 = x;
      int x2 =
          yearWidth
              * (g.graph.getStart().diff(gInfo.getDrawBounds().getStart())
                  + g.xoffset
                  + g.graph.getRingWidthData().size());

      // if we're past the end, draw only as far as we need
      if (x2 > r + yearWidth) {
        x2 = r + yearWidth;
      }

      // log.debug("Drawing mean line: "+x1+", "+meanYVal+", "+x2+", "+meanYVal);
      g2.drawLine(x1, baseYVal, x2, baseYVal);

      // Calcs for start/end triangles
      int YLineHeight = 50;
      int YTriangleHeight = 25;
      int XTriangleWidth = 15;
      if (areBonesBelowLine) {
        YLineHeight = -50;
        YTriangleHeight = -25;
      }

      // Draw start triangle
      g2.drawLine(x1, baseYVal, x1, baseYVal - YTriangleHeight);
      g2.drawLine(x1, baseYVal, x1 - XTriangleWidth, baseYVal - YTriangleHeight);
      g2.drawLine(x1 - XTriangleWidth, baseYVal - YTriangleHeight, x1, baseYVal - YTriangleHeight);
      g2.drawLine(x1, baseYVal, x1, baseYVal - YLineHeight);

      // Draw end triangle
      g2.drawLine(x2, baseYVal, x2, baseYVal - YTriangleHeight);
      g2.drawLine(x2, baseYVal, x2 + XTriangleWidth, baseYVal - YTriangleHeight);
      g2.drawLine(x2 + XTriangleWidth, baseYVal - YTriangleHeight, x2, baseYVal - YTriangleHeight);
      g2.drawLine(x2, baseYVal, x2, baseYVal - YLineHeight);

    } catch (ClassCastException cce) {

    }

    int value;
    try {
      value = ((Number) g.graph.getRingWidthData().get(0)).intValue();
      value = yTransform(value * g.scale);
    } catch (ClassCastException cce) {
      value = yTransform(0);
    }

    int n = g.graph.getRingWidthData().size();

    for (int i = 1; i < n; i++) {
      // new x-position for this point
      x += yearWidth;

      // if we're past the end, draw what we've got, and say goodbye
      // (go +_yearsize so the line going off the screen is visible)
      if (x > r + yearWidth) {
        break;
      }

      // Extract the window of interest
      int ringsEitherSideOfFocus =
          (App.prefs.getIntPref(PrefKey.STATS_SKELETON_PLOT_WINDOW_SIZE, 7) - 1) / 2;

      // Convert to ArrayList first as its easier to handle
      ArrayList<Double> ringWidths = new ArrayList<Double>();
      for (int z = 0; z < n; z++) {
        ringWidths.add((double) g.graph.getRingWidthData().get(z).intValue());
      }

      int firstind = i - 1 - ringsEitherSideOfFocus;
      int lastind = i + ringsEitherSideOfFocus;
      if (firstind < 0) firstind = 0;
      if (lastind > n) lastind = n;
      int size = lastind - firstind;

      double[] window = new double[size];

      int t = 0;
      for (int w = firstind; w < lastind; w++) {
        window[t] = ringWidths.get(w);
        t++;
      }

      DescriptiveStatistics windowStats = new DescriptiveStatistics(window);
      /*if(i<7 )
      {
      	log.debug("Stats for ring: "+i);
      	try{
      		log.debug("  Window 0: "+window[0]);
      		log.debug("  Window 1: "+window[1]);
      		log.debug("  Window 2: "+window[2]);
      		log.debug("  Window 3: "+window[3]);
      		log.debug("  Window 4: "+window[4]);
      		log.debug("  Window 5: "+window[5]);
      		log.debug("  Window 6: "+window[6]);
      	} catch (ArrayIndexOutOfBoundsException e){}
      	log.debug("  Mean  is "+i+" - "+(int) windowStats.getMean());
      	log.debug("  Min   is "+i+" - "+(int) windowStats.getMin());
      	log.debug("  Std   is "+i+" - "+(int) windowStats.getStandardDeviation());
      	log.debug("  Std/2 is "+i+" - "+(int) windowStats.getStandardDeviation()/2);
      }*/

      // y-position for this point
      try {
        value = yTransform(((Number) g.graph.getRingWidthData().get(i)).intValue() * g.scale);
      } catch (ClassCastException cce) {
        value = yTransform(0); // e.g., if it's being edited, it's still a string
        // BAD!  instead: (1) draw what i've got so far, and (2) NEXT point is a move-to.
        // -- try to parse String as an integer?
      }
      int y = bottom - (int) (value * unitScale) - (int) (g.yoffset * unitScale);

      // Calculate skeleton category
      Integer skeletonCateogory = null;

      String prefAlg =
          App.prefs.getPref(
              PrefKey.STATS_SKELETON_PLOT_ALGORITHM, SkeletonPlotAlgorithm.PERCENTILES.toString());

      if (prefAlg.equals(SkeletonPlotAlgorithm.PERCENTILES.toString())) {
        skeletonCateogory = getSkeletonCategoryFromPercentiles(value, windowStats);
      } else if (prefAlg.equals(SkeletonPlotAlgorithm.CROPPER1979_0POINT5.toString())) {
        skeletonCateogory = getSkeletonCategoryFromCropper1979(value, windowStats, 0.5);
      } else if (prefAlg.equals(SkeletonPlotAlgorithm.CROPPER1979_0POINT75.toString())) {
        skeletonCateogory = getSkeletonCategoryFromCropper1979(value, windowStats, 0.75);
      }

      // Draw the skeleton line
      if (areBonesBelowLine) {
        g2.drawLine(x, baseYVal, x, baseYVal + (skeletonCateogory * 5));
      } else {
        g2.drawLine(x, baseYVal, x, baseYVal - (skeletonCateogory * 5));
      }

      // Try and paint remark icons
      try {
        List<TridasRemark> remarks = g.graph.getTridasValues().get(i).getRemarks();

        if (areBonesBelowLine) {
          Graph.drawRemarkIcons(
              g2, gInfo, remarks, g.graph.getTridasValues().get(i), x, baseYVal, false);
        } else {
          Graph.drawRemarkIcons(
              g2, gInfo, remarks, g.graph.getTridasValues().get(i), x, baseYVal, true);
        }

      } catch (Exception e) {
        log.error("Exception drawing icons to graph: " + e.getClass());
      }
    }
  }