Package plotter.xy

Source Code of plotter.xy.LinearXYPlotLine

/*******************************************************************************
* Mission Control Technologies, Copyright (c) 2009-2012, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* The MCT platform is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* MCT includes source code licensed under additional open source licenses. See
* the MCT Open Source Licenses file included with this distribution or the About
* MCT Licenses dialog available at runtime from the MCT Help menu for additional
* information.
*******************************************************************************/
package plotter.xy;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;

import plotter.DoubleData;
import plotter.DoubleDataDouble;


/**
* Plots linear XY data.
* Assumes that the X data is sorted.
* @author Adam Crume
*/
public class LinearXYPlotLine extends XYPlotLine implements XYDataset {
  private static final long serialVersionUID = 1L;

  /** The X data. */
  private DoubleData xData = new DoubleDataDouble();

  /** The Y data. */
  private DoubleData yData = new DoubleDataDouble();

  /** The X axis, used to retrieve the min and max. */
  private XYAxis xAxis;

  /** The Y axis, used to retrieve the min and max. */
  private XYAxis yAxis;

  /** The algorithm used to draw the line. */
  private LineMode lineMode = LineMode.STRAIGHT;

  /** The stroke used to draw the line, or null to use the default. */
  private Stroke stroke;

  /** Specifies which lines to draw around a missing/invalid point. */
  private MissingPointMode missingPointMode = MissingPointMode.NONE;

  /** The independent dimension stores data in increasing or decreasing order.  May be null for a scatter plot or parametric plot, although this is not supported yet. */
  private XYDimension independentDimension;


  /**
   * Creates a plot line.
   * The independent dimension stores data in increasing or decreasing order.  May be null for a scatter plot or parametric plot, although this is not supported yet.
   * @param xAxis the X axis
   * @param yAxis the Y axis
   * @param independentDimension the independent dimension
   */
  public LinearXYPlotLine(XYAxis xAxis, XYAxis yAxis, XYDimension independentDimension) {
    this.xAxis = xAxis;
    this.yAxis = yAxis;
    this.independentDimension = independentDimension;
  }


