/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program 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 2.1 of the License, or (at your option) any later version.
*
* This program 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 this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2003, by :
* Corporate:
* Astrium SAS
* EADS CRC
* Individual:
* Nicolas Brodu
*
* $Id: PieChartShape.java,v 1.9 2008/09/29 10:27:46 ogor Exp $
*
* Changes
* -------
* 05-Nov-2003 : Initial version (NB);
*
*/
package jsynoptic.plugins.jfreechart;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Paint;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ResourceBundle;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.undo.CompoundEdit;
import jsynoptic.base.ContextualActionProvider;
import jsynoptic.ui.JSynoptic;
import jsynoptic.ui.LongAction;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.Pie3DPlot;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.ui.ChartPropertyEditPanel;
import simtools.data.DataInfo;
import simtools.data.DataSource;
import simtools.data.DataSourceCollection;
import simtools.data.DataSourceListener;
import simtools.data.DataSourcePool;
import simtools.data.EndNotificationListener;
import simtools.shapes.AbstractShape;
import simtools.ui.ColorMapper;
import simtools.ui.DynamicColorChooser;
import simtools.ui.GenericMapper;
import simtools.ui.MapperListener;
import simtools.ui.NumberField;
import simtools.ui.ResourceFinder;
import simtools.ui.TextMapper;
/**
* A shape to manage JFreeChart Bar Charts
*/
public class PieChartShape extends ChartShape implements ContextualActionProvider, DataSourceListener, EndNotificationListener, MapperListener {
static final long serialVersionUID = 7031896621461605415L;
public static ResourceBundle resources = ResourceFinder.get(PieChartShape.class, StandardPlotShape.resources);
protected Vector mappers;
protected transient Vector mapperSources;
protected transient boolean dirty = false;
public PieChartShape(JFreeChart chart) {
this(chart,0,0,300,300);
}
public PieChartShape(JFreeChart chart, int ox, int oy, int width, int height) {
super(chart, ox, oy, width, height);
PiePlot plot = (PiePlot)chart.getPlot();
mapperSources = new Vector();
mappers = new Vector();
}
/* (non-Javadoc)
* @see jsynoptic.base.ContextualActionProvider#getActions(double, double, java.lang.Object, int)
*/
public String[] getActions(double x, double y, Object o, int context) {
if (context==MOUSE_OVER_CONTEXT) {
return null;
}
if (context==MOUSE_OUT_CONTEXT) {
return null;
}
if (context==MOUSE_PRESSED_CONTEXT) {
return null;
}
Vector v = new Vector();
v.add(resources.getString("Properties..."));
if (o instanceof DataSource) {
v.add(resources.getString("AddSource"));
}
else if (o instanceof DataSourceCollection) {
v.add(resources.getString("SetSources"));
v.add(resources.getString("AddSources"));
}
return (String[])v.toArray(new String[v.size()]);
}
/* (non-Javadoc)
* @see jsynoptic.base.ContextualActionProvider#doAction(double, double, java.lang.Object, java.lang.String)
*/
public boolean doAction(double x, double y, Object o, String action, CompoundEdit undoableEdit) {
if (action.equals(resources.getString("Properties..."))) {
new LongAction(LongAction.LONG_ACTION_SHAPE, null, this) {
protected void doAction() {
ChartPropertyEditPanel panel = new ChartPropertyEditPanel(chart);
JTabbedPane tab = findTab(panel);
PlotPanel plotPanel = null;
if (tab!=null) tab.add(plotPanel = new PlotPanel(),0);
int result = JOptionPane.showConfirmDialog(null, panel,
resources.getString("ChartProperties"), JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE);
if (result == JOptionPane.OK_OPTION) {
panel.updateChartProperties(chart);
if (plotPanel!=null) plotPanel.updateChartProperties();
}
notifyChange();
}
}.start();
return true;
}
if (action.equals(resources.getString("AddSource"))) {
try {
PiePlot plot = (PiePlot)chart.getPlot();
SourcePieDataset dst = (SourcePieDataset)plot.getDataset();
dst.addSource((DataSource)o);
mappers.add(null); mapperSources.add(null); // no mapper by default
notifyChange();
} catch (ClassCastException cce) {
JSynoptic.setStatus(resources.getString("ErrorWhileSettingDataSource"));
}
}
if (action.equals(resources.getString("AddSources"))) {
try {
PiePlot plot = (PiePlot)chart.getPlot();
SourcePieDataset dst = (SourcePieDataset)plot.getDataset();
dst.addDataSourceCollection((DataSourceCollection)o);
for (int i=0; i<((DataSourceCollection)o).size(); ++i) {
mappers.add(null); mapperSources.add(null); // no mapper by default
}
notifyChange();
} catch (ClassCastException cce) {
JSynoptic.setStatus(resources.getString("ErrorWhileSettingDataSourceCollection"));
}
}
if (action.equals(resources.getString("SetSources"))) {
try {
PiePlot plot = (PiePlot)chart.getPlot();
SourcePieDataset dst = (SourcePieDataset)plot.getDataset();
dst.setDataSourceCollection((DataSourceCollection)o);
chart.setTitle(dst.getName());
for (int i=0; i<mappers.size(); ++i) {
DataSource ds = (DataSource)mapperSources.get(i);
if (ds!=null) ds.removeListener(this);
ColorMapper cm = (ColorMapper)mappers.get(i);
if (cm!=null) cm.removeListener(this);
}
for (int i=0; i<((DataSourceCollection)o).size(); ++i) {
mappers.add(null); mapperSources.add(null); // no mapper by default
}
notifyChange();
} catch (ClassCastException cce) {
JSynoptic.setStatus(resources.getString("ErrorWhileSettingDataSourceCollection"));
}
}
return true;
}
/* (non-Javadoc)
* @see jsynoptic.base.ContextualActionProvider#canDoAction(double, double, java.lang.Object, java.lang.String, int)
*/
public boolean canDoAction(double x, double y, Object o, String action, int context) {
return true;
}
static protected class CbxEntry {
public int datasetIndex;
public String name;
public Color color;
public DataSource source;
public ColorMapper mapper;
public double ratio;
public CbxEntry(int datasetIndex, String name, Color c, DataSource ds, ColorMapper cm, double ratio) {
this.name = name;
this.datasetIndex=datasetIndex;
color = c;
source = ds;
mapper = cm;
this.ratio = ratio;
}
public String toString() {
return name;
}
}
public class PlotPanel extends JPanel {
protected JTextField tfTitle;
protected JComboBox pcbxcurves, cbxmapper;
protected JButton pcurvecolor, pcurvedelete, editCategories, beditmapper, bnewmapper, bdelmapper;
protected JTextField pcbxeditortf;
protected boolean peditLocked;
protected CbxEntry pactiveEntry;
protected NumberField tfRatio;
protected TextMapper mapper;
public PlotPanel() {
setLayout(new BorderLayout());
JPanel panel;
Box section;
Box content = Box.createVerticalBox();
setName(resources.getString("PlotProperties"));
PiePlot plot = (PiePlot)chart.getPlot();
SourcePieDataset dst = (SourcePieDataset)plot.getDataset();
// Create the title section
section=Box.createHorizontalBox();
section.add(new JLabel(resources.getString("PlotTitle:")));
section.add(tfTitle = new JTextField());
tfTitle.setText(chart.getTitle().getText());
content.add(section);
section=Box.createVerticalBox();
section.setBorder(BorderFactory.createTitledBorder(resources.getString("Series")));
Box box = Box.createHorizontalBox();
box.add(pcbxcurves = new JComboBox());
box.add(pcurvecolor = new JButton(" "));
box.add(pcurvedelete = new JButton(resources.getString("Delete")));
section.add(box);
box = Box.createHorizontalBox();
box.add(new JLabel(resources.getString("ExplodedRatio(>=1)")));
box.add(tfRatio = new NumberField(1.0));
// So long as this does not work for 3D pies, do not show it
if (!(plot instanceof Pie3DPlot)) section.add(box);
content.add(section);
pcbxcurves.setEditable(true);
Color noCurveColor = pcurvecolor.getBackground();
pcurvecolor.setFocusPainted(false);
int n = dst.getItemCount();
double factor = 1.0 / plot.getRadius() - 1.0; // may be null
// Now initialize entries
for (int i=0; i<n; ++i) {
DataSource ds = (DataSource)mapperSources.get(i);
if (ds==null) ds = dst.getSource(i); // take the datasource associated with this series by default
pcbxcurves.addItem(new CbxEntry(
i,
dst.getName(i),
(Color)plot.getSectionPaint(i),
ds,
(ColorMapper)mappers.get(i),
plot.getExplodePercent(i) * factor + 1.0
));
}
pcbxeditortf = (JTextField)pcbxcurves.getEditor().getEditorComponent();
pupdateForEntry(pcbxcurves.getSelectedItem());
pcbxcurves.addPopupMenuListener(new PopupMenuListener() {
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
peditLocked = true;
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
peditLocked = false;
}
public void popupMenuCanceled(PopupMenuEvent e) {
peditLocked = false;
}
});
pcbxcurves.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange()==ItemEvent.DESELECTED) pactiveEntry = null;
else pupdateForEntry(e.getItem());
}
});
pcbxeditortf.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
updateName();
}
public void removeUpdate(DocumentEvent e) {
updateName();
}
public void changedUpdate(DocumentEvent e) {
updateName();
}
public void updateName() {
if ((peditLocked) || (pactiveEntry == null)) return;
pactiveEntry.name = pcbxeditortf.getText();
}
});
tfRatio.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
updateValue();
}
public void removeUpdate(DocumentEvent e) {
updateValue();
}
public void changedUpdate(DocumentEvent e) {
updateValue();
}
public void updateValue() {
if ((peditLocked) || (pactiveEntry == null)) return;
try {
pactiveEntry.ratio = Double.parseDouble(tfRatio.getText());
if (pactiveEntry.ratio<1.0) pactiveEntry.ratio = 1.0;
} catch (NumberFormatException nfe) {
pactiveEntry.ratio = 1.0;
}
}
});
pcurvecolor.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (pactiveEntry==null) return;
DynamicColorChooser dialog = new DynamicColorChooser(
null, resources.getString("ChooseAColor"),null ,pactiveEntry.color,pactiveEntry.source,pactiveEntry.mapper);
dialog.pack();
dialog.setVisible(true);
if (dialog.isOk()){
pactiveEntry.color = dialog.getColor();
pactiveEntry.source = dialog.getSource();
pactiveEntry.mapper = dialog.getMapper();
pupdateForEntry(pactiveEntry);
}
}
});
pcurvedelete.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (pactiveEntry==null) return;
pcbxcurves.removeItem(pactiveEntry);
}
});
add(content, BorderLayout.NORTH);
}
protected void pupdateForEntry(Object o) {
if (o instanceof CbxEntry) pactiveEntry = (CbxEntry)o;
else pactiveEntry = null;
if (pactiveEntry == null) return;
tfRatio.setValue(pactiveEntry.ratio);
pcurvecolor.setBackground(pactiveEntry.color);
}
protected void updateChartProperties() {
chart.setTitle(tfTitle.getText());
PiePlot plot = (PiePlot)chart.getPlot();
SourcePieDataset dst = (SourcePieDataset)plot.getDataset();
int n = dst.getItemCount();
// If some curves were deleted, remove them
if (n != pcbxcurves.getItemCount()) {
boolean[] toKeep = new boolean[n]; // boolean are initialized to false by JVM
for (int i=0; i<pcbxcurves.getItemCount(); ++i)
toKeep[((CbxEntry)pcbxcurves.getItemAt(i)).datasetIndex] = true;
// Now we can remove the deleted curves, and update the existing ones
// Start from the end, to remove the entries and keep the index OK
for (int i=n-1; i>=0; i--) {
if (!toKeep[i]) {
GenericMapper mapper = (GenericMapper)mappers.remove(i);
if (mapper!=null) mapper.removeListener(PieChartShape.this);
DataSource source = (DataSource)mapperSources.remove(i);
if (source!=null) source.removeListener(PieChartShape.this);
dst.removeSource(i);
}
}
}
// Now we match the data set and the list
int len = pcbxcurves.getItemCount();
// first compute max ratio => define JFreeChart 100% and compute 1.0 equivalent
double maxratio = Double.NEGATIVE_INFINITY;
for (int i=0; i<len; ++i) {
CbxEntry e = (CbxEntry)pcbxcurves.getItemAt(i);
maxratio = Math.max(e.ratio, maxratio);
}
double delta = maxratio - 1.0;
// now apply changes
for (int i=0; i<len; ++i) {
CbxEntry e = (CbxEntry)pcbxcurves.getItemAt(i);
dst.setName(i,e.name);
DataSource ds = (DataSource)mapperSources.get(i);
if ((e.source==null) && (ds!=null)) {
ds.removeListener(PieChartShape.this);
ds.removeEndNotificationListener(PieChartShape.this);
}
mapperSources.set(i,e.source);
if (e.source!=null) {
e.source.addListener(PieChartShape.this);
e.source.addEndNotificationListener(PieChartShape.this);
}
ColorMapper cm = (ColorMapper)mappers.get(i);
if ((e.mapper==null) && (cm!=null)) cm.removeListener(PieChartShape.this);
mappers.set(i,e.mapper);
if (e.mapper!=null) e.mapper.addListener(PieChartShape.this);
if ((e.mapper!=null) && (e.source!=null)) {
e.mapper.setDefaultPaint(e.color);
plot.setSectionPaint(i,e.mapper.getPaint(e.source));
}
else plot.setSectionPaint(i,e.color);
plot.setExplodePercent(i, (delta==0) ? 0.0 : (e.ratio-1.0) / delta );
}
plot.setRadius(1.0/maxratio);
}
}
// Monitor changes in source and mapper used for dynamic colors.
/* (non-Javadoc)
* @see simtools.ui.MapperListener#mappingChanged(simtools.ui.GenericMapper)
*/
public void mappingChanged(GenericMapper mapper) {
notifyChange();
}
public boolean checkMapperChange(DataSource ds, long index) {
PiePlot plot = (PiePlot)chart.getPlot();
SourcePieDataset dst = (SourcePieDataset)plot.getDataset();
boolean hasChanged = false;
for (int i=0; i<mapperSources.size(); ++i) {
if (ds.equals(mapperSources.get(i))) {
ColorMapper cm = (ColorMapper)mappers.get(i);
if (cm!=null) {
Paint p1 = hasChanged ? null : plot.getSectionPaint(i);
plot.setSectionPaint(i,cm.getPaint(ds,index));
// check colors only if useful => when one color changed, don't check the others
if (!hasChanged) {
Paint p2 = plot.getSectionPaint(i);
if (!p1.equals(p2)) hasChanged = true;
}
}
}
}
return hasChanged;
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceValueChanged(simtools.data.DataSource, long, long)
*/
public void DataSourceValueChanged(DataSource ds, long minIndex, long maxIndex) {
dirty |= checkMapperChange(ds,maxIndex);
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceIndexRangeChanged(simtools.data.DataSource, long, long)
*/
public void DataSourceIndexRangeChanged(DataSource ds, long startIndex, long lastIndex) {
dirty |= checkMapperChange(ds,lastIndex);
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceInfoChanged(simtools.data.DataSource, simtools.data.DataInfo)
*/
public void DataSourceInfoChanged(DataSource ds, DataInfo newInfo) {
// Don't care if mapper source info changed
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceValueRangeChanged(simtools.data.DataSource)
*/
public void DataSourceValueRangeChanged(DataSource ds) {
// Don't care if mapper source range changed, it's the mapper business
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceOrderChanged(simtools.data.DataSource, int)
*/
public void DataSourceOrderChanged(DataSource ds, int newOrder) {
// Don't care if mapper source order changed
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceReplaced(simtools.data.DataSource, simtools.data.DataSource)
*/
public void DataSourceReplaced(DataSource oldData, DataSource newData) {
for (int i=0; i<mapperSources.size(); ++i) {
if(mapperSources.get(i)==oldData){
mapperSources.set(i,newData);
if(newData!=null){
newData.addListener(this);
newData.addEndNotificationListener(this);
}
oldData.removeListener(this);
oldData.removeEndNotificationListener(this);
}
}
}
/* (non-Javadoc)
* @see simtools.data.EndNotificationListener#notificationEnd(java.lang.Object)
*/
public void notificationEnd(Object referer) {
if (dirty) notifyChange();
dirty = false;
}
// Take care of serialisation. Special handling for datasources
private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
out.defaultWriteObject();
int n = mapperSources.size();
out.writeInt(n);
for (int i=0; i<n; ++i) {
DataSourcePool.global.writeDataSource(out, (DataSource)mapperSources.get(i));
}
}
private void readObject(java.io.ObjectInputStream in) throws java.lang.ClassNotFoundException, java.io.IOException {
in.defaultReadObject();
mapperSources = new Vector();
int n =in.readInt();
for (int i=0; i<n; ++i) {
DataSource ds = DataSourcePool.global.readDataSource(in);
mapperSources.add(ds);
if (ds!=null) {
ds.addListener(this);
ds.addEndNotificationListener(this);
}
}
}
/* (non-Javadoc)
* @see simtools.shapes.AbstractShape#cloneShape()
*/
protected AbstractShape cloneShape() {
ChartShape cs = (ChartShape)super.cloneShape();
PiePlot plot = (PiePlot)chart.getPlot();
SourcePieDataset dst = (SourcePieDataset)plot.getDataset();
((PiePlot)cs.chart.getPlot()).setDataset(dst.cloneSet());
return cs;
}
}