/* ========================
* 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: CyclicPlotShape.java,v 1.10 2008/09/29 10:27:46 ogor Exp $
*
* Changes
* -------
* 25-Sep-2003 : Initial public release (NB);
*
*/
package jsynoptic.plugins.jfreechart;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.FlowLayout;
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.BoxLayout;
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.ui.LongAction;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CyclicNumberAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.ui.ChartPropertyEditPanel;
import simtools.data.DataInfo;
import simtools.data.DataSource;
import simtools.data.DataSourceCollection;
import simtools.ui.ActionCheckBox;
import simtools.ui.DynamicColorChooser;
import simtools.ui.NumberField;
import simtools.ui.ResourceFinder;
/**
* A shape specially designed for Cyclic axis.
* Contains all the parameter configuration options for such axis.
*
*/
public class CyclicPlotShape extends StandardPlotShape {
static final long serialVersionUID = -7549636147827861795L;
public static ResourceBundle resources = ResourceFinder.get(CyclicPlotShape.class, StandardPlotShape.resources);
public CyclicPlotShape(JFreeChart chart) {
this(chart,0,0,400,300);
}
public CyclicPlotShape(JFreeChart chart, int ox, int oy, int width, int height) {
super(chart, ox, oy, width, height);
XYPlot plot = (XYPlot)chart.getXYPlot();
plot.setDomainAxis(new CyclicNumberAxis(10,0));
plot.setRenderer(new JSynopticCyclicXYItemRenderer());
}
/* (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 super.getActions(x,y,o,context);
}
if (context==MOUSE_OUT_CONTEXT) {
return super.getActions(x,y,o,context);
}
if (context==MOUSE_PRESSED_CONTEXT) {
return null;
}
if (context==EDITOR_CONTEXT) {
}
Vector v = new Vector();
v.add(resources.getString("Properties..."));
XYPlot plot = (XYPlot)chart.getXYPlot();
SourceXYDataset dst = (SourceXYDataset)plot.getDataset();
if (o instanceof DataSource) {
v.add(resources.getString("setX"));
if (dst.getSources().size() != 0) {
v.add(resources.getString("addY"));
// No secondary Y here
}
}
else if (o instanceof DataSourceCollection) {
v.add(resources.getString("setXY1Yn"));
if (dst.getSources().size() != 0) {
v.add(resources.getString("addY1Yn"));
}
}
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;
}
return super.doAction(x,y,o,action, undoableEdit);
}
public class PlotPanel extends JPanel {
ActionCheckBox cbautox,cbfixedautox,cbcyclex,cbinvertx;
NumberField nffixedautox,tfpxmin,tfpxmax,nfperiodx,nfoffsetx;
JLabel lpxmin, lpxmax;
ActionCheckBox cbautoy,cbfixedautoy,cbcycley,cbinverty;
NumberField nffixedautoy,tfpymin,tfpymax,nfperiody,nfoffsety;
JLabel lpymin, lpymax;
JTextField tfTitle;
JPanel xcards,ycards;
Color noCurveColor;
// primary Y variables
JComboBox pcbxcurves;
JButton pcurvecolor, pcurvedelete;
CbxEntry pactiveEntry;
JTextField pcbxeditortf;
boolean peditLocked;
public PlotPanel() {
// setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
setLayout(new BorderLayout());
JPanel panel;
Box section;
Box content = Box.createVerticalBox();
setName(resources.getString("PlotProperties"));
XYPlot plot = chart.getXYPlot();
// 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);
// X axis section
section = new Box(BoxLayout.Y_AXIS);
section.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(),resources.getString("XAxis")));
panel = new JPanel();
panel.setLayout(new FlowLayout(FlowLayout.LEFT));
CyclicNumberAxis cna = null;
if (plot.getDomainAxis() instanceof CyclicNumberAxis) cna = (CyclicNumberAxis)plot.getDomainAxis();
panel.add(cbcyclex = new ActionCheckBox(resources.getString("CyclicAxis"), cna!=null) {
public void actionPerformed(ActionEvent e) {
CardLayout cl = (CardLayout)(xcards.getLayout());
if (isSelected()) cl.show(xcards, "cyclic");
else cl.show(xcards, "normal");
}
});
panel.add(cbinvertx = new ActionCheckBox(resources.getString("Inverted"), plot.getDomainAxis().isInverted()) {
public void actionPerformed(ActionEvent e) {
}
});
section.add(panel);
xcards = new JPanel();
xcards.setLayout(new CardLayout());
Box hbox = Box.createHorizontalBox();
hbox.add(new JLabel(resources.getString("Period")));
hbox.add(nfperiodx = new NumberField(cna==null ? 0 : cna.getPeriod()));
hbox.add(Box.createHorizontalGlue());
hbox.add(new JLabel(resources.getString("Offset")));
hbox.add(nfoffsetx = new NumberField(cna==null ? 0 : cna.getOffset()));
xcards.add(hbox,"cyclic");
panel = new JPanel();
panel.setLayout(new FlowLayout(FlowLayout.LEFT));
double autorange = plot.getDomainAxis().getFixedAutoRange();
panel.add(cbfixedautox = new ActionCheckBox(resources.getString("FixedRange"),autorange!=0) {
public void actionPerformed(ActionEvent e) {
nffixedautox.setEnabled(isSelected() && isEnabled());
}
});
panel.add(nffixedautox = new NumberField(autorange,10));
xcards.add(panel,"normal");
section.add(xcards);
panel = new JPanel();
panel.setLayout(new FlowLayout(FlowLayout.LEFT));
panel.add(cbautox = new ActionCheckBox(resources.getString("Auto-update"), plot.getDomainAxis().isAutoRange()) {
public void actionPerformed(ActionEvent e) {
cbfixedautox.setEnabled(isSelected());
cbfixedautox.apply();
lpxmin.setEnabled(!isSelected());
lpxmax.setEnabled(!isSelected());
tfpxmin.setEnabled(!isSelected());
tfpxmax.setEnabled(!isSelected());
}
});
panel.add(lpxmin = new JLabel(resources.getString("minLabel")));
panel.add(tfpxmin = new NumberField(10));
tfpxmin.setValue(plot.getDomainAxis().getLowerBound());
panel.add(lpxmax = new JLabel(resources.getString("maxLabel")));
panel.add(tfpxmax = new NumberField(10));
tfpxmax.setValue(plot.getDomainAxis().getUpperBound());
cbautox.apply();
section.add(panel);
content.add(section);
// Now handle Y axis
section = new Box(BoxLayout.Y_AXIS);
section.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(),resources.getString("YAxis")));
panel = new JPanel();
panel.setLayout(new FlowLayout(FlowLayout.LEFT));
cna = null;
if (plot.getRangeAxis() instanceof CyclicNumberAxis) cna = (CyclicNumberAxis)plot.getRangeAxis();
panel.add(cbcycley = new ActionCheckBox(resources.getString("CyclicAxis"), cna!=null) {
public void actionPerformed(ActionEvent e) {
CardLayout cl = (CardLayout)(ycards.getLayout());
if (isSelected()) cl.show(ycards, "cyclic");
else cl.show(ycards, "normal");
}
});
panel.add(cbinverty = new ActionCheckBox(resources.getString("Inverted"), plot.getRangeAxis().isInverted()) {
public void actionPerformed(ActionEvent e) {
}
});
section.add(panel);
ycards = new JPanel();
ycards.setLayout(new CardLayout());
hbox = Box.createHorizontalBox();
hbox.add(new JLabel(resources.getString("Period")));
hbox.add(nfperiody = new NumberField(cna==null ? 0 : cna.getPeriod()));
hbox.add(Box.createHorizontalGlue());
hbox.add(new JLabel(resources.getString("Offset")));
hbox.add(nfoffsety = new NumberField(cna==null ? 0 : cna.getOffset()));
ycards.add(hbox,"cyclic");
panel = new JPanel();
panel.setLayout(new FlowLayout(FlowLayout.LEFT));
autorange = plot.getRangeAxis().getFixedAutoRange();
panel.add(cbfixedautoy = new ActionCheckBox(resources.getString("FixedRange"),autorange!=0) {
public void actionPerformed(ActionEvent e) {
nffixedautoy.setEnabled(isSelected() && isEnabled());
}
});
panel.add(nffixedautoy = new NumberField(autorange,10));
ycards.add(panel,"normal");
section.add(ycards);
panel = new JPanel();
panel.setLayout(new FlowLayout(FlowLayout.LEFT));
panel.add(cbautoy = new ActionCheckBox(resources.getString("Auto-update"), plot.getRangeAxis().isAutoRange()) {
public void actionPerformed(ActionEvent e) {
cbfixedautoy.setEnabled(isSelected());
cbfixedautoy.apply();
lpymin.setEnabled(!isSelected());
lpymax.setEnabled(!isSelected());
tfpymin.setEnabled(!isSelected());
tfpymax.setEnabled(!isSelected());
}
});
panel.add(lpymin = new JLabel(resources.getString("minLabel")));
panel.add(tfpymin = new NumberField(10));
tfpymin.setValue(plot.getRangeAxis().getLowerBound());
panel.add(lpymax = new JLabel(resources.getString("maxLabel")));
panel.add(tfpymax = new NumberField(10));
tfpymax.setValue(plot.getRangeAxis().getUpperBound());
cbautoy.apply();
section.add(panel);
Box box = Box.createHorizontalBox();
box.add(new JLabel(resources.getString("Curves:")));
box.add(pcbxcurves = new JComboBox());
box.add(pcurvecolor = new JButton(" "));
box.add(pcurvedelete = new JButton(resources.getString("Delete")));
section.add(box);
content.add(section);
pcbxcurves.setEditable(true);
noCurveColor = pcurvecolor.getBackground();
pcurvecolor.setFocusPainted(false);
SourceXYDataset dst = (SourceXYDataset)plot.getDataset();
int n = dst.getSeriesCount();
JSynopticXYItemRenderer renderer = (JSynopticXYItemRenderer)plot.getRenderer();
for (int i=0; i<n; ++i) {
DataSource ds = renderer.getDataSource(i);
if (ds==null) {
ds = dst.getYSource(i); // take the datasource associated with this curve by default
DataSource linked = DataInfo.getLinkedSource(ds); // and follow link if necessary
if (linked!=null) ds = linked;
}
pcbxcurves.addItem(new CbxEntry(i,dst.getSeriesName(i),(Color)renderer.getSeriesPaint(i),ds,renderer.getColorMapper(i)));
}
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();
}
});
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);
}
});
cbcyclex.apply();
cbcycley.apply();
add(content, BorderLayout.NORTH);
}
protected void pupdateForEntry(Object o) {
if (o instanceof CbxEntry) pactiveEntry = (CbxEntry)o;
else pactiveEntry = null;
if (pactiveEntry == null) return;
pcurvecolor.setBackground(pactiveEntry.color);
}
protected void updateChartProperties() {
ValueAxis xaxis = null;
if (cbcyclex.isSelected()) {
xaxis = new CyclicNumberAxis(nfperiodx.getDoubleValue(),nfoffsetx.getDoubleValue());
} else {
xaxis = new NumberAxis();
if (nffixedautox.isEnabled()) xaxis.setFixedAutoRange(nffixedautox.getDoubleValue());
}
xaxis.setAutoRange(cbautox.isSelected());
xaxis.setInverted(cbinvertx.isSelected());
if (tfpxmin.isEnabled() && tfpxmax.isEnabled()) xaxis.setRange(tfpxmin.getDoubleValue(), tfpxmax.getDoubleValue());
ValueAxis yaxis = null;
if (cbcycley.isSelected()) {
yaxis = new CyclicNumberAxis(nfperiody.getDoubleValue(),nfoffsety.getDoubleValue());
} else {
yaxis = new NumberAxis();
if (nffixedautoy.isEnabled()) yaxis.setFixedAutoRange(nffixedautoy.getDoubleValue());
}
yaxis.setAutoRange(cbautoy.isSelected());
yaxis.setInverted(cbinverty.isSelected());
if (tfpymin.isEnabled() && tfpymax.isEnabled()) yaxis.setRange(tfpymin.getDoubleValue(), tfpymax.getDoubleValue());
chart.setTitle(tfTitle.getText());
XYPlot plot = chart.getXYPlot();
plot.setDomainAxis(xaxis);
plot.setRangeAxis(yaxis);
// Primary Y update
SourceXYDataset dst = (SourceXYDataset)plot.getDataset();
int n = dst.getSeriesCount();
// 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;
JSynopticXYItemRenderer renderer = (JSynopticXYItemRenderer)plot.getRenderer();
// 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]) {
if (i>0) dst.removeXSource(i);
else dst.removeYSource(i);
renderer.removeColorMapper(i);
renderer.removeDataSource(i);
}
}
}
// Now we match the data set and the list
for (int i=0; i<pcbxcurves.getItemCount(); ++i) {
CbxEntry e = (CbxEntry)pcbxcurves.getItemAt(i);
dst.setName(i,e.name);
JSynopticXYItemRenderer renderer = (JSynopticXYItemRenderer)plot.getRenderer();
renderer.setSeriesPaint(i,e.color);
renderer.setDataSource(i, e.source);
renderer.setColorMapper(i, e.mapper);
}
}
}
}