  @Override
  protected void paintComponent(Graphics g) {
    int n = xData.getLength();
    Graphics2D g2 = (Graphics2D) g;
    final double xstart = xAxis.getStart();
    final double xend = xAxis.getEnd();
    final double ystart = yAxis.getStart();
    final double yend = yAxis.getEnd();
    final int width = getWidth();
    final int height = getHeight();
    g2.setColor(getForeground());
    if(stroke != null) {
      g2.setStroke(stroke);
    }

    // Skip the points that are outside our clipping area
    // TODO: Support a stop index, not just a start index
    int index;
    if(independentDimension == XYDimension.X) {
      int clipx;
      if(xstart < xend) {
        clipx = g.getClipBounds().x;
      } else {
        clipx = (int) g.getClipBounds().getMaxX();
      }
      clipx = toAxisX(clipx) - 1;
      double min = xAxis.toLogical(clipx);
      index = xData.binarySearch(min);
      if(index < 0) {
        index = -index - 1;
      }
      index--;
    } else {
      int clipy;
      if(ystart < yend) {
        clipy = (int) g.getClipBounds().getMaxY();
      } else {
        clipy = (int) g.getClipBounds().getMinY();
      }
      clipy = toAxisY(clipy) - 1;
      double min = yAxis.toLogical(clipy);
      index = yData.binarySearch(min);
      if(index < 0) {
        index = -index - 1;
      }
      index--;
    }

    int i = Math.max(0, index);
    double xscale = width / (xend - xstart);
    double yscale = height / (yend - ystart);
    int[] pointsx = new int[(n - i) * 2];
    int[] pointsy = new int[pointsx.length];

    DoubleData dependentData = independentDimension == XYDimension.Y ? xData : yData;
    // Loop through all the points to draw.
    outer: while(i < n - 1) {
      // Find the first non-NaN point.
      while(Double.isNaN(dependentData.get(i))) {
        i++;
        if(i == n) {
          break outer;
        }
      }
      int x = (int) ((xData.get(i) - xstart) * xscale + .5) - 1;
      int y = height - (int) ((yData.get(i) - ystart) * yscale + .5);
      int points = 0;
      if(missingPointMode == MissingPointMode.RIGHT || missingPointMode == MissingPointMode.BOTH) {
        if(i > 0 && i > index) {
          if(independentDimension == XYDimension.X) {
            pointsx[points] = (int) ((xData.get(i - 1) - xstart) * xscale + .5) - 1;
            pointsy[points] = y;
            points++;
          } else if(independentDimension == XYDimension.Y) {
            pointsy[points] = height - (int) ((yData.get(i - 1) - ystart) * yscale + .5);
            pointsx[points] = x;
            points++;
          }
        }
      }
      pointsx[points]=x;
      pointsy[points]=y;
      points++;
      i++;
      // Add points until we come to the end or a NaN point.
      while(i < n) {
        if(Double.isNaN(dependentData.get(i))) {
          if(missingPointMode == MissingPointMode.BOTH || missingPointMode == MissingPointMode.LEFT) {
            if(independentDimension == XYDimension.X) {
              double xd = xData.get(i);
              int x2 = (int) ((xd - xstart) * xscale + .5) - 1;
              pointsx[points] = x2;
              pointsy[points] = y;
              points++;
            } else if(independentDimension == XYDimension.Y) {
              double yd = yData.get(i);
              int y2 = height - (int) ((yd - ystart) * yscale + .5);
              pointsx[points] = x;
              pointsy[points] = y2;
              points++;
            }
          }
          i++;
          break;
        }
        double xd = xData.get(i);
        double yd = yData.get(i);
        int x2 = (int) ((xd - xstart) * xscale + .5) - 1;
        int y2 = height - (int) ((yd - ystart) * yscale + .5);

        if(lineMode == LineMode.STRAIGHT) {
          pointsx[points]=x2;
          pointsy[points]=y2;
          points++;
        } else if(lineMode == LineMode.STEP_XY) {
          pointsx[points]=x2;
          pointsy[points]=y;
          points++;
          pointsx[points]=x2;
          pointsy[points]=y2;
          points++;
        } else {
          pointsx[points]=x;
          pointsy[points]=y2;
          points++;
          pointsx[points]=x2;
          pointsy[points]=y2;
          points++;
        }

        x = x2;
        y = y2;
        i++;
      }
      if(points > 1) {
        g2.drawPolyline(pointsx, pointsy, points);
      }
    }

    if(pointFill != null || pointOutline != null || pointIcon != null) {
      int oldx = 0;
      int oldy = 0;
      for(i = Math.max(0, index); i < n; i++) {
        double xx = xData.get(i);
        double yy = yData.get(i);
        if(!Double.isNaN(xx) && !Double.isNaN(yy)) {
          int x = (int) ((xx - xstart) * xscale + .5) - 1;
          int y = height - (int) ((yy - ystart) * yscale + .5);
          g2.translate(x - oldx, y - oldy);
          if(pointFill != null) {
            g2.fill(pointFill);
          }
          if(pointOutline != null) {
            g2.draw(pointOutline);
          }
          if(pointIcon != null) {
            pointIcon.paintIcon(this, g2, 0, 0);
          }
          oldx = x;
          oldy = y;
        }
      }
    }
  }


  private int toAxisX(int x) {
    // Assumption: plot line is contained in an XYPlotContents, which is contained in an XYPlot.  xAxis is contained in the XYPlot.
    return x + getParent().getX() - xAxis.getX();
  }


  private int toAxisY(int y) {
    // Assumption: plot line is contained in an XYPlotContents, which is contained in an XYPlot.  yAxis is contained in the XYPlot.
    return y + getParent().getY() - yAxis.getY();
  }


