/*
 * 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 java.awt.Color;
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.Dimension2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;

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.graphics.Location;
import de.erichseifert.gral.plots.areas.AreaRenderer;
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.legends.AbstractLegend;
import de.erichseifert.gral.plots.legends.Legend;
import de.erichseifert.gral.plots.legends.ValueLegend;
import de.erichseifert.gral.plots.lines.LineRenderer;
import de.erichseifert.gral.plots.points.DefaultPointRenderer2D;
import de.erichseifert.gral.plots.points.PointData;
import de.erichseifert.gral.plots.points.PointRenderer;
import de.erichseifert.gral.util.GraphicsUtils;
import de.erichseifert.gral.util.MathUtils;
import de.erichseifert.gral.util.PointND;
import de.erichseifert.gral.util.SerializationUtils;


/**
 * 

Class that displays data in a bar plot.

*

To create a new {@code BarPlot} simply create a new instance * using one or more data sources. Example:

*
 * DataTable data = new DataTable(Integer.class, Double.class);
 * data.add(2010, -5.00);
 * data.add(2011,  3.25);
 * data.add(2012, -0.50);
 * data.add(2012,  4.00);
 *
 * BarPlot plot = new BarPlot(data);
 * 
*/ public class BarPlot extends XYPlot { /** Version id for serialization. */ private static final long serialVersionUID = 3177733647455649147L; /** Relative width of the bars. 1.0 means the bars touch each other * without gap. */ private double barWidth; /** Minimal height of the bars in pixels. */ private double barHeightMin; /** Decides whether the bars should be filled as a whole, or each bar on * its own. This can e.g. be important for gradients. */ private boolean paintAllBars; /** * Class that renders a bar in a bar plot. */ public static class BarRenderer extends DefaultPointRenderer2D { /** Version id for serialization. */ private static final long serialVersionUID = 2183638342305398522L; /** Plot that contains settings and renderers. */ private final BarPlot plot; /** Stroke to draw the border of the bar. */ // Custom serialization will be done with a wrapper object private transient Stroke borderStroke; /** Color to fill the border of the bar. */ private Paint borderColor; /** * Constructor that creates a new instance and initializes it with a * plot as data provider. * @param plot The associated plot. */ public BarRenderer(BarPlot plot) { this.plot = plot; setValueLocation(Location.NORTH); borderStroke = null; borderColor = Color.BLACK; } /** * 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(); // Custom deserialization borderStroke = (Stroke) SerializationUtils.unwrap( (Serializable) in.readObject()); } /** * Custom serialization method. * @param out Output stream. * @throws ClassNotFoundException if a serialized class doesn't exist. * @throws IOException if there is an error while writing data to the * output stream. */ private void writeObject(ObjectOutputStream out) throws ClassNotFoundException, IOException { // Default serialization out.defaultWriteObject(); // Custom serialization out.writeObject(SerializationUtils.wrap(borderStroke)); } /** * Returns the stroke used to paint the outline of the point shape. * @return Stroke used to paint the outline of the point shape. */ public Stroke getBorderStroke() { return borderStroke; } /** * Sets the stroke used to paint the outline of the point shape. * @param stroke Stroke used to paint the outline of the point shape. */ public void setBorderStroke(Stroke stroke) { this.borderStroke = stroke; } /** * Returns the paint which is used to fill the point shape. * @return Paint which is used to fill the point shape. */ public Paint getBorderColor() { return borderColor; } /** * Sets the paint which will be used to fill the point shape. * @param color Paint which will be used to fill the point shape. */ public void setBorderColor(Paint color) { this.borderColor = color; } @Override public Drawable getPoint(final PointData data, final Shape shape) { return new AbstractDrawable() { /** Version id for serialization. */ private static final long serialVersionUID = -3145112034673683520L; public void draw(DrawingContext context) { BarRenderer renderer = BarRenderer.this; Rectangle2D paintBoundaries = null; Graphics2D graphics = context.getGraphics(); ColorMapper colors = renderer.getColor(); Paint paint = colors.get(data.index); if (plot.isPaintAllBars()) { AffineTransform txOld = graphics.getTransform(); Rectangle2D shapeBounds = shape.getBounds2D(); paintBoundaries = new Rectangle2D.Double();//plot.getPlotArea().getBounds(); paintBoundaries = new Rectangle2D.Double( shapeBounds.getX(), paintBoundaries.getY() - txOld.getTranslateY(), shapeBounds.getWidth(), paintBoundaries.getHeight() ); } GraphicsUtils.fillPaintedShape( graphics, shape, paint, paintBoundaries); Stroke stroke = renderer.getBorderStroke(); Paint strokePaint = renderer.getBorderColor(); if (stroke != null && strokePaint != null) { GraphicsUtils.drawPaintedShape( graphics, shape, strokePaint, null, stroke); } } }; } /** * 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. */ @Override public Shape getPointShape(PointData data) { int colX = 0; int colY = 1; 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; if (!row.isColumnNumeric(colX) || !row.isColumnNumeric(colY)) { return null; } double valueX = ((Number) row.get(colX)).doubleValue(); double valueY = ((Number) row.get(colY)).doubleValue(); double axisYOrigin = 0.0; double barWidthRel = plot.getBarWidth(); barWidthRel = Math.max(barWidthRel, 0.0); double barAlign = 0.5; double barXMin = axisXRenderer .getPosition(axisX, valueX - barWidthRel*barAlign, true, false) .get(PointND.X); double barXMax = axisXRenderer .getPosition(axisX, valueX + barWidthRel*barAlign, true, false) .get(PointND.X); double barYVal = axisYRenderer.getPosition( axisY, valueY, true, false).get(PointND.Y); double barYOrigin = axisYRenderer.getPosition( axisY, axisYOrigin, true, false).get(PointND.Y); double barYMin = Math.min(barYVal, barYOrigin); double barYMax = Math.max(barYVal, barYOrigin); double barWidth = Math.abs(barXMax - barXMin); double barHeight = Math.abs(barYMax - barYMin); // position of the bar's left edge in screen coordinates double barX = axisXRenderer.getPosition( axisX, valueX, true, false).get(PointND.X); // position of the bar's upper edge in screen coordinates // (the origin of the screen y axis is at the top) boolean barAboveAxis = barYMax == barYOrigin; double barY = barAboveAxis ? 0.0 : -barHeight; double barHeightMin = plot.getBarHeightMin(); if (MathUtils.isCalculatable(barHeightMin) && barHeightMin > 0.0 && barHeight < barHeightMin) { if (barAboveAxis) { barY += -barHeightMin + barHeight; } barHeight = barHeightMin; } Shape shape = getBarShape( barXMin - barX, barY, barWidth, barHeight); return shape; } /** * Returns the shape for a bar. The default shape is defined in the * settings, but more complex shapes may be implemented by overriding * this method. * @param x Distance from the left in view units (e.g. pixels). * @param y Distance from the top in view units (e.g. pixels). * @param width Width of the shape in view units (e.g. pixels). * @param height Height of the shape in view units (e.g. pixels). * @return A geometric shape for displaying a bar in bar plot. */ protected Shape getBarShape(double x, double y, double width, double height) { Shape shape = getShape(); Rectangle2D shapeBounds = shape.getBounds2D(); AffineTransform tx = new AffineTransform(); tx.translate(x, y); tx.scale(width/shapeBounds.getWidth(), height/shapeBounds.getHeight()); tx.translate(-shapeBounds.getMinX(), -shapeBounds.getMinY()); Shape shapeTransformed = tx.createTransformedShape(shape); return shapeTransformed; } /** * 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. */ @Override public Drawable getValue(final PointData data, final Shape shape) { Drawable drawable = new AbstractDrawable() { /** Version id for serialization. */ private static final long serialVersionUID = -1133369168849171793L; public void draw(DrawingContext context) { PointRenderer renderer = BarRenderer.this; Row row = data.row; if (renderer.isValueVisible()) { int colValue = renderer.getValueColumn(); drawValueLabel(context, shape, row, data.index, colValue); } } }; return drawable; } } /** * A legend implementation for bar plots that displays all values of the * data source as items. */ public static class BarPlotLegend extends ValueLegend { /** Version id for serialization. */ private static final long serialVersionUID = 4752278896167602641L; /** Plot that contains settings and renderers. */ private final BarPlot plot; /** * Constructor that initializes the instance with a plot acting as a * provider for settings and renderers. * @param plot Plot. */ public BarPlotLegend(BarPlot plot) { this.plot = plot; } @Override protected Drawable getSymbol(final Row row) { List pointRenderers = plot.getPointRenderers(row.getSource()); BarRenderer barRenderer = (BarRenderer) pointRenderers.get(0); return new BarPlot.LegendSymbol(row, barRenderer, plot.getFont(), plot.getLegend().getSymbolSize()); } } private static class LegendSymbol extends AbstractLegend.AbstractSymbol { private final Row row; private final BarRenderer barRenderer; public LegendSymbol(Row row, BarRenderer barRenderer, Font font, Dimension2D symbolSize) { super(font, symbolSize); this.row = row; this.barRenderer = barRenderer; } @Override public void draw(DrawingContext context) { double width = getPreferredSize().getWidth(); double height = getPreferredSize().getHeight(); Shape shape = barRenderer.getBarShape(0.0, 0.0, width, height); Graphics2D graphics = context.getGraphics(); AffineTransform txOrig = graphics.getTransform(); graphics.translate(getX(), getY()); GraphicsUtils.fillPaintedShape( context.getGraphics(), shape, barRenderer.getColor().get(0), null); GraphicsUtils.drawPaintedShape( context.getGraphics(), shape, barRenderer.getBorderColor(), null, barRenderer.getBorderStroke()); graphics.setTransform(txOrig); } } /** * Creates a new instance and initializes it with the specified * data sources. * @param data Data to be displayed. */ public BarPlot(DataSource... data) { super(data); ((XYPlotArea2D) getPlotArea()).setMajorGridX(false); barWidth = 1.0; barHeightMin = 0.0; paintAllBars = false; Legend legend = new BarPlotLegend(this); setLegend(legend); autoscaleAxes(); } @Override public void autoscaleAxis(String axisName) { if (!AXIS_X.equals(axisName) && !AXIS_Y.equals(axisName)) { super.autoscaleAxis(axisName); } Axis axis = getAxis(axisName); if (axis == null || !axis.isAutoscaled()) { return; } List sources = getData(); if (sources.isEmpty()) { return; } int rowCount = 0; for (DataSource data : sources) { rowCount = Math.max(rowCount, data.getRowCount()); } if (rowCount == 0) { return; } double min = getAxisMin(axisName); double max = getAxisMax(axisName); double spacing = 0.0; if (AXIS_X.equals(axisName)) { // Add margin double barWidth = getBarWidth(); double margin = barWidth*(max - min)/rowCount; spacing = margin/2.0; } else { // Make sure 0 is always visible for y axis min = Math.min(min, 0.0); max = Math.max(max, 0.0); } axis.setRange(min - spacing, max + spacing); } @Override public void add(int index, DataSource source, boolean visible) { super.add(index, source, visible); // Assign default renderers PointRenderer pointRendererDefault = new BarRenderer(this); LineRenderer lineRendererDefault = null; AreaRenderer areaRendererDefault = null; // FIXME: Overwrites possible present point and line renderers setPointRenderers(source, pointRendererDefault); setLineRenderers(source, lineRendererDefault); setAreaRenderers(source, areaRendererDefault); } /** * Returns the width of the bars in axis coordinates. * @return Width of the bars in axis coordinates. */ public double getBarWidth() { return barWidth; } /** * Sets the width of the bars in axis coordinates. * @param barWidth Width of the bars in axis coordinates. */ public void setBarWidth(double barWidth) { this.barWidth = barWidth; } /** * Returns the minimum height of the bars in view units * (e.g. pixels on screen). * @return Minimum height of the bars in view units. */ public double getBarHeightMin() { return barHeightMin; } /** * Sets the minimum height of the bars in view units * (e.g. pixels on screen). * @param barHeightMin Minimum height of the bars in view units. */ public void setBarHeightMin(double barHeightMin) { this.barHeightMin = barHeightMin; } /** * Returns whether all bars are filled as a whole, or if each bar is filled * independently. * @return {@code true} if all bars are filled as a whole, or * {@code false} if each bar is filled independently. */ public boolean isPaintAllBars() { return paintAllBars; } /** * Sets whether all bars will be filled as a whole, or if each bar will be * filled independently. * @param paintAllBars {@code true} to fill all bars as a whole, or * {@code false} to fill each bar independently. */ public void setPaintAllBars(boolean paintAllBars) { this.paintAllBars = paintAllBars; } }

Ads help maintain this website.