/*
* Copyright (C) 2004 The Concord Consortium, Inc.,
* 10 Concord Crossing, Concord, MA 01742
*
* Web Site: http://www.concord.org
* Email: info@concord.org
*
* This library 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 library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* END LICENSE */
package org.concord.swing.graph;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.Vector;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* Bar graph component.
* This component in its default form provides a simple bar graph. Setting a value
* at a particular index will show a graphic bar at that height at that offset. This
* component is built out of an array of gauges, each of which has it's own set of
* properties.
* <p>
* In addition, there is a more complicated use of this component that involves remapping
* the index values used in the updateValue, setColor and getColor methods. Since each gauge
* also has indexed values and color, updating the values of the bar graph with a single
* index requires some special setup. This setup is done with index mapping. Using
* the addMapEntry method, the developer can specify that the graph index 'i' will
* reference sub-bar 'k' in gauge 'j'. This would be done by calling 'addMapEntry(i, j, k)'.
* In the absence of mapping for a paticular index, the gauge at that index will
* be treated as a simple, single bar.
*/
public class BarGraph
extends JComponent
implements ValueGraph
{
public static Vector oldGauges = new Vector();
public static final int DEFAULT_GAUGE_WIDTH = 100;
public static final int DEFAULT_NUMBER_OF_GAUGES = 5;
protected int gaugeWidth = DEFAULT_GAUGE_WIDTH;
protected int numberOfGauges = DEFAULT_NUMBER_OF_GAUGES;
protected Color background;
protected Gauge [] gauges;
protected boolean editable = false;
protected Vector indexMap = new Vector();
protected ChangeListener sliderChanged = new ChangeListener()
{
public void stateChanged(ChangeEvent event)
{
if (slider instanceof JSlider)
setScaleMax(slider.getMaximum() - slider.getValue());
BarGraph.this.repaint();
}
};
protected JSlider slider;
protected ComponentAdapter sizeChanged = new ComponentAdapter()
{
public void componentResized(ComponentEvent event)
{
setBarGraphSize(BarGraph.this.getSize());
}
};
/**
* Bar graph constructor.
* Sets the default number of gauges for this graph.
*/
public BarGraph()
{
super();
setLayout(null);
addComponentListener(sizeChanged);
setNumberOfGauges(DEFAULT_NUMBER_OF_GAUGES);
background = getBackground();
}
/**
* Add or replace an index map entry.
* If the values provided to the graph are shown non-sequentially,
* then index mapping must be done to assign the value to the correct
* gauge and sub-bar.<p>
* @param index - int index of the graph to be associated with a value.
* @param i - index refers to the offset of a gauge within the graph.
* @param j - index refers to the sub-bar within a particular gauge.
*/
public void addMapEntry(int index, int i, int j)
{
if (index >= indexMap.size())
{
indexMap.setSize(index + 1);
}
Point point = (Point) indexMap.elementAt(index);
if (point == null)
{
point = new Point(i, j);
indexMap.setElementAt(point, index);
}
else
{
point.x = i;
point.y = j;
}
}
/**
* Removes an index map for a particular index.
* <p>
* @param index - int index of the graph to be associated with a value.
* @param i - index refers to the offset of a gauge within the graph.
* @param j - index refers to the sub-bar within a particular gauge.
*/
public void removeMapEntry(int index, int i, int j)
{
if (index < indexMap.size())
{
indexMap.setElementAt(null, index);
}
}
/**
* Resets the index mapping.
*/
public void clearMap()
{
indexMap.removeAllElements();
}
public Color getBackground()
{
return background;
}
public void setBackground(Color color)
{
background = color;
}
/**
* Sets the number of gauges in this graph. Recylcles gauges
* from previous setting, creating new ones as necessary.
* <p>
* @param number - int number of gauges.
*/
public void setNumberOfGauges(int number)
{
removeAll();
if (gauges != null)
{
for (int i = 0; i < gauges.length; i++)
{
oldGauges.addElement(gauges[i]);
gauges[i] = null;
}
}
numberOfGauges = number;
gauges = new Gauge[number];
for (int i = 0; i < number; i++)
{
if (oldGauges.size() > 0)
{
gauges[i] = (Gauge) oldGauges.elementAt(0);
oldGauges.removeElementAt(0);
}
else
gauges[i] = new Gauge();
add(gauges[i]);
}
setBarGraphSize(getSize());
}
public void setMax(double value)
{
for (int i = 0; i< gauges.length; i++)
{
if (gauges[i] instanceof Gauge)
gauges[i].setMax(value);
}
}
public void setScaleMax(int scale)
{
double value = (((double) scale) - 50.0d) / 10.0d;
setMax(Math.pow(10.0d, value));
}
public double getMax()
{
return gauges[0].getMax();
}
protected double log10(double x)
{
return Math.log(x) / Math.log(10.0d);
}
public void setSliderValue()
{
int value = (int) (10.0d * log10(getMax()) + 50.0d);
value = slider.getMaximum() - value;
if (value < 0)
value = 0;
slider.setValue(value);
}
/**
* Returns editable state.
* Gauge values can be set with pointer device interaction.
* This method returns whether or not that feature is enabled.<p>
*/
public boolean isEditable()
{
return editable;
}
/**
* Make gauges editable.
* Gauge values can be set with pointer device interaction.
* This method enables and disables that feature.<p>
*/
public void setEditable(boolean value)
{
editable = value;
for (int i = 0; i < numberOfGauges; i++)
gauges[i].setEditable(value);
}
public JSlider getSlider()
{
if (slider == null)
{
setSlider(new JSlider());
}
return slider;
}
public void setSlider(JSlider slider)
{
if (this.slider instanceof JSlider)
{
this.slider.removeChangeListener(sliderChanged);
}
this.slider = slider;
if (this.slider instanceof JSlider)
{
this.slider.addChangeListener(sliderChanged);
}
}
/**
* Returns color associated with a particular index.
* <p>
* @param index - int index of graph element.
* @return - Color color of graph element.
*/
public Color getColor(int index)
{
if (index < indexMap.size())
{
Point point = (Point) indexMap.elementAt(index);
if (point instanceof Point)
{
return gauges[point.x].getColor(point.y);
}
}
return gauges[index].getColor();
}
/**
* Sets the color associated with a particular index.
* <p>
* @param color - Color of graph element.
* @param index - int index of graph element.
*/
public void setColor(Color color, int index)
{
if (index < indexMap.size())
{
Point point = (Point) indexMap.elementAt(index);
if (point instanceof Point)
{
gauges[point.x].setColor(color, point.y);
}
}
else
{
gauges[index].setColor(color);
}
repaint();
}
/**
* Sets value that determines pixel spaceing between gauges.
* <p>
* @param gap - int gap between gauges.
*/
public void setGaugeWidth(int width)
{
gaugeWidth = width;
setBarGraphSize(getSize());
}
/**
* Gets value that determines pixel spaceing between gauges.
* <p>
* @return - int gap between gauges.
*/
public int getGaugeWidth()
{
return gaugeWidth;
}
/**
* Returns the gauge component at a particular index.
* This method does not use index mapping.
* <p>
* @param index - int index of gauge.
* @return - Gauge gauge at index.
*/
public Gauge getGauge(int index)
{
return gauges[index];
}
protected void setBarGraphSize(Dimension size)
{
int gaugeGap = (size.width - numberOfGauges * gaugeWidth) / (numberOfGauges + 1);
int width = gaugeWidth;
if (gaugeGap <= 0)
{
width = (size.width / numberOfGauges) - 1;
gaugeGap = 1;
}
for (int i = 0; i < numberOfGauges; i++)
{
gauges[i].setBounds((i + 1) * gaugeGap + i * width, 0, width, size.height);
gauges[i].setEditable(editable);
}
}
/**
* Get value of graph element at index.
* Uses index mapping to determine the graph element.
* @param index - int index of graph element.
*/
public double getValue(int index)
{
if (index < indexMap.size())
{
Point point = (Point) indexMap.elementAt(index);
if (point instanceof Point)
{
return gauges[point.x].getValue(point.y);
}
}
return gauges[index].getValue();
}
public double getValue()
{
return getValue(0);
}
/**
* Updates graph element at index with new value.
* Uses index mapping to determine the graph element.
* @param x - double value to update.
* @param index - int index of graph element.
*/
public void updateValue(double x, int index)
{
if (index < indexMap.size())
{
Point point = (Point) indexMap.elementAt(index);
if (point instanceof Point)
{
gauges[point.x].updateValue(x, point.y);
return;
}
}
gauges[index].updateValue(x);
repaint();
}
public void updateValue(double x)
{
updateValue(x, 0);
}
public void updateValue(float x, int index)
{
updateValue((double) x, index);
}
public void updateValue(float x)
{
updateValue((double) x);
}
public void paintComponent(Graphics g)
{
Rectangle b = getBounds();
g.setColor(background);
g.fillRect(0, 0, b.width, b.height);
super.paintComponent(g);
}
public static void main(String [] args)
{
JFrame frame = new JFrame("Test bar graph");
BarGraph barGraph = new BarGraph();
barGraph.setBackground(Color.black);
barGraph.setEditable(true);
frame.getContentPane().add(barGraph, "Center");
frame.setSize(600, 500);
frame.setVisible(true);
}
}