001/* ----------------------------------------------------------------------------
002   The Kiwi Toolkit - A Java Class Library
003   Copyright (C) 1998-2004 Mark A. Lindner
004
005   This library is free software; you can redistribute it and/or
006   modify it under the terms of the GNU General Public License as
007   published by the Free Software Foundation; either version 2 of the
008   License, or (at your option) any later version.
009
010   This library is distributed in the hope that it will be useful,
011   but WITHOUT ANY WARRANTY; without even the implied warranty of
012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013   General Public License for more details.
014
015   You should have received a copy of the GNU General Public License
016   along with this library; if not, write to the Free Software
017   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018   02111-1307, USA.
019 
020   The author may be contacted at: mark_a_lindner@yahoo.com
021   ----------------------------------------------------------------------------
022   $Log: ChartView.java,v $
023   Revision 1.10  2004/05/05 22:24:14  markl
024   comment block updates
025
026   Revision 1.9  2004/03/16 06:43:39  markl
027   LocaleManager method change
028
029   Revision 1.8  2004/01/23 00:06:51  markl
030   javadoc corrections
031
032   Revision 1.7  2003/01/19 09:33:21  markl
033   Javadoc & comment header updates.
034
035   Revision 1.6  2001/03/20 00:54:56  markl
036   Fixed deprecated calls.
037
038   Revision 1.5  2001/03/12 07:23:29  markl
039   Javadoc cleanup.
040
041   Revision 1.4  2000/10/15 09:40:29  markl
042   Added javadoc and final API polishing.
043
044   Revision 1.3  2000/10/13 08:06:05  markl
045   Integration fixes, cleanup.
046
047   Revision 1.2  2000/10/13 02:04:19  markl
048   Added remaining classes, and integrated components with models.
049   ----------------------------------------------------------------------------
050*/
051
052package kiwi.ui.graph;
053
054import java.awt.*;
055import java.awt.geom.*;
056import java.util.*;
057import javax.swing.*;
058
059import kiwi.event.*;
060import kiwi.ui.model.*;
061import kiwi.util.*;
062
063/** A base class for chart components that provides basic rendering logic.
064 * <code>ChartView</code> provides logic to recalculate the scale and repaint
065 * the chart when the component is resized or when the data model changes.
066 * <p>
067 * The <i>scale</i> value is the ratio of pixels to units, and is computed by 
068 * dividing the width (or height, depending on the orientation) of the
069 * component in pixels by the maximum value to be plotted in this chart. The
070 * calculation of the  <i>maximum value</i> is a chart-specific; the method
071 * <code>getMaxValue()</code> must be overridden to perform this calculation.
072 * <p>
073 * The method <code>paintChart()</code> should be overridden to paint the
074 * chart and (if necessary) the chart scale. Convenience methods are provided
075 * for rendering chart scales. By convention, chart scales appear on the left
076 * for vertical charts and at the top for horizontal charts.
077 *
078 * @author Mark Lindner
079 */
080
081public abstract class ChartView extends JComponent
082  implements ChartModelListener
083  {
084  private double maxValue = 0.0;
085  private double tickInterval = 10.0;
086
087  /** A vertical orientation. */
088  public static final int VERTICAL = 0;
089  /** A horizontal orientation. */
090  public static final int HORIZONTAL = 1;
091  /** The data model for this view. */
092  protected ChartModel model = null;
093  /** The scale factor for this view. */
094  protected double scale = 1.0;
095  /** The horizontal padding for this view. */
096  protected int horizontalPad = 10;
097  /** The vertical padding for this view. */
098  protected int verticalPad = 10;
099  /** A cached reference to the locale manager. */
100  protected LocaleManager lm = LocaleManager.getDefault();
101  /** The orientation for this view. */
102  protected int orientation = VERTICAL;
103  /** The precision for this view. */
104  protected int precision = 2;
105  /** The chart definition for this view. */
106  protected Chart chart;
107  /** The width of the scale, in pixels. */
108  protected int scaleWidth = 50;
109  
110  /** Construct a new <code>ChartView</code> for the specified chart
111   * definition.
112   *
113   * @param chart The chart definition.
114   */
115  
116  protected ChartView(Chart chart)
117    {
118    setChart(chart);
119    
120    setFont(new Font("Dialog", Font.PLAIN, 10));
121    setBackground(Color.white);
122    }
123
124  /** Set the chart definition. The precision and tick interval from the chart
125   * definition are automatically copied into the view.
126   *
127   * @param chart The chart definition.
128   */
129
130  public void setChart(Chart chart)
131    {
132    this.chart = chart;
133
134    setPrecision(chart.getPrecision());
135    setTickInterval(chart.getTickInterval());
136    }
137
138  /** Get the chart definition.
139   *
140   * @return The chart definition.
141   */
142  
143  public Chart getChart()
144    {
145    return(chart);
146    }
147  
148  /** Set the data model for this view.
149   *
150   * @param model The data model.
151   */
152  
153  public void setModel(ChartModel model)
154    {
155    if (this.model != null)
156      this.model.removeChartModelListener(this);
157    this.model = model;
158    model.addChartModelListener(this);
159    }
160
161  /** Get the data model for this view.
162   *
163   * @return The data model.
164   */
165
166  public ChartModel getModel()
167    {
168    return(model);
169    }
170
171  /** Handle <i>chart data changed</i> events. In response to data model
172   * events, the scale is recalculated and the chart is redrawn.
173   *
174   * @param event The event.
175   */
176
177  public void chartDataChanged(ChartModelEvent event)
178    {
179    recalculateScale();
180    repaint();
181    }
182
183  /** Paint the component. This method should not be overridden by
184   * subclassers.
185   */
186  
187  public void paintComponent(Graphics gc)
188    {
189    Dimension d = getSize();
190    gc.setColor(getBackground());
191    gc.fillRect(0, 0, d.width, d.height);
192
193    if(model != null)
194      paintChart(gc);
195    }
196
197  /** Set the chart orientation.
198   *
199   * @param orientation The orientation; one of the constants
200   * <code>HORIZONTAL</code> or <code>VERTICAL</code>.
201   */
202
203  public void setOrientation(int orientation)
204    {
205    if(orientation < VERTICAL || orientation > HORIZONTAL)
206      throw(new IllegalArgumentException("bad orientation value"));
207
208    this.orientation = orientation;
209    }
210
211  /** Set the horizontal padding. The padding value is used for the margins of
212   * the chart as well as the space between the chart and the scale.
213   *
214   * @param hpad The horizontal padding, in pixels.
215   *
216   */
217
218  public void setHorizontalPad(int hpad)
219    {
220    horizontalPad = hpad;
221    }
222
223  /** Set the vertical padding. The padding value is used for the margins of
224   * the chart as well as the space between the chart and the scale.
225   *
226   * @param vpad The vertical padding, in pixels.
227   *
228   */
229  
230  public void setVerticalPad(int vpad)
231    {
232    verticalPad = vpad;
233    }
234
235  /** Get the horizontal padding.
236   *
237   * @return The horizontal padding, in pixels.
238   */
239  
240  public int getHorizontalPad()
241    {
242    return(horizontalPad);
243    }
244
245  /** Get the vertical padding.
246   *
247   * @return The vertical padding, in pixels.
248   */
249  
250  public int getVerticalPad()
251    {
252    return(verticalPad);
253    }
254
255  /** Set the tick interval for this view. The tick interval is specified
256   * (conceptually) in the same units as the actual data values in the chart.
257   *
258   * @param tickInterval The tick interval.
259   */
260  
261  public void setTickInterval(double tickInterval)
262    {
263    this.tickInterval = tickInterval;
264    }
265
266  /** Get the tick interval for this view.
267   *
268   * @return The tick interval.
269   */
270
271  public double getTickInterval()
272    {
273    return(tickInterval);
274    }
275
276  /** Set the decimal precision for this view. This value
277   * determines how many decimal places are significant in the data values
278   * that are displayed by this view; the values in the chart scale will also
279   * be rendered to this many decimal places.
280   *
281   * @param precision The precision.
282   */
283
284  public void setPrecision(int precision)
285    {
286    if(precision < 0 || precision > 10)
287      throw(new IllegalArgumentException("Invalid precision"));
288    
289    this.precision = precision;
290    }
291
292  /** Get the decimal precision for this view.
293   *
294   * @return The precision.
295   */
296  
297  public int getPrecision()
298    {
299    return(precision);
300    }
301  
302  /** Paint the chart.
303   */
304  
305  protected abstract void paintChart(Graphics gc);
306
307  /** Determine if this chart has a scale. Some charts (notably pie charts)
308   * do not have a scale. The default implementation returns <code>true</code>.
309   * 
310   * @return <code>true</code> if the chart has a scale, <code>false</code>
311   * otherwise.
312   */
313  
314  protected boolean hasScale()
315    {
316    return(true);
317    }
318
319  /*
320   */
321
322  private void recalculateScale()
323    {
324    if(hasScale())
325      {
326      Dimension d = getSize();
327    
328      maxValue = getMaxValue();
329    
330      if(orientation == VERTICAL)
331        scale = (d.height - (2 * verticalPad)) / maxValue;
332      else
333        scale = (d.width - (2 * horizontalPad)) / maxValue;
334      }
335    }
336  
337  /** Overridden to recalculate the scale when the component geometry
338   * changes.
339   */
340  
341  public void setBounds(int x, int y, int width, int height)
342    {
343    super.setBounds(x, y, width, height);
344
345    recalculateScale();
346    }
347
348  /** Compute the maximum value. The default implementation returns
349   * <code>0.0</code>.
350   *
351   * @return The maximum value to be plotted.
352   */
353
354  protected double getMaxValue()
355    {
356    return(0.0);
357    }
358
359  /** A convenience method for drawing a vertical scale with tickmarks and
360   * tick labels.
361   *
362   * @param gc The graphics context.
363   * @param x The x-coordinate of the scale.
364   */
365  
366  protected void drawVerticalScale(Graphics gc, int x)
367    {
368    Dimension d = getSize();
369    FontMetrics fm = gc.getFontMetrics(getFont());
370    int fh = fm.getAscent() / 2;
371    int y = d.height - verticalPad;
372
373    gc.setColor(Color.black);
374    gc.drawLine(x, y, x, verticalPad);
375    
376    for(double tick = 0.0; tick <= maxValue; tick += tickInterval)
377      {
378      int yp = (int)(y - (tick * scale));
379      gc.drawLine(x - 1, yp, x - 4, yp);
380
381      String label = lm.formatDecimal(tick, precision);
382      gc.drawString(label, x - 4 - fm.stringWidth(label), yp + fh);
383      }
384    }
385
386  /** A convenience method for drawing a horizontal scale with tickmarks and
387   * tick labels.
388   *
389   * @param gc The graphics context.
390   * @param y The y-coordinate of the scale.
391   */
392
393  protected void drawHorizontalScale(Graphics gc, int y)
394    {
395    Dimension d = getSize();
396    FontMetrics fm = gc.getFontMetrics(getFont());
397    int fh = fm.getAscent() / 2;
398    int x = horizontalPad;
399
400    gc.setColor(Color.black);
401    gc.drawLine(x, y, d.width - horizontalPad, y);
402
403    for(double tick = 0.0; tick <= maxValue; tick += tickInterval)
404      {
405      int xp = (int)(x + (tick * scale));
406      gc.drawLine(xp, y - 1, xp, y - 4);
407      }
408
409    Graphics2D gc2d = (Graphics2D)gc;
410    Paint paint = Color.black;
411    gc2d.setPaint(paint);
412    AffineTransform rotate = new AffineTransform(0, 1, -1, 0, 0, 0);
413    gc2d.transform(rotate);
414
415    for(double tick = 0.0; tick <= maxValue; tick += tickInterval)    
416      {
417      int xp = (int)(x + (tick * scale));
418
419      String label = lm.formatDecimal(tick, precision);
420      // in the rotated coordinate system, (x', y') = (y, -x)
421      gc2d.drawString(label, y - 5 - fm.stringWidth(label), -(xp - fh));
422      }
423    }
424  
425  }
426
427/* end of source file */