/*
 * GRAL: GRAphing Library for Java(R)
 *
 * (C) Copyright 2009-2016 Erich Seifert ,
 * Michael Seifert 
 *
 * 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 .
 */
package de.erichseifert.gral.plots;

import static java.util.Arrays.asList;

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.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import de.erichseifert.gral.data.AbstractDataSource;
import de.erichseifert.gral.data.Column;
import de.erichseifert.gral.data.DataChangeEvent;
import de.erichseifert.gral.data.DataListener;
import de.erichseifert.gral.data.DataSource;
import de.erichseifert.gral.data.Row;
import de.erichseifert.gral.data.filters.Accumulation;
import de.erichseifert.gral.graphics.AbstractDrawable;
import de.erichseifert.gral.graphics.Drawable;
import de.erichseifert.gral.graphics.DrawingContext;
import de.erichseifert.gral.graphics.Insets2D;
import de.erichseifert.gral.graphics.Label;
import de.erichseifert.gral.graphics.Location;
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.AbstractLegend;
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.MathUtils;
import de.erichseifert.gral.util.PointND;


/**
 * 

Class that displays data as segments of a pie plot. Empty segments are * displayed for negative values.

*

To create a new {@code PiePlot} simply create a new instance using * a data source. Example:

*
 * 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);
 * 
*/ 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 pointRenderers; /** 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 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 getCenter() { Point2D center = plot.getCenter(); return new PointND(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 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 deltas) { PlotArea plotArea = plot.getPlotArea(); PointND center = (PointND) 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) { final double fontSize = getBaseFont().getSize2D(); // 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; } // TODO Use property for column index 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 axes = asList(axis); List axisRenderers = asList(axisRenderer); // Draw graphics for (int rowIndex = 0; rowIndex < s.getRowCount(); rowIndex++) { Row row = s.getRow(rowIndex); PointData pointData = new PointData( axes, axisRenderers, row, row.getIndex(), 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, row.getIndex(), 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; /** Whether the slice is visible. */ public final boolean visible; /** * Initializes a new slice with start and end value. * @param start Value where the slice starts. * @param end Value where the slice ends. * @param visible Visibility of the slice. */ public Slice(double start, double end, boolean visible) { this.start = start; this.end = end; this.visible = visible; } } /** * 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; } private Slice getSlice(PointData pointData) { double sliceStart = (Double) pointData.row.get(0); double sliceEnd = (Double) pointData.row.get(1); boolean sliceVisible = (Boolean) pointData.row.get(2); return new Slice(sliceStart, sliceEnd, sliceVisible); } /** * 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; } @Override 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 = getSlice(data); if (!slice.visible) { 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 = data.index / (double)(rows - 1); double posRelInv = 1.0 - posRel; coloringRel = posRelInv*sliceStartRel + posRel*sliceEndRel; } paint = ((ContinuousColorMapper) colorMapper).get(coloringRel); } else { paint = colorMapper.get(data.index); } 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) { Slice slice = getSlice(data); if (!slice.visible) { 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 Row row = data.row; double sum = plot.getSum(row.getSource()); if (sum == 0.0) { 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. */ protected void drawValueLabel(DrawingContext context, Slice slice, double radius, Row row, int rowIndex) { Comparable value = slice.end - slice.start; // 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(rowIndex); 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 = getSlice(data); if (!slice.visible) { 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()) { drawValueLabel(context, slice, radius, row, data.index); } } }; 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 getEntries(DataSource source) { Iterable slicesAndGaps = super.getEntries(source); List slices = new LinkedList(); for (Row row : slicesAndGaps) { if (!row.isColumnNumeric(0)) { continue; } boolean isVisible = (Boolean) row.get(2); if (isVisible) { slices.add(row); } } return slices; } @Override protected Drawable getSymbol(final Row row) { return new LegendSymbol(row, plot.getPointRenderer(row.getSource()), plot.getFont(), plot.getLegend().getSymbolSize()); } @Override protected String getLabel(Row row) { Number sliceStart = (Number) row.get(0); Number sliceEnd = (Number) row.get(1); Number sliceWidth = sliceEnd.doubleValue() - sliceStart.doubleValue(); Format format = getLabelFormat(); if ((format == null)) { format = NumberFormat.getInstance(); } return format.format(sliceWidth); } } private static class LegendSymbol extends AbstractLegend.AbstractSymbol { private final Row row; private final PointRenderer pointRenderer; public LegendSymbol(Row row, PointRenderer pointRenderer, Font font, Dimension2D symbolSize) { super(font, symbolSize); this.row = row; this.pointRenderer = pointRenderer; } @Override public void draw(DrawingContext context) { Rectangle2D bounds = getBounds(); Shape shape = new Rectangle2D.Double( 0.0, 0.0, bounds.getWidth(), bounds.getHeight()); PointData pointData = new PointData( asList((Axis) null), asList((AxisRenderer) null), row, row.getIndex(), 0); Drawable 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(); 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 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 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; synchronized (source) { sum = (Double) source.get(1, source.getRowCount() - 1); } return sum; } private static class PieData extends AbstractDataSource { private final DataSource data; public PieData(DataSource data) { this.data = data; data.addDataListener(new DataListener() { @Override public void dataAdded(DataSource source, DataChangeEvent... events) { notifyDataAdded(events); } @Override public void dataUpdated(DataSource source, DataChangeEvent... events) { notifyDataUpdated(events); } @Override public void dataRemoved(DataSource source, DataChangeEvent... events) { notifyDataRemoved(events); } }); setColumnTypes(getColumnTypesFor(data).toArray(new Class[] {})); } private List>> getColumnTypesFor(DataSource data) { List>> columnTypes = new LinkedList>>(); for (int colIndex = 0; colIndex < data.getColumnCount(); colIndex++) { Column column = data.getColumn(colIndex); if (column.isNumeric()) { columnTypes.add(Double.class); columnTypes.add(Double.class); columnTypes.add(Boolean.class); } else { columnTypes.add(column.getType()); } } return columnTypes; } @Override public Comparable get(int col, int row) { Iterable accumulatedColumnData = new Accumulation(data.getColumn(0)); if (col == 0) { if (row == 0) { return 0.0; } return get(accumulatedColumnData, row - 1); } else if (col == 1) { return get(accumulatedColumnData, row); } else if (col == 2) { return ((Number) data.get(0, row)).doubleValue() > 0.0; } return null; } @Override public int getRowCount() { return data.getRowCount(); } private static T get(Iterable iterable, int index) { T element = null; int elementIndex = 0; for (T e : iterable) { if (elementIndex == index) { element = e; break; } elementIndex++; } return element; } } public static DataSource createPieData(DataSource data) { return new PieData(data); } @Override protected void dataChanged(DataSource source, DataChangeEvent... events) { super.dataChanged(source, events); autoscaleAxes(); } /** * 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(); // 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); } } } }

Ads help maintain this website.