/**
* Copyright (c) 2012, Lindsay Bradford and other Contributors.
* All rights reserved.
*
* This program and the accompanying materials are made available
* under the terms of the BSD 3-Clause licence which accompanies
* this distribution, and is available at
* http://opensource.org/licenses/BSD-3-Clause
*/
package blacksmyth.personalfinancier.view.budget;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.LinearGradientPaint;
import java.awt.Shape;
import java.util.Observable;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import de.erichseifert.gral.data.DataTable;
import de.erichseifert.gral.graphics.Drawable;
import de.erichseifert.gral.plots.PiePlot;
import de.erichseifert.gral.plots.PiePlot.PieSliceRenderer;
import de.erichseifert.gral.plots.points.PointData;
import de.erichseifert.gral.ui.InteractivePanel;
import de.erichseifert.gral.util.Insets2D;
import de.erichseifert.gral.util.Location;
import blacksmyth.personalfinancier.control.budget.IBudgetObserver;
import blacksmyth.personalfinancier.model.budget.BudgetModel;
/**
* An abstract Pie Chart that summarises and displays budget model data.
* subclasses need only implement the abstract methods
* {@see #createTitleText()} and {@see #createPiePlotData()} to get a
* completely functional Budget Pie Chart.
* @author linds
*
*/
public abstract class AbstractBudgetPieChart extends JPanel implements IBudgetObserver {
private static final int IS_NEGATIVE_COLUMN = 1;
private static final int LABEL_COLUMN = 2;
private BudgetModel model;
public AbstractBudgetPieChart(BudgetModel model) {
super(new BorderLayout());
this.setBorder(new EmptyBorder(5,5,5,5));
this.setBackground(Color.WHITE);
setModel(model);
}
private void setModel(BudgetModel model) {
this.model = model;
model.addObserver(this);
}
protected BudgetModel getModel() {
return this.model;
}
@Override
public void update(Observable arg0, Object arg1) {
forceUpdate();
}
private void forceUpdate() {
displayPiePlot(
createPiePlot(
createTitleText(),
createPiePlotData()
)
);
}
abstract protected String createTitleText();
abstract protected DataTable createPiePlotData();
protected void displayPiePlot(PiePlot plot) {
this.removeAll(); // destroy old PiePlot
this.add(
new InteractivePanel(plot),
BorderLayout.CENTER
);
// this.validate(); // need to force a redraw
}
private PiePlot createPiePlot(String title, DataTable cashFlowData) {
PiePlot plot = new PiePlot(cashFlowData);
configurePiePlot(plot, title);
plot.setPointRenderer(
cashFlowData,
new DualSliceRenderer(
plot,
Color.BLACK, // positive render colour
Color.RED // negative render colour
)
);
return plot;
}
private void configurePiePlot(PiePlot plot, String title) {
//TODO: email info@erichseifert.de asking about small slices.
plot.getTitle().setText(title);
plot.getPlotArea().setBorderStroke(null);
plot.setRadius(0.6);
plot.setStart(180); //black on top
plot.setInsets(new Insets2D.Double(1.0, 1.0, 1.0, 1.0));
}
/**
* A PieSliceRenderer that delegates rendering to a positive or a
* negative PieSliceRenderer based on whether the data point to
* render is a positive or negative number.
* @author linds
*
*/
private class DualSliceRenderer extends PieSliceRenderer {
private PiePlot plot;
private PieSliceRenderer positiveSliceRenderer;
private PieSliceRenderer negativeSliceRenderer;
public DualSliceRenderer(PiePlot plot, Color positiveRenderColor,
Color negativeRenderColor) {
super(plot);
this.plot = plot;
this.positiveSliceRenderer = createPieSliceRenderer(positiveRenderColor);
this.negativeSliceRenderer = createPieSliceRenderer(negativeRenderColor);
}
/**
* Utility function that creates a number of PieSliceRendeer instances
* configured to differ only in rendering a single colour.
* @param renderer
* @param color
*/
private PieSliceRenderer createPieSliceRenderer(Color color) {
PieSliceRenderer renderer = new PieSliceRenderer(plot);
renderer.setGap(0.1);
renderer.setColor(
new LinearGradientPaint(
0f,0f, 0f,1f,
new float[] { 0.0f, 1.0f },
new Color[] { color, Color.GRAY }
)
);
renderer.setValueVisible(true);
renderer.setValueLocation(Location.NORTH);
renderer.setValueDistance(1.2);
renderer.setValueColumn(LABEL_COLUMN);
renderer.setValueColor(color);
renderer.setValueFont(Font.decode(null).deriveFont(Font.BOLD));
return renderer;
}
@Override
public Drawable getPoint(PointData data, Shape shape) {
if (valueIsNegative(data)) {
return negativeSliceRenderer.getPoint(data, shape);
}
return positiveSliceRenderer.getPoint(data, shape);
}
@Override
public Shape getPointShape(PointData data) {
if (valueIsNegative(data)) {
return negativeSliceRenderer.getPointShape(data);
}
return positiveSliceRenderer.getPointShape(data);
}
@Override
public Drawable getValue(PointData data,
java.awt.Shape shape) {
if (valueIsNegative(data)) {
return negativeSliceRenderer.getValue(data, shape);
}
return positiveSliceRenderer.getValue(data, shape);
}
/**
* Interrogates the current plot's data value
* for the given <tt>data</tt> entry. If the
* value is negative, returns <tt>true</tt>.
* @param data
* @return
*/
private boolean valueIsNegative(PointData data) {
return (Boolean) plot.getData().get(0).get(
IS_NEGATIVE_COLUMN,
data.row.getIndex()
);
}
}
}