  /**
   * Repaints a data point and adjoining line segments.
   * @param index index of the data point
   */
  @Override
  public void repaintData(int index) {
    // Implementation note:
    // We don't call repaintData(int,int) with a count of 1
    // because this method gets called a lot (from SimpleXYDataset.add, for example)
    // so we want it to be fast.

    // Calculate the bounding box of the line segment(s) that need to be repainted
    double x = xData.get(index);
    double y = yData.get(index);
    XYAxis xAxis = getXAxis();
    XYAxis yAxis = getYAxis();
    int xmin = xAxis.toPhysical(x);
    int xmax = xmin;
    int ymin = yAxis.toPhysical(y);
    int ymax = ymin;

    // Take care of the previous point
    if(index > 0) {
      int x2p = xAxis.toPhysical(xData.get(index - 1));
      if(x2p > xmax) {
        xmax = x2p;
      } else if(x2p < xmin) {
        xmin = x2p;
      }
      int y2p = yAxis.toPhysical(yData.get(index - 1));
      if(y2p > ymax) {
        ymax = y2p;
      } else if(y2p < ymin) {
        ymin = y2p;
      }
    }

    // Take care of the next point
    if(index < xData.getLength() - 1) {
      int x2p = xAxis.toPhysical(xData.get(index + 1));
      if(x2p > xmax) {
        xmax = x2p;
      } else if(x2p < xmin) {
        xmin = x2p;
      }
      int y2p = yAxis.toPhysical(yData.get(index + 1));
      if(y2p > ymax) {
        ymax = y2p;
      } else if(y2p < ymin) {
        ymin = y2p;
      }
    }

    // Adjust for offsets.
    int xo = toAxisX(0);
    int yo = toAxisY(0);
    xmin -= xo;
    xmax -= xo;
    ymin -= yo;
    ymax -= yo;

    // Add a fudge factor, just to be sure.
    int fudge = 1;
    xmin -= fudge;
    xmax += fudge;
    ymin -= fudge;
    ymax += fudge;

    repaint(xmin, ymin, xmax - xmin, ymax - ymin);
  }


  /**
   * Repaints data points and adjoining line segments.
   * @param index index of the first data point
   * @param count number of data points
   */
  @Override
  public void repaintData(int index, int count) {
    if(count == 0) {
      return;
    }

    // Calculate the bounding box of the line segment(s) that need to be repainted
    XYAxis xAxis = getXAxis();
    XYAxis yAxis = getYAxis();
    double xmin = Double.POSITIVE_INFINITY;
    double xmax = Double.NEGATIVE_INFINITY;
    double ymin = Double.POSITIVE_INFINITY;
    double ymax = Double.NEGATIVE_INFINITY;

    // Take care of the interior points
    for(int i = 0; i < count; i++) {
      double x = xData.get(index + i);
      double y = yData.get(index + i);
      if(x > xmax) {
        xmax = x;
      }
      if(x < xmin) {
        xmin = x;
      }
      if(y > ymax) {
        ymax = y;
      }
      if(y < ymin) {
        ymin = y;
      }
    }

    // Take care of the previous point
    if(index > 0) {
      double x = xData.get(index - 1);
      if(x > xmax) {
        xmax = x;
      }
      if(x < xmin) {
        xmin = x;
      }
      double y = yData.get(index - 1);
      if(y > ymax) {
        ymax = y;
      }
      if(y < ymin) {
        ymin = y;
      }
    }

    // Take care of the next point
    if(index + count < xData.getLength()) {
      double x = xData.get(index + count);
      if(x > xmax) {
        xmax = x;
      }
      if(x < xmin) {
        xmin = x;
      }
      double y = yData.get(index + count);
      if(y > ymax) {
        ymax = y;
      }
      if(y < ymin) {
        ymin = y;
      }
    }

    int xmin2 = xAxis.toPhysical(xmin);
    int xmax2 = xAxis.toPhysical(xmax);
    int ymin2 = yAxis.toPhysical(ymin);
    int ymax2 = yAxis.toPhysical(ymax);
    if(xmin2 > xmax2) {
      int tmp = xmin2;
      xmin2 = xmax2;
      xmax2 = tmp;
    }
    if(ymin2 > ymax2) {
      int tmp = ymin2;
      ymin2 = ymax2;
      ymax2 = tmp;
    }

    // Adjust for offsets.
    int xo = toAxisX(0);
    int yo = toAxisY(0);
    xmin2 -= xo;
    xmax2 -= xo;
    ymin2 -= yo;
    ymax2 -= yo;

    // Add a fudge factor, just to be sure.
    int fudge = 1;
    xmin2 -= fudge;
    xmax2 += fudge;
    ymin2 -= fudge;
    ymax2 += fudge;

    repaint(xmin2, ymin2, xmax2 - xmin2, ymax2 - ymin2);
  }


