Here's a suggestion. You can keep the BarChart model separate from the view (the rendering of the chart) and the event code. MVC = model view controller.
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.text.NumberFormat;
import javax.swing.*;
import javax.swing.event.*;
public class BarModelTest {
BarChartModel chartModel = new BarChartModel();
BarChartPanel chartPanel = new BarChartPanel();
JSpinner barSpinner;
JSpinner heightSpinner;
NumberFormat nf;
public BarModelTest() {
nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(1);
double[] values = { 19.7, 47.2, 32.4 };
int n = values.length;
String[] labels = new String[n];
for(int j = 0; j < n; j++)
labels[j] = nf.format(values[j]);
chartModel.setValuesAndLabels(values, labels, n);
chartPanel.setDataAndLabels(values, labels);
chartPanel.setSelection(chartModel.getCurrentBar());
}
private Box getUIPanel() {
int bar = chartModel.getCurrentBar() +1;
int nbr = chartModel.getNbrBars();
SpinnerNumberModel barModel = new SpinnerNumberModel(bar,1,nbr,1);
barSpinner = new JSpinner(barModel);
double val = chartModel.getBarValue(chartModel.getCurrentBar());
SpinnerNumberModel heightModel = new SpinnerNumberModel(val,10.0,100.0,0.1);
heightSpinner = new JSpinner(heightModel);
int bars = chartModel.getNbrBars();
int max = chartModel.getMaxBars();
SpinnerNumberModel sizeModel = new SpinnerNumberModel(bars,1,max,1);
final JSpinner sizeSpinner = new JSpinner(sizeModel);
ChangeListener cl = new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSpinner spinner = (JSpinner)e.getSource();
Number value = (Number)spinner.getValue();
if(spinner == barSpinner) {
int index = value.intValue() -1;
chartModel.setCurrentBar(index);
heightSpinner.setValue(chartModel.getBarValue(index));
chartPanel.setSelection(index);
}
if(spinner == heightSpinner) {
int selected = chartModel.getCurrentBar();
double barVal = value.doubleValue();
String label = nf.format(barVal);
chartModel.setBarValue(selected, barVal);
chartModel.setBarLabel(selected, label);
chartPanel.setValueAndLabel(selected, barVal, label);
}
if(spinner == sizeSpinner) {
int val = value.intValue();
SpinnerNumberModel model = (SpinnerNumberModel)barSpinner.getModel();
model.setMaximum(val);
if(model.getNumber().intValue() > val)
model.setValue(value);
if(val > chartModel.getNbrBars()) {
double height = ((Number)heightSpinner.getValue()).doubleValue();
chartModel.addBar(height, nf.format(height));
} else if(val < chartModel.getNbrBars()) {
chartModel.removeBar();
}
chartPanel.setDataAndLabels(chartModel.values, chartModel.labels);
}
}
};
barSpinner.addChangeListener(cl);
heightSpinner.addChangeListener(cl);
sizeSpinner.addChangeListener(cl);
Box box = Box.createHorizontalBox();
box.add(Box.createHorizontalGlue());
box.add(new JLabel("bar"));
box.add(barSpinner);
box.add(Box.createHorizontalGlue());
box.add(new JLabel("cell height"));
box.add(heightSpinner);
box.add(Box.createHorizontalGlue());
box.add(new JLabel("bars"));
box.add(sizeSpinner);
box.add(Box.createHorizontalGlue());
return box;
}
public static void main(String[] args) {
BarModelTest test = new BarModelTest();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(test.chartPanel);
f.getContentPane().add(test.getUIPanel(), "Last");
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class BarChartPanel extends JPanel {
double[] data = new double[0];
String[] labels = new String[0];
AffineTransform at;
double maxValue;
int selectedBar = -1;
final int SPAD = 30;
final int PAD = 20;
public void setValueAndLabel(int index, double value, String label) {
data[index] = value;
labels[index] = label;
// if value exceeds top of ordinate -> rescale
if(value > maxValue) {
maxValue = value;
if(at != null)
at = null;
}
repaint();
}
public void setDataAndLabels(double[] values, String[] labels) {
data = values;
this.labels = labels;
// new data -> rescale all data
double max = getDataMax();
if(Math.abs(max - maxValue) > 1.0) {
maxValue = max;
if(at != null)
at = null;
}
repaint();
}
public void setSelection(int index) {
selectedBar = index;
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
double w = getWidth();
double h = getHeight();
double xInc = (w-2*PAD)/data.length;
if(at == null)
initTransform();
g2.setPaint(new Color(51,51,51));
// ordinate
g2.draw(new Line2D.Double(PAD, PAD, PAD, h-PAD));
// abcissa
g2.draw(new Line2D.Double(PAD, h-PAD, w-PAD, h-PAD));
// draw labels
Color color = g2.getColor();
Color selectedColor = Color.red;
Font font = g2.getFont().deriveFont(14f);
g2.setFont(font);
FontRenderContext frc = g2.getFontRenderContext();
for(int j = 0; j < labels.length; j++) {
float width =(float)font.getStringBounds(labels[j], frc).getWidth();
LineMetrics lm = font.getLineMetrics(labels[j], frc);
float height = lm.getHeight();
float sx = (float)(PAD + j*xInc + (xInc - width)/2);
float sy = (float)(h-SPAD + (height+SPAD)/2);
// show selection
g2.setPaint((j == selectedBar) ? selectedColor : color);
g2.drawString(labels[j], sx, sy);
}
// plot data
g2.setPaint(Color.blue);
double x = PAD, lastY;
double zero = modelToView(0);
for(int j = 0; j < data.length; j++) {
double y = modelToView(data[j]);
g2.draw(new Line2D.Double(x, y, x+xInc, y));
if(j > 0) {
if(data[j-1] < data[j])
g2.draw(new Line2D.Double(x, y, x, zero));
if(j < data.length-1 && data[j+1] <= data[j])
g2.draw(new Line2D.Double(x+xInc, y, x+xInc, zero));
} else if(j == 0 && data.length > 1 && data[0] >= data[1]) {
g2.draw(new Line2D.Double(x+xInc, y, x+xInc, zero));
}
if(j == data.length-1) {
// last bar, close
g2.draw(new Line2D.Double(x+xInc, y, x+xInc, zero));
}
x += xInc;
lastY = y;
}
}
public Dimension getPreferredSize() {
return new Dimension(400,400);
}
private double modelToView(double y) {
Point2D.Double dest = new Point2D.Double();
Point2D.Double src = new Point2D.Double(0, y);
at.transform(src, dest);
return dest.y;
}
private void initTransform() {
double w = getWidth();
double h = getHeight();
double maxY = getDataMax();
double xScale = (w-2*PAD)/data.length;
double yScale = (h-2*PAD)/maxY;
// move to origin
double x = PAD;
double y = h-PAD;
at = AffineTransform.getTranslateInstance(x,y);
// scale and flip(-) about the abcissa
at.scale(xScale, -yScale);
}
private double getDataMax() {
double max = Double.MIN_VALUE;
for(int j = 0; j < data.length; j++) {
if(data[j] > max)
max = data[j];
}
return max;
}
}
class BarChartModel {
double[] values = new double[0];
String[] labels = new String[0];
int currentBar = 0;
int numBars = 0;
int MAX_BARS = 5;
public void setValuesAndLabels(double[] values, String[] labels, int nbr) {
this.values = values;
this.labels = labels;
numBars = nbr;
}
public int getCurrentBar() {
return currentBar;
}
public void setCurrentBar(int value) {
currentBar = value;
}
public int getNbrBars() {
return numBars;
}
public int getMaxBars() {
return MAX_BARS;
}
public double getBarValue(int idx) {
return values[idx];
}
public void setBarValue(int idx, double value) {
values[idx] = value;
}
public String getBarLabel(int idx) {
return labels[idx];
}
public void setBarLabel(int idx, String value) {
labels[idx] = value;
}
public void addBar(double val, String lbl) {
addValue(val);
addLabel(lbl);
numBars = values.length;
}
public void removeBar() {
// remove currentBar
numBars--;
double[] tempValues = new double[numBars];
String[] tempLabels = new String[numBars];
for(int j = 0, k = 0; j < values.length; j++) {
if(j == currentBar) {
continue;
} else {
tempValues[k] = values[j];
tempLabels[k++] = labels[j];
}
}
values = tempValues;
labels = tempLabels;
}
private void addValue(double value) {
double[] temp = new double[numBars+1];
System.arraycopy(values, 0, temp, 0, numBars);
temp[numBars] = value;
values = temp;
}
private void addLabel(String label) {
String[] temp = new String[numBars+1];
System.arraycopy(labels, 0, temp, 0, numBars);
temp[numBars] = label;
labels = temp;
}
}