content: // ===============================================================================
  // Authors: AFRL/RQQD
  // Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division
  // 
  // Copyright (c) 2017 Government of the United State of America, as represented by
  // the Secretary of the Air Force.  No copyright is claimed in the United States under
  // Title 17, U.S. Code.  All Other Rights Reserved.
  // ===============================================================================
  
  /*
   * GRAL: GRAphing Library for Java(R)
   *
   * (C) Copyright 2009-2013 Erich Seifert <dev[at]erichseifert.de>,
   * Michael Seifert <mseifert[at]error-reports.org>
   *
   * This file is part of GRAL.
   *
   * GRAL 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 3 of the License, or
   * (at your option) any later version.
   *
   * GRAL 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 GRAL.  If not, see <http://www.gnu.org/licenses/>.
   */
  package de.erichseifert.gral.plots;
  
  import java.awt.Color;
  import java.awt.Graphics2D;
  import java.awt.Paint;
  import java.awt.Shape;
  import java.awt.geom.AffineTransform;
  import java.awt.geom.Dimension2D;
  import java.awt.geom.Point2D;
  import java.awt.geom.Rectangle2D;
  
  import de.erichseifert.gral.data.DataSource;
  import de.erichseifert.gral.data.DataTable;
  import de.erichseifert.gral.data.Row;
  import de.erichseifert.gral.data.statistics.Statistics;
  import de.erichseifert.gral.graphics.AbstractDrawable;
  import de.erichseifert.gral.graphics.Drawable;
  import de.erichseifert.gral.graphics.DrawingContext;
  import de.erichseifert.gral.plots.axes.Axis;
  import de.erichseifert.gral.plots.axes.AxisRenderer;
  import de.erichseifert.gral.plots.colors.ColorMapper;
  import de.erichseifert.gral.plots.colors.ContinuousColorMapper;
  import de.erichseifert.gral.plots.colors.Grayscale;
  import de.erichseifert.gral.plots.points.AbstractPointRenderer;
  import de.erichseifert.gral.plots.points.PointData;
  import de.erichseifert.gral.util.GraphicsUtils;
  import de.erichseifert.gral.util.PointND;
  
  
  /**
   * <p>Class that displays two coordinate values and a value as a raster of
   * boxes. The data source must provide at least three columns:</p>
   * <ul>
   *   <li>x coordinate</li>
   *   <li>y coordinate</li>
   *   <li>value</li>
   * </ul>
   * <p>The method {@link #createRasterData(DataSource)} can be used to convert
   * a matrix of values to the (coordinates, value) format.</p>
   *
   * <p>To create a new {@code RasterPlot} simply create a new instance using
   * a suitable data source. Example:</p>
   * <pre>
   * DataTable data = new DataTable(Double.class, Double.class);
   * data.add(10.98, -12.34);
   * data.add( 7.65,  45.67);
   * data.add(43.21,  89.01);
   * DataSource rasterData = RasterPlot.createRasterData(data);
   * RasterPlot plot = new RasterPlot(rasterData);
   * </pre>
   */
  public class RasterPlot extends XYPlot {
  	/** Version id for serialization. */
  	private static final long serialVersionUID = 5844862286358250831L;
  
  	/** Offset of the raster pixels to the origin. */
  	private final Point2D offset;
  	/** Size of the raster pixels. */
  	private final Dimension2D distance;
  	/** Color mapping to fill the raster pixels. */
  	private ColorMapper colors;
  
  	/**
  	 * Class that renders a box and its whiskers in a box-and-whisker plot.
  	 */
  	protected static class RasterRenderer extends AbstractPointRenderer {
  		/** Version id for serialization. */
  		private static final long serialVersionUID = 1266585364126459761L;
  
  		/** Plot specific settings. */
  		private final RasterPlot plot;
  
  		/** Horizontal position of the current raster pixel. */
  		private int xColumn;
  		/** Vertical position of the current raster pixel. */
  		private int yColumn;
  		/** Value of the current raster pixel. */
  		private int valueColumn;
  
  		/**
  		 * Constructor that creates a new instance and initializes it with a
  		 * plot as data provider. The default columns for (x, y, value) are set
  		 * to (0, 1, 2)
  		 * @param plot Plot storing global settings.
  		 */
  		public RasterRenderer(RasterPlot plot) {
  			this.plot = plot;
  			xColumn = 0;
  			yColumn = 1;
  			valueColumn = 2;
  		}
  
  		/**
  		 * Returns the index of the column which is used for the x coordinate
  		 * of a point.
  		 * @return Index of the column for the x coordinate of a point.
  		 */
  		public int getXColumn() {
  			return xColumn;
  		}
  
  		/**
  		 * Sets the index of the column which will be used for the x coordinate
  		 * of a point.
  		 * @param columnIndex Index of the column for the x coordinate of a point.
  		 */
  		public void setXColumn(int columnIndex) {
  			this.xColumn = columnIndex;
  		}
  
  		/**
  		 * Returns the index of the column which is used for the y coordinate
  		 * of a point.
  		 * @return Index of the column for the y coordinate of a point.
  		 */
  		public int getYColumn() {
  			return yColumn;
  		}
  
  		/**
  		 * Sets the index of the column which will be used for the y coordinate
  		 * of a point.
  		 * @param columnIndex Index of the column for the y coordinate of a point.
  		 */
  		public void setYColumn(int columnIndex) {
  			this.yColumn = columnIndex;
  		}
  
  		/**
  		 * Returns the index of the column which is used for the value of a
  		 * point.
  		 * @return Index of the column for the value of a point.
  		 */
  		@Override
  		public int getValueColumn() {
  			return valueColumn;
  		}
  
  		/**
  		 * Sets the index of the column which will be used for the value of a
  		 * point.
  		 * @param columnIndex Index of the column for the value of a point.
  		 */
  		@Override
  		public void setValueColumn(int columnIndex) {
  			this.valueColumn = columnIndex;
  		}
  
  		/**
  		 * Returns the graphical representation to be drawn for the specified data
  		 * value.
  		 * @param data Information on axes, renderers, and values.
  		 * @param shape Outline that describes the point's shape.
  		 * @return Component that can be used to draw the point
  		 */
  		public Drawable getPoint(final PointData data, final Shape shape) {
  			return new AbstractDrawable() {
  				/** Version id for serialization. */
  				private static final long serialVersionUID = -1136689797647794969L;
  
  				public void draw(DrawingContext context) {
  					RasterRenderer renderer = RasterRenderer.this;
  
  					Axis axisX = data.axes.get(0);
  					Axis axisY = data.axes.get(1);
  					AxisRenderer axisXRenderer = data.axisRenderers.get(0);
  					AxisRenderer axisYRenderer = data.axisRenderers.get(1);
  					Row row = data.row;
  
  					int colX = renderer.getXColumn();
  					if (colX < 0 || colX >= row.size() || !row.isColumnNumeric(colX)) {
  						return;
  					}
  					int colY = renderer.getYColumn();
  					if (colY < 0 || colY >= row.size() || !row.isColumnNumeric(colY)) {
  						return;
  					}
  					int colValue = renderer.getValueColumn();
  					if (colValue < 0 || colValue >= row.size() || !row.isColumnNumeric(colValue)) {
  						return;
  					}
  
  					double valueX = ((Number) row.get(colX)).doubleValue();
  					double valueY = ((Number) row.get(colY)).doubleValue();
  					Number value = (Number) row.get(colValue);
  
  					// Pixel dimensions
  					double xMin = axisXRenderer
  						.getPosition(axisX, valueX - 0.5, true, false)
  						.get(PointND.X);
  					double xMax = axisXRenderer
  						.getPosition(axisX, valueX + 0.5, true, false)
  						.get(PointND.X);
  					double width = Math.abs(xMax - xMin) + 1.0;
  					double yMin = axisYRenderer
  						.getPosition(axisY, valueY - 0.5, true, false)
  						.get(PointND.Y);
  					double yMax = axisYRenderer
  						.getPosition(axisY, valueY + 0.5, true, false)
  						.get(PointND.Y);
  					double height = Math.abs(yMax - yMin) + 1.0;
  
  					// Create shape for pixel
  					// The origin of all shapes is (boxX, boxY)
  					Rectangle2D shapeBounds = shape.getBounds2D();
  					AffineTransform tx = new AffineTransform();
  					tx.scale(width/shapeBounds.getWidth(), height/shapeBounds.getHeight());
  					tx.translate(-shapeBounds.getMinX(), -shapeBounds.getMinY());
  					Shape pixel = tx.createTransformedShape(shape);
  
  					// Paint pixel
  					Graphics2D graphics = context.getGraphics();
  					ColorMapper colorMapper = plot.getColors();
  					Paint paint;
  					if (colorMapper instanceof ContinuousColorMapper) {
  						paint = ((ContinuousColorMapper) colorMapper)
  							.get(value.doubleValue());
  					} else if (colorMapper != null) {
  						Integer index = value.intValue();
  						paint = colorMapper.get(index);
  					} else {
  						paint = Color.BLACK;
  					}
  					GraphicsUtils.fillPaintedShape(
  						graphics, pixel, paint, pixel.getBounds2D());
  				}
  			};
  		}
  
  		/**
  		 * Returns a {@code Shape} instance that can be used for further
  		 * calculations.
  		 * @param data Information on axes, renderers, and values.
  		 * @return Outline that describes the point's shape.
  		 */
  		public Shape getPointShape(PointData data) {
  			return getShape();
  		}
  
  		/**
  		 * Returns a graphical representation of the value label to be drawn for
  		 * the specified data value.
  		 * @param data Information on axes, renderers, and values.
  		 * @param shape Outline that describes the bounds for the value label.
  		 * @return Component that can be used to draw the value label.
  		 */
  		public Drawable getValue(final PointData data, final Shape shape) {
  			Drawable drawable = new AbstractDrawable() {
  				/** Version id for serialization. */
  				private static final long serialVersionUID = -8402945980942955359L;
  
  				public void draw(DrawingContext context) {
  					// TODO Implement rendering of value label
  				}
  			};
  			return drawable;
  		}
  	}
  
  	/**
  	 * Initializes a new box-and-whisker plot with the specified data source.
  	 * @param data Data to be displayed.
  	 */
  	public RasterPlot(DataSource data) {
  		offset = new Point2D.Double();
  		distance = new de.erichseifert.gral.util.Dimension2D.Double(1.0, 1.0);
  		colors = new Grayscale();
  
  		((XYPlotArea2D) getPlotArea()).setMajorGridX(false);
  		((XYPlotArea2D) getPlotArea()).setMajorGridY(false);
  		//getAxisRenderer(AXIS_X).setSetting(AxisRenderer.TICKS, false);
  		//getAxisRenderer(AXIS_Y).setSetting(AxisRenderer.TICKS, false);
  		getAxisRenderer(AXIS_X).setIntersection(-Double.MAX_VALUE);
  		getAxisRenderer(AXIS_Y).setIntersection(-Double.MAX_VALUE);
  
  		// Store data
  		add(data);
  
  		// Adjust axes to the data series
  		autoscaleAxes();
  	}
  
  	@Override
  	public void autoscaleAxis(String axisName) {
  		if (AXIS_X.equals(axisName) || AXIS_Y.equals(axisName)) {
  			Dimension2D dist = getDistance();
  			// In case we get called before settings defaults have been set,
  			// just set distance to a sane default
  			if (dist == null) {
  				dist = new de.erichseifert.gral.util.Dimension2D.Double(1.0, 1.0);
  			}
  
  			Axis axis = getAxis(axisName);
  			if (axis == null || !axis.isAutoscaled()) {
  				return;
  			}
  
  			double min = getAxisMin(axisName);
  			double max = getAxisMax(axisName);
  			if (AXIS_X.equals(axisName)) {
  				axis.setRange(min, max + dist.getWidth());
  			} else if (AXIS_Y.equals(axisName)) {
  				axis.setRange(min - dist.getHeight(), max);
  			}
  		} else {
  			super.autoscaleAxis(axisName);
  		}
  	}
  
  	/**
  	 * Takes a matrix of values and creates a new data source that stores the
  	 * values in (x, y, value) format.
  	 * @param data Original data source with values in each cell.
  	 * @return New data source with (x, y, value) columns
  	 */
  	@SuppressWarnings("unchecked")
  	public static DataSource createRasterData(DataSource data) {
  		if (data == null) {
  			throw new NullPointerException("Cannot convert null data source.");
  		}
  
  		DataTable coordsValueData =
  			new DataTable(Double.class, Double.class, Double.class);
  
  		// Generate pixel data with (x, y, value)
  		Statistics stats = data.getStatistics();
  		double min = stats.get(Statistics.MIN);
  		double max = stats.get(Statistics.MAX);
  		double range = max - min;
  		int i = 0;
  		for (Comparable<?> cell : data) {
  			int x =  i%data.getColumnCount();
  			int y = -i/data.getColumnCount();
  			double v = Double.NaN;
  			if (cell instanceof Number) {
  				Number numericCell = (Number) cell;
  				v = (numericCell.doubleValue() - min) / range;
  			}
  			coordsValueData.add((double) x, (double) y, v);
  			i++;
  		}
  		return coordsValueData;
  	}
  
  	@Override
  	public void add(int index, DataSource source, boolean visible) {
  		if (getData().size() > 0) {
  			throw new IllegalArgumentException(
  				"This plot type only supports a single data source."); //$NON-NLS-1$
  		}
  		// Add data source
  		super.add(index, source, visible);
  		// Adjust rendering
  		setLineRenderer(source, null);
  		setPointRenderer(source, new RasterRenderer(this));
  	}
  
  	/**
  	 * Returns the horizontal and vertical offset of the raster from the
  	 * origin.
  	 * @return Horizontal and vertical offset of the raster from the origin.
  	 */
  	public Point2D getOffset() {
  		return offset;
  	}
  
  	/**
  	 * Sets the horizontal and vertical offset of the raster from the
  	 * origin.
  	 * @param offset Horizontal and vertical offset of the raster from the
  	 * origin.
  	 */
  	public void setOffset(Point2D offset) {
  		this.offset.setLocation(offset);
  	}
  
  	/**
  	 * Returns the horizontal and vertical distance of the raster elements.
  	 * @return Horizontal and vertical distance of the raster elements.
  	 */
  	public Dimension2D getDistance() {
  		return distance;
  	}
  
  	/**
  	 * Returns the horizontal and vertical distance of the raster elements.
  	 * @param distance Horizontal and vertical distance of the raster elements.
  	 */
  	public void setDistance(Dimension2D distance) {
  		this.distance.setSize(distance);
  	}
  
  	/**
  	 * Returns the object which is used to map pixel values to colors.
  	 * @return Object which is used to map pixel values to colors.
  	 */
  	public ColorMapper getColors() {
  		return colors;
  	}
  
  	/**
  	 * Sets the object which will be used to map pixel values to colors.
  	 * @param colors Object which will be used to map pixel values to colors.
  	 */
  	public void setColors(ColorMapper colors) {
  		this.colors = colors;
  	}
  }
  