  /**
   * Returns the X data.
   * @return the X data
   */
  @Override
  public DoubleData getXData() {
    return xData;
  }


  /**
   * Sets the X data.
   * @param xData the X data
   */
  public void setXData(DoubleData xData) {
    this.xData = xData;
  }


  /**
   * Returns the Y data.
   * @return the Y data
   */
  @Override
  public DoubleData getYData() {
    return yData;
  }


  /**
   * Sets the Y data.
   * @param yData the Y data
   */
  public void setYData(DoubleData yData) {
    this.yData = yData;
  }


  /**
   * Returns the X axis.
   * @return the X axis
   */
  public XYAxis getXAxis() {
    return xAxis;
  }


  /**
   * Returns the Y axis.
   * @return the Y axis
   */
  public XYAxis getYAxis() {
    return yAxis;
  }


  /**
   * Returns the line mode used for connecting points.
   * @return the line mode used for connecting points
   */
  public LineMode getLineMode() {
    return lineMode;
  }


  /**
   * Sets the line mode used for connecting points.
   * @param lineMode the line mode used for connecting points
   */
  public void setLineMode(LineMode lineMode) {
    this.lineMode = lineMode;
  }


  /**
   * Returns the stroke used to draw the line.
   * @return the stroke used to draw the line
   */
  public Stroke getStroke() {
    return stroke;
  }


  /**
   * Sets the stroke used to draw the line.
   * @param stroke the stroke used to draw the line
   */
  public void setStroke(Stroke stroke) {
    this.stroke = stroke;
  }


  /**
   * Returns the missing point mode.
   * @return the missing point mode
   */
  public MissingPointMode getMissingPointMode() {
    return missingPointMode;
  }


  /**
   * Sets the missing point mode.
   * @param missingPointMode the missing point mode
   */
  public void setMissingPointMode(MissingPointMode missingPointMode) {
    this.missingPointMode = missingPointMode;
  }


  /**
   * Returns the independent dimension.  May be null.
   * @return the independent dimension
   */
  @Override
  public XYDimension getIndependentDimension() {
    return independentDimension;
  }


  @Override
  public void prepend(double[] x, int xoff, double[] y, int yoff, int len) {
    xData.prepend(x, xoff, len);
    yData.prepend(y, yoff, len);
    repaintData(0, len);
  }


  @Override
  public void prepend(DoubleData x, DoubleData y) {
    int len = x.getLength();
    if(len != y.getLength()) {
      throw new IllegalArgumentException("x and y must be the same length.  x.length = " + x.getLength() + ", y.length = " + y.getLength());
    }
    xData.prepend(x, 0, len);
    yData.prepend(y, 0, len);
    repaintData(0, len);
  }


  @Override
  public int getPointCount() {
    return xData.getLength();
  }


  @Override
  public void removeAllPoints() {
    xData.removeAll();
    yData.removeAll();
    repaint();
  }


  @Override
  public void add(double x, double y) {
    xData.add(x);
    yData.add(y);
    repaintData(xData.getLength() - 1);
  }


  @Override
  public void removeFirst(int removeCount) {
    repaintData(0, removeCount);
    xData.removeFirst(removeCount);
    yData.removeFirst(removeCount);
  }


  @Override
  public void removeLast(int count) {
    repaintData(xData.getLength() - count, count);
    xData.removeLast(count);
    yData.removeLast(count);
  }


  /**
   * A line mode specifies how lines are drawn connecting points.
   */
  public enum LineMode {
    /**
     * Straight lines are drawn between points.
     */
    STRAIGHT,

    /**
     * A horizontal line is drawn to the new point's X coordinate, then a vertical line is drawn.
     */
    STEP_XY,

    /**
     * A vertical line is drawn the the new point's Y coordinate, then a horizontal line is drawn.
     */
    STEP_YX
  }
TOP

Related Classes of plotter.xy.LinearXYPlotLine

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.