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.BasicStroke;
  import java.awt.Font;
  import java.awt.Graphics2D;
  import java.awt.Paint;
  import java.awt.Shape;
  import java.awt.Stroke;
  import java.awt.geom.AffineTransform;
  import java.awt.geom.Arc2D;
  import java.awt.geom.Area;
  import java.awt.geom.Dimension2D;
  import java.awt.geom.Ellipse2D;
  import java.awt.geom.Point2D;
  import java.awt.geom.Rectangle2D;
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.text.Format;
  import java.text.NumberFormat;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  
  import de.erichseifert.gral.data.Column;
  import de.erichseifert.gral.data.DataChangeEvent;
  import de.erichseifert.gral.data.DataSource;
  import de.erichseifert.gral.data.Row;
  import de.erichseifert.gral.graphics.AbstractDrawable;
  import de.erichseifert.gral.graphics.Drawable;
  import de.erichseifert.gral.graphics.DrawingContext;
  import de.erichseifert.gral.navigation.AbstractNavigator;
  import de.erichseifert.gral.navigation.Navigable;
  import de.erichseifert.gral.navigation.Navigator;
  import de.erichseifert.gral.plots.axes.Axis;
  import de.erichseifert.gral.plots.axes.AxisRenderer;
  import de.erichseifert.gral.plots.axes.LinearRenderer2D;
  import de.erichseifert.gral.plots.colors.ColorMapper;
  import de.erichseifert.gral.plots.colors.ContinuousColorMapper;
  import de.erichseifert.gral.plots.colors.QuasiRandomColors;
  import de.erichseifert.gral.plots.legends.ValueLegend;
  import de.erichseifert.gral.plots.points.AbstractPointRenderer;
  import de.erichseifert.gral.plots.points.PointData;
  import de.erichseifert.gral.plots.points.PointRenderer;
  import de.erichseifert.gral.util.GeometryUtils;
  import de.erichseifert.gral.util.GraphicsUtils;
  import de.erichseifert.gral.util.Insets2D;
  import de.erichseifert.gral.util.Location;
  import de.erichseifert.gral.util.MathUtils;
  import de.erichseifert.gral.util.PointND;
  
  
  /**
   * <p>Class that displays data as segments of a pie plot. Empty segments are
   * displayed for negative values.</p>
   * <p>To create a new {@code PiePlot} simply create a new instance using
   * a data source. Example:</p>
   * <pre>
   * DataTable data = new DataTable(Integer.class, Double.class);
   * data.add(-23.50);
   * data.add(100.00);
   * data.add( 60.25);
   *
   * PiePlot plot = new PiePlot(data);
   * </pre>
   */
  public class PiePlot extends AbstractPlot implements Navigable {
  	/** Version id for serialization. */
  	private static final long serialVersionUID = 5486418164040578150L;
  
  	/** Key for specifying the tangential axis of a pie plot. */
  	public static final String AXIS_TANGENTIAL = "tangential"; //$NON-NLS-1$
  
  	/** Mapping from data source to point renderer. */
  	private final Map<DataSource, PointRenderer> pointRenderers;
  	/** Slice objects with start and end position for each visible data source. */
  	private transient Map<DataSource, List<Slice>> slices;
  	/** Cache for the {@code Navigator} implementation. */
  	private transient PiePlotNavigator navigator;
  
  	/** Position of the pie center. */
  	private final Point2D center;
  	/** Radius of the the pie. */
  	private double radius;
  	/** Starting angle in degrees. */
  	private double start;
  	/** Decides whether pie slices are drawn in clockwise direction. */
  	private boolean clockwise;
  
  	/**
  	 * Navigator implementation for pie plots. Zooming changes the
  	 * {@code RADIUS} setting and panning the {@code CENTER} setting.
  	 */
  	public static class PiePlotNavigator extends AbstractNavigator {
  		/** Pie plot that will be navigated. */
  		private final PiePlot plot;
  		/** Location of center in default state. */
  		private PointND<? extends Number> centerOriginal;
  		/** Zoom level in default state. */
  		private double zoomOriginal;
  		/** Current zoom level. */
  		private double zoom;
  
  		/**
  		 * Initializes a new instance with a pie plot to be navigated.
  		 * @param plot Pie plot.
  		 */
  		public PiePlotNavigator(PiePlot plot) {
  			this.plot = plot;
  			this.zoom = 1.0;
  			setDefaultState();
  		}
  
  		/**
  		 * Returns the current zoom level of the associated object.
  		 * @return Current zoom level.
  		 */
  		public double getZoom() {
  			return zoom;
  		}
  
  		/**
  		 * Sets the zoom level of the associated object to the specified value.
  		 * @param zoomNew New zoom level.
  		 */
  		public void setZoom(double zoomNew) {
  			if (!isZoomable() || (zoomNew <= 0.0) ||
  					!MathUtils.isCalculatable(zoomNew)) {
  				return;
  			}
  			double zoomOld = getZoom();
  			zoomNew = MathUtils.limit(zoomNew, getZoomMin(), getZoomMax());
  			if (zoomOld == zoomNew) {
  				return;
  			}
  			zoom = zoomNew;
  			plot.setRadius(zoomOriginal/getZoom());
  		}
  
  		/**
  		 * Returns the current center point. The returned point contains value in
  		 * world units.
  		 * @return Center point in world units.
  		 */
  		public PointND<? extends Number> getCenter() {
  			Point2D center = plot.getCenter();
  			return new PointND<Number>(center.getX(), center.getY());
  		}
  
  		/**
  		 * Sets a new center point. The values of the point are in world units.
  		 * @param center New center point in world units.
  		 */
  		public void setCenter(PointND<? extends Number> center) {
  			if (center == null || !isPannable()) {
  				return;
  			}
  			Point2D center2d = center.getPoint2D();
  			plot.setCenter(center2d);
  		}
  
  		/**
  		 * Moves the center by the relative values of the specified point.
  		 * The values of the point are in screen units.
  		 * @param deltas Relative values to use for panning.
  		 */
  		@SuppressWarnings("unchecked")
  		public void pan(PointND<? extends Number> deltas) {
  			PlotArea plotArea = plot.getPlotArea();
  			PointND<Number> center = (PointND<Number>) getCenter();
  			double x = center.get(0).doubleValue();
  			x += deltas.get(0).doubleValue()/plotArea.getWidth();
  			double y = center.get(1).doubleValue();
  			y += deltas.get(1).doubleValue()/plotArea.getHeight();
  			center.set(0, x);
  			center.set(1, y);
  			setCenter(center);
  		}
  
  		/**
  		 * Sets the object's position and zoom level to the default state.
  		 */
  		public void reset() {
  			setCenter(centerOriginal);
  			setZoom(1.0);
  		}
  
  		/**
  		 * Sets the current state as the default state of the object.
  		 * Resetting the navigator will then return to the default state.
  		 */
  		public void setDefaultState() {
  			centerOriginal = getCenter();
  			zoomOriginal = plot.getRadius();
  		}
  	}
  
  	/**
  	 * Class that represents the drawing area of a {@code PiePlot}.
  	 */
  	public static class PiePlotArea2D extends PlotArea {
  		/** Version id for serialization. */
  		private static final long serialVersionUID = 5646816099037852271L;
  
  		/** Pie plot that this renderer is associated to. */
  		private final PiePlot plot;
  
  		/**
  		 * Constructor that creates a new instance and initializes it with a
  		 * plot acting as data provider.
  		 * @param plot Data provider.
  		 */
  		public PiePlotArea2D(PiePlot plot) {
  			this.plot = plot;
  		}
  
  		/**
  		 * Draws the {@code Drawable} with the specified drawing context.
  		 * @param context Environment used for drawing
  		 */
  		public void draw(DrawingContext context) {
  			drawBackground(context);
  			drawBorder(context);
  			drawPlot(context);
  			plot.drawLegend(context);
  		}
  
  		@Override
  		protected void drawPlot(DrawingContext context) {
  			Graphics2D graphics = context.getGraphics();
  
  			Shape clipBoundsOld = graphics.getClip();
  			Insets2D clipOffset = getClippingOffset();
  			if (clipOffset != null) {
  				// TODO Use real font size instead of fixed value
  				final double fontSize = 10.0;
  
  				// Perform clipping
  				Shape clipBounds = new Rectangle2D.Double(
  					getX() + clipOffset.getLeft()*fontSize,
  					getY() + clipOffset.getTop()*fontSize,
  					getWidth() - clipOffset.getHorizontal()*fontSize,
  					getHeight() - clipOffset.getVertical()*fontSize
  				);
  				// Take care of old clipping region. This is used when getting
  				// scrolled in a JScrollPane for example.
  				if (clipBoundsOld != null) {
  					Area clipBoundsNew = new Area(clipBoundsOld);
  					clipBoundsNew.intersect(new Area(clipBounds));
  					clipBounds = clipBoundsNew;
  				}
  				graphics.setClip(clipBounds);
  			}
  
  			AffineTransform txOrig = graphics.getTransform();
  			graphics.translate(getX(), getY());
  
  			// Get width and height of the plot area for relative sizes
  			Rectangle2D bounds = getBounds();
  
  			// Move to center, so origin for point renderers will be (0, 0)
  			Point2D center = plot.getCenter();
  			if (center == null) {
  				center = new Point2D.Double(0.5, 0.5);
  			}
  			graphics.translate(
  				center.getX()*bounds.getWidth(),
  				center.getY()*bounds.getHeight()
  			);
  
  			// Paint points and lines
  			for (DataSource s : plot.getVisibleData()) {
  				// Skip empty data source
  				if (s.getColumnCount() == 0) {
  					continue;
  				}
  
  				int colIndex = 0;
  				if (colIndex < 0 || colIndex >= s.getColumnCount() ||
  						!s.isColumnNumeric(colIndex)) {
  					continue;
  				}
  
  				PointRenderer pointRenderer = plot.getPointRenderer(s);
  
  				String[] axisNames = plot.getMapping(s);
  				// TODO Use loop to get all axes instead of direct access
  				Axis axis = plot.getAxis(axisNames[0]);
  				if (!axis.isValid()) {
  					continue;
  				}
  				AxisRenderer axisRenderer = plot.getAxisRenderer(axisNames[0]);
  
  				List<Axis> axes = Arrays.asList(axis);
  				List<AxisRenderer> axisRenderers = Arrays.asList(axisRenderer);
  				// Draw graphics
  				for (int rowIndex = 0; rowIndex < s.getRowCount(); rowIndex++) {
  					Row row = s.getRow(rowIndex);
  					PointData pointData = new PointData(
  						axes, axisRenderers, row, 0);
  					Shape shape = pointRenderer.getPointShape(pointData);
  					Drawable point = pointRenderer.getPoint(pointData, shape);
  					point.setBounds(bounds);
  					point.draw(context);
  				}
  				// Draw labels
  				for (int rowIndex = 0; rowIndex < s.getRowCount(); rowIndex++) {
  					Row row = s.getRow(rowIndex);
  					PointData pointData = new PointData(
  						axes, axisRenderers, row, 0);
  					Shape shape = pointRenderer.getPointShape(pointData);
  					Drawable point = pointRenderer.getValue(pointData, shape);
  					point.setBounds(bounds);
  					point.draw(context);
  				}
  			}
  
  			graphics.setTransform(txOrig);
  
  			if (clipOffset != null) {
  				// Reset clipping
  				graphics.setClip(clipBoundsOld);
  			}
  		}
  	}
  
  	/**
  	 * Data class for storing slice information in world units.
  	 */
  	protected static final class Slice {
  		/** Value where the slice starts. */
  		public final double start;
  		/** Value where the slice ends. */
  		public final double end;
  
  		/**
  		 * Initializes a new slice with start and end value.
  		 * @param start Value where the slice starts.
  		 * @param end Value where the slice ends.
  		 */
  		public Slice(double start, double end) {
  			this.start = start;
  			this.end = end;
  		}
  	}
  
  	/**
  	 * A point renderer for a single slice in a pie plot.
  	 */
  	public static class PieSliceRenderer extends AbstractPointRenderer {
  		/** Version id for serialization. */
  		private static final long serialVersionUID = 1135636437801090607L;
  
  		/** Pie plot this renderer is attached to. */
  		private final PiePlot plot;
  
  		/** Relative outer radius of the current pie slice,
  		 * in percentage of the total radius. */
  		private double outerRadius;
  		/** Relative inner radius of the current pie slice,
  		 * in percentage of the total radius. */
  		private double innerRadius;
  		/** Gap of the current pie slice, in pixels. */
  		private double gap;
  
  		/**
  		 * Initializes a new instance with a pie plot object.
  		 * @param plot Pie plot.
  		 */
  		public PieSliceRenderer(PiePlot plot) {
  			this.plot =  plot;
  
  			setValueColumn(0);
  			setErrorColumnTop(1);
  			setErrorColumnBottom(2);
  
  			setColor(new QuasiRandomColors());
  			outerRadius = 1.0;
  			innerRadius = 0.0;
  			gap = 0.0;
  		}
  
  		/**
  		 * Returns the value for the outer radius of a pie relative to the
  		 * radius set in the plot.
  		 * @return Outer radius of a pie relative to the radius of the plot.
  		 */
  		public double getOuterRadius() {
  			return outerRadius;
  		}
  
  		/**
  		 * Sets the value for the outer radius of a pie relative to the radius
  		 * set in the plot.
  		 * @param radius Outer radius of a pie relative to the radius of the
  		 * plot.
  		 */
  		public void setOuterRadius(double radius) {
  			this.outerRadius = radius;
  		}
  
  		/**
  		 * Returns the value for the inner radius of a pie relative to the
  		 * radius set in the plot.
  		 * @return Inner radius of a pie relative to the radius of the plot.
  		 */
  		public double getInnerRadius() {
  			return innerRadius;
  		}
  
  		/**
  		 * Sets the value for the inner radius of a pie relative to the radius
  		 * set in the plot.
  		 * @param radius Inner radius of a pie relative to the radius of the
  		 * plot.
  		 */
  		public void setInnerRadius(double radius) {
  			this.innerRadius = radius;
  		}
  
  		/**
  		 * Returns the width of gaps between the segments relative to the font
  		 * size.
  		 * @return Width of gaps between the segments relative to the font
  		 * size.
  		 */
  		public double getGap() {
  			return gap;
  		}
  
  		/**
  		 * Sets the width of gaps between the segments relative to the font
  		 * size.
  		 * @param gap Width of gaps between the segments relative to the font
  		 * size.
  		 */
  		public void setGap(double gap) {
  			this.gap = gap;
  		}
  
  		/**
  		 * 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 = -1783451355453643712L;
  
  				public void draw(DrawingContext context) {
  					PointRenderer renderer = PieSliceRenderer.this;
  
  					Row row = data.row;
  					if (shape == null) {
  						return;
  					}
  
  					Slice slice = plot.getSlice(
  						row.getSource(), row.getIndex());
  					if (slice == null) {
  						return;
  					}
  
  					// Paint slice
  					ColorMapper colorMapper = renderer.getColor();
  					Paint paint;
  					if (colorMapper instanceof ContinuousColorMapper) {
  						double sum = plot.getSum(row.getSource());
  						if (sum == 0.0) {
  							return;
  						}
  						double sliceStartRel = slice.start/sum;
  						double sliceEndRel = slice.end/sum;
  
  						double coloringRel = 0.0;
  						int rows = row.getSource().getRowCount();
  						if (rows > 1) {
  							double posRel = row.getIndex() / (double)(rows - 1);
  							double posRelInv = 1.0 - posRel;
  							coloringRel =
  								posRelInv*sliceStartRel + posRel*sliceEndRel;
  						}
  						paint = ((ContinuousColorMapper) colorMapper).get(coloringRel);
  					} else {
  						paint = colorMapper.get(row.getIndex());
  					}
  					GraphicsUtils.fillPaintedShape(
  						context.getGraphics(), shape, paint, null);
  				}
  			};
  		}
  
  		/**
  		 * 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) {
  			Row row = data.row;
  			int col = data.col;
  			Number valueObj = (Number) row.get(col);
  			if (!MathUtils.isCalculatable(valueObj) ||
  					valueObj.doubleValue() <= 0.0) {
  				return null;
  			}
  
  			Font font = getValueFont();
  			double fontSize = font.getSize2D();
  
  			PlotArea plotArea = plot.getPlotArea();
  			double plotAreaSize = Math.min(
  				plotArea.getWidth(), plotArea.getHeight())/2.0;
  			double radiusRel = plot.getRadius();
  			double radius = plotAreaSize*radiusRel;
  			double radiusRelOuter = getOuterRadius();
  			double radiusOuter = radius*radiusRelOuter;
  
  			// Construct slice
  			double sum = plot.getSum(row.getSource());
  			if (sum == 0.0) {
  				return null;
  			}
  			Slice slice = plot.getSlice(
  				row.getSource(), row.getIndex());
  			if (slice == null) {
  				return null;
  			}
  			double sliceStartRel = slice.start/sum;
  			double sliceEndRel = slice.end/sum;
  
  			double start = plot.getStart();
  
  			double sliceSpan = (sliceEndRel - sliceStartRel)*360.0;
  			double sliceStart;
  			if (plot.isClockwise()) {
  				sliceStart = start - sliceEndRel*360.0;
  			} else {
  				sliceStart = start + sliceStartRel*360.0;
  			}
  			start = MathUtils.normalizeDegrees(start);
  
  			Arc2D pieSlice = new Arc2D.Double(
  				-radiusOuter, -radiusOuter,
  				2.0*radiusOuter, 2.0*radiusOuter,
  				sliceStart, sliceSpan,
  				Arc2D.PIE
  			);
  			Area doughnutSlice = new Area(pieSlice);
  
  			double gap = getGap();
  			if (gap > 0.0) {
  				Stroke sliceStroke =
  					new BasicStroke((float) (gap*fontSize));
  				Area sliceContour =
  					new Area(sliceStroke.createStrokedShape(pieSlice));
  				doughnutSlice.subtract(sliceContour);
  			}
  
  			double radiusRelInner = getInnerRadius();
  			if (radiusRelInner > 0.0 && radiusRelInner < radiusRelOuter) {
  				double radiusInner = radius*radiusRelInner;
  				Ellipse2D inner = new Ellipse2D.Double(
  					-radiusInner, -radiusInner,
  					2.0*radiusInner, 2.0*radiusInner
  				);
  				Area hole = new Area(inner);
  				doughnutSlice.subtract(hole);
  			}
  
  			return doughnutSlice;
  		}
  
  		/**
  		 * Draws the specified value label for the specified shape.
  		 * @param context Environment used for drawing.
  		 * @param slice Pie slice to draw.
  		 * @param radius Radius of pie slice in view units (e.g. pixels).
  		 * @param row Data row containing the point.
  		 * @param col Index of the column that will be projected on the axis.
  		 */
  		protected void drawValueLabel(DrawingContext context, Slice slice,
  				double radius, Row row, int col) {
  			Comparable<?> value = row.get(col);
  
  			// Formatting
  			Format format = getValueFormat();
  			if ((format == null) && (value instanceof Number)) {
  				format = NumberFormat.getInstance();
  			}
  
  			// Text to display
  			String text = (format != null) ? format.format(value) : value.toString();
  
  			// Visual settings
  			ColorMapper colors = getValueColor();
  			Paint paint = colors.get(row.getIndex());
  			Font font = getValueFont();
  			double fontSize = font.getSize2D();
  
  			// Layout settings
  			Location location = getValueLocation();
  			double alignX = getValueAlignmentX();
  			double alignY = getValueAlignmentY();
  			double rotation = getValueRotation();
  			double distance = getValueDistance();
  			if (MathUtils.isCalculatable(distance)) {
  				distance *= fontSize;
  			} else {
  				distance = 0.0;
  			}
  
  			// Vertical layout
  			double radiusRelOuter = getOuterRadius();
  			double radiusRelInner = getInnerRadius();
  			double radiusOuter = radius*radiusRelOuter;
  			double radiusInner = radius*radiusRelInner;
  			double distanceV = distance;
  			double labelPosV;
  			if (location == Location.NORTH) {
  				labelPosV = radiusOuter + distanceV;
  			} else if (location == Location.SOUTH) {
  				labelPosV = Math.max(radiusInner - distanceV, 0);
  			} else {
  				double sliceHeight = radiusOuter - radiusInner;
  				if (2.0*distance >= sliceHeight) {
  					alignY = 0.5;
  					distanceV = 0.0;
  				}
  				labelPosV = radiusInner + distanceV +
  					alignY*(sliceHeight - 2.0*distanceV);
  			}
  
  			// Horizontal layout
  			double sum = plot.getSum(row.getSource());
  			if (sum == 0.0) {
  				return;
  			}
  			double sliceStartRel = slice.start/sum;
  			double sliceEndRel = slice.end/sum;
  			double circumference = 2.0*labelPosV*Math.PI;
  			double distanceRelH = distance/circumference;
  			double sliceWidthRel = sliceEndRel - sliceStartRel;
  			if (2.0*distanceRelH >= sliceWidthRel) {
  				alignX = 0.5;
  				distanceRelH = 0.0;
  			}
  			double labelPosRelH = sliceStartRel + distanceRelH +
  				alignX*(sliceWidthRel - 2.0*distanceRelH);
  
  			double start = plot.getStart();
  
  			double angleStart = Math.toRadians(-start);
  			double direction = 1.0;
  			if (!plot.isClockwise()) {
  				direction = -1.0;
  			}
  			double angle = angleStart + direction*labelPosRelH*2.0*Math.PI;
  			double dirX = Math.cos(angle);
  			double dirY = Math.sin(angle);
  
  			// Create a label with the settings
  			Label label = new Label(text);
  			label.setAlignmentX(1.0 - 0.5*dirX - 0.5);
  			label.setAlignmentY(0.5*dirY + 0.5);
  			label.setRotation(rotation);
  			label.setColor(paint);
  			label.setFont(font);
  
  			// Calculate label position
  			Dimension2D sizeLabel = label.getPreferredSize();
  			double anchorX = 0.5;
  			double anchorY = 0.5;
  			if (location == Location.NORTH || location == Location.SOUTH) {
  				anchorX = dirX*sizeLabel.getWidth()/2.0;
  				anchorY = dirY*sizeLabel.getHeight()/2.0;
  				if (location == Location.SOUTH) {
  					anchorX = -anchorX;
  					anchorY = -anchorY;
  				}
  			}
  
  			// Resize label component
  			double x = labelPosV*dirX + anchorX - sizeLabel.getWidth()/2.0;
  			double y = labelPosV*dirY + anchorY - sizeLabel.getHeight()/2.0;
  			double w = sizeLabel.getWidth();
  			double h = sizeLabel.getHeight();
  			label.setBounds(x, y, w, h);
  
  			label.draw(context);
  		}
  
  		@Override
  		public Drawable getValue(final PointData data, final Shape shape) {
  			Drawable drawable = new AbstractDrawable() {
  				/** Version id for serialization. */
  				private static final long serialVersionUID = 8389872806138135038L;
  
  				public void draw(DrawingContext context) {
  					PointRenderer renderer = PieSliceRenderer.this;
  
  					Row row = data.row;
  					if (shape == null) {
  						return;
  					}
  
  					Slice slice = plot.getSlice(
  						row.getSource(), row.getIndex());
  					if (slice == null) {
  						return;
  					}
  
  					PlotArea plotArea = plot.getPlotArea();
  					double plotAreaSize = Math.min(
  						plotArea.getWidth(), plotArea.getHeight())/2.0;
  					double radiusRel = plot.getRadius();
  					double radius = plotAreaSize*radiusRel;
  
  					if (renderer.isValueVisible()) {
  						int colValue = renderer.getValueColumn();
  						drawValueLabel(context, slice, radius, row, colValue);
  					}
  				}
  			};
  			return drawable;
  		}
  	}
  
  	/**
  	 * A legend implementation for pie plots that displays items for each data
  	 * value of a data source.
  	 */
  	public static class PiePlotLegend extends ValueLegend {
  		/** Version id for serialization. */
  		private static final long serialVersionUID = 309673490751330686L;
  
  		/** Plot that contains settings and renderers. */
  		private final PiePlot plot;
  
  		/**
  		 * Initializes a new instance with a specified plot.
  		 * @param plot Plot.
  		 */
  		public PiePlotLegend(PiePlot plot) {
  			this.plot = plot;
  		}
  
  		@Override
  		protected Iterable<Row> getEntries(DataSource source) {
  			Iterable<Row> slicesAndGaps = super.getEntries(source);
  			List<Row> slices = new LinkedList<Row>();
  			for (Row row : slicesAndGaps) {
  				if (!row.isColumnNumeric(0)) {
  					continue;
  				}
  				Number value = (Number) row.get(0);
  				boolean isGap = value.doubleValue() < 0.0;
  				if (!isGap) {
  					slices.add(row);
  				}
  			}
  			return slices;
  		}
  
  		/**
  		 * Returns a symbol for rendering a legend item.
  		 * @param row Data row.
  		 * @return A drawable object that can be used to display the symbol.
  		 */
  		public Drawable getSymbol(final Row row) {
  			return new AbstractSymbol(this) {
  				/** Version id for serialization. */
  				private static final long serialVersionUID = -5460249256507481057L;
  
  				/**
  				 * Draws the {@code Drawable} with the specified drawing context.
  				 * @param context Environment used for drawing
  				 */
  				public void draw(DrawingContext context) {
  					DataSource data = row.getSource();
  
  					Rectangle2D bounds = getBounds();
  
  					PointRenderer pointRenderer = plot.getPointRenderer(data);
  					Shape shape = new Rectangle2D.Double(
  						0.0, 0.0, bounds.getWidth(), bounds.getHeight());
  					Drawable drawable = null;
  					if (pointRenderer == null) {
  						return;
  					}
  
  					PointData pointData = new PointData(
  						Arrays.asList((Axis) null),
  						Arrays.asList((AxisRenderer) null),
  						row, 0);
  
  					drawable = pointRenderer.getPoint(pointData, shape);
  
  					Graphics2D graphics = context.getGraphics();
  					AffineTransform txOrig = graphics.getTransform();
  					graphics.translate(bounds.getX(), bounds.getY());
  					drawable.draw(context);
  					graphics.setTransform(txOrig);
  				}
  			};
  		}
  	}
  
  	/**
  	 * Initializes a new pie plot with the specified data source.
  	 * @param data Data to be displayed.
  	 */
  	public PiePlot(DataSource data) {
  		super();
  
  		center = new Point2D.Double(0.5, 0.5);
  		radius = 1.0;
  		start = 0.0;
  		clockwise = true;
  
  		pointRenderers = new HashMap<DataSource, PointRenderer>();
  		slices = new HashMap<DataSource, List<Slice>>();
  
  		setPlotArea(new PiePlotArea2D(this));
  		setLegend(new PiePlotLegend(this));
  
  		add(data);
  
  		createDefaultAxes();
  		createDefaultAxisRenderers();
  
  		dataUpdated(data);
  	}
  
  	@Override
  	protected void createDefaultAxes() {
  		// Create x axis and y axis by default
  		Axis axisPie = new Axis();
  		setAxis(AXIS_TANGENTIAL, axisPie);
  	}
  
  	@Override
  	public void autoscaleAxis(String axisName) {
  		if (!AXIS_TANGENTIAL.equals(axisName)) {
  			super.autoscaleAxis(axisName);
  			return;
  		}
  
  		List<DataSource> sources = getVisibleData();
  		if (sources.isEmpty()) {
  			return;
  		}
  
  		DataSource data = sources.get(0);
  		if (data.getRowCount() == 0) {
  			return;
  		}
  
  		double sum = getSum(data);
  		if (sum == 0.0) {
  			return;
  		}
  
  		Axis axis = getAxis(axisName);
  		if (axis == null || !axis.isAutoscaled()) {
  			return;
  		}
  		axis.setRange(0.0, sum);
  	}
  
  	@Override
  	protected void createDefaultAxisRenderers() {
  		// Create a linear renderer for the pie slices by default
  		AxisRenderer renderer = new LinearRenderer2D();
  		// Create a circle with radius 1.0 as shape for the axis
  		Shape shape = new Ellipse2D.Double(-1.0, -1.0, 2.0, 2.0);
  		renderer.setShape(shape);
  		// Don't show axis
  		renderer.setShapeVisible(false);
  
  		setAxisRenderer(AXIS_TANGENTIAL, renderer);
  	}
  
  	@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$
  		}
  		super.add(index, source, visible);
  		PointRenderer pointRendererDefault = new PieSliceRenderer(this);
  		setPointRenderer(source, pointRendererDefault);
  		setMapping(source, AXIS_TANGENTIAL);
  	}
  
  	/**
  	 * Returns the {@code PointRenderer} for the specified data source.
  	 * @param s Data source.
  	 * @return PointRenderer.
  	 */
  	public PointRenderer getPointRenderer(DataSource s) {
  		return pointRenderers.get(s);
  	}
  
  	/**
  	 * Sets the {@code PointRenderer} for a certain data source to the
  	 * specified value.
  	 * @param s Data source.
  	 * @param pointRenderer PointRenderer to be set.
  	 */
  	public void setPointRenderer(DataSource s, PointRenderer pointRenderer) {
  		this.pointRenderers.put(s, pointRenderer);
  	}
  
  	/**
  	 * Returns a navigator instance that can control the current object.
  	 * @return A navigator instance.
  	 */
  	public Navigator getNavigator() {
  		if (navigator == null) {
  			navigator = new PiePlotNavigator(this);
  		}
  		return navigator;
  	}
  
  	/**
  	 * Returns the sum of all absolute values from the specified data source up
  	 * to the row with the specified index. This is used to determine the
  	 * position of pie slices.
  	 * @param source Data source.
  	 * @param index Index of the row.
  	 * @return Sum of all absolute values from the specified data source up
  	 *         to the row with the specified index
  	 */
  	protected Slice getSlice(DataSource source, int index) {
  		if (index < 0) {
  			return null;
  		}
  		List<Slice> dataSlices;
  		synchronized (slices) {
  			if (!slices.containsKey(source)) {
  				createSlices(source);
  			}
  			dataSlices = slices.get(source);
  		}
  		if (dataSlices == null || index >= dataSlices.size()) {
  			return null;
  		}
  		return dataSlices.get(index);
  	}
  
  	/**
  	 * Returns the sum of all absolute values in the data column of a specified
  	 * data source.
  	 * @param source Data source.
  	 * @return Sum of all absolute values for the specified data source.
  	 */
  	protected double getSum(DataSource source) {
  		double sum = 0.0;
  		synchronized (source) {
  			Slice lastSlice = getSlice(source, source.getRowCount() - 1);
  			if (lastSlice != null) {
  				sum = lastSlice.end;
  			}
  		}
  		return sum;
  	}
  
  	/**
  	 * Creates the slice objects with start and end information for a specified
  	 * data source.
  	 * @param source Data source.
  	 */
  	private void createSlices(DataSource source) {
  		if (!isVisible(source)) {
  			return;
  		}
  		final int colIndex = 0;
  		Column col = source.getColumn(colIndex);
          List<Slice> dataSlices = new ArrayList<Slice>(col.size());
          slices.put(source, dataSlices);
  
          double start = 0.0;
          for (Comparable<?> cell : col) {
              Number numericCell = (Number) cell;
              double value = 0.0;
              if (MathUtils.isCalculatable(numericCell)) {
                  value = numericCell.doubleValue();
              }
              // abs() is required because negative values cause
              // "empty" slices
              double span = Math.abs(value);
              Slice slice = new Slice(start, start + span);
              dataSlices.add(slice);
              start += span;
          }
  	}
  
  	/**
  	 * Rebuilds cached information for a specified data source.
  	 * @param source Data source.
  	 */
  	protected void revalidate(DataSource source) {
  		slices.remove(source);
  		autoscaleAxes();
  	}
  
  	@Override
  	protected void dataChanged(DataSource source, DataChangeEvent... events) {
  		super.dataChanged(source, events);
  		revalidate(source);
  	};
  
  	/**
  	 * Custom deserialization method.
  	 * @param in Input stream.
  	 * @throws ClassNotFoundException if a serialized class doesn't exist anymore.
  	 * @throws IOException if there is an error while reading data from the
  	 *         input stream.
  	 */
  	private void readObject(ObjectInputStream in)
  			throws ClassNotFoundException, IOException {
  		// Default deserialization
  		in.defaultReadObject();
  		// Handle transient fields
  		slices = new HashMap<DataSource, List<Slice>>();
  
  		// Update caches
  		for (DataSource source : getData()) {
  			dataUpdated(source);
  		}
  	}
  
  	/**
  	 * Returns a point which defines the center of the pie. The coordinates
  	 * are relative to the plot area dimensions, i.e. 0.0 means left/top,
  	 * 0.5 means the center, and 1.0 means right/bottom.
  	 * @return Point which defines the center of the pie.
  	 */
  	public Point2D getCenter() {
  		return center;
  	}
  
  	/**
  	 * Sets the center of the pie. The coordinates must be relative to the plot
  	 * area dimensions, i.e. 0.0 means left/top, 0.5 means the center, and 1.0
  	 * means right/bottom.
  	 * @param center Point which defines the center of the pie.
  	 */
  	public void setCenter(Point2D center) {
  		this.center.setLocation(center);
  	}
  
  	/**
  	 * Returns the radius of the pie relative to the plot area size.
  	 * @return Radius of the pie relative to the plot area size.
  	 */
  	public double getRadius() {
  		return radius;
  	}
  
  	/**
  	 * Sets the radius of the pie relative to the plot area size.
  	 * @param radius Radius of the pie relative to the plot area size.
  	 */
  	public void setRadius(double radius) {
  		this.radius = radius;
  	}
  
  	/**
  	 * Returns the starting angle of the first segment. The angle is
  	 * counterclockwise.
  	 * @return Starting angle of the first segment in degrees.
  	 */
  	public double getStart() {
  		return start;
  	}
  
  	/**
  	 * Sets the starting angle of the first segment. The angle is always
  	 * applied counterclockwise.
  	 * @param start Starting angle of the first segment in degrees.
  	 */
  	public void setStart(double start) {
  		double startOld = this.start;
  
  		this.start = start;
  
  		AxisRenderer axisRenderer = getAxisRenderer(PiePlot.AXIS_TANGENTIAL);
  		if (axisRenderer != null) {
  			Shape shape = axisRenderer.getShape();
  			if (shape != null) {
  				double delta = Math.toRadians(startOld - start);
  				AffineTransform tx = AffineTransform.getRotateInstance(delta);
  				shape = tx.createTransformedShape(shape);
  				axisRenderer.setShape(shape);
  			}
  		}
  	}
  
  	/**
  	 * Returns whether the segments are in clockwise or counterclockwise order.
  	 * @return {@code true} if segments are in clockwise order,
  	 * otherwise {@code false}.
  	 */
  	public boolean isClockwise() {
  		return clockwise;
  	}
  
  	/**
  	 * Sets whether the segments will be in clockwise or counterclockwise order.
  	 * @param clockwise {@code true} if segments should be in clockwise order,
  	 * otherwise {@code false}.
  	 */
  	public void setClockwise(boolean clockwise) {
  		this.clockwise = clockwise;
  
  		AxisRenderer axisRenderer = getAxisRenderer(PiePlot.AXIS_TANGENTIAL);
  		if (axisRenderer != null) {
  			Shape shape = axisRenderer.getShape();
  			if (shape != null) {
  				shape = GeometryUtils.reverse(shape);
  				axisRenderer.setShape(shape);
  			}
  		}
  	}
  }
  