001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * Copyright by the Board of Trustees of the University of Illinois.         *
004 * All rights reserved.                                                      *
005 *                                                                           *
006 * This file is part of the HDF Java Products distribution.                  *
007 * The full copyright notice, including terms governing use, modification,   *
008 * and redistribution, is contained in the files COPYING and Copyright.html. *
009 * COPYING can be found at the root of the source code distribution tree.    *
010 * Or, see http://hdfgroup.org/products/hdf-java/doc/Copyright.html.         *
011 * If you do not have access to either file, you may request a copy from     *
012 * help@hdfgroup.org.                                                        *
013 ****************************************************************************/
014
015package hdf.view;
016
017import java.awt.BorderLayout;
018import java.awt.Color;
019import java.awt.Dimension;
020import java.awt.Frame;
021import java.awt.Graphics;
022import java.awt.Point;
023import java.awt.Window;
024import java.awt.event.ActionEvent;
025import java.awt.event.ActionListener;
026import java.lang.reflect.Array;
027
028import javax.swing.BorderFactory;
029import javax.swing.JButton;
030import javax.swing.JComponent;
031import javax.swing.JDialog;
032import javax.swing.JPanel;
033import javax.swing.WindowConstants;
034
035/**
036 * ChartView displays histogram/line chart of selected row/column of table data
037 * or image. There are two types of chart, histogram and line plot.
038 * 
039 * @author Peter X. Cao
040 * @version 2.4 9/6/2007
041 */
042public class Chart extends JDialog implements ActionListener {
043
044    /**
045     * 
046     */
047    private static final long serialVersionUID = 6306479533747330357L;
048
049    /** histogram style chart */
050    public static final int HISTOGRAM = 0;
051
052    /** line style chart */
053    public static final int LINEPLOT = 1;
054
055    /** The default colors of lines for selected columns */
056    public static final Color[] LINE_COLORS = { Color.black, Color.red,
057            Color.green.darker(), Color.blue, Color.magenta, Color.pink,
058            Color.yellow, Color.orange, Color.gray, Color.cyan };
059
060    /** the data values of line points or histogram */
061    protected double data[][];
062
063    /** Panel that draws plot of data values. */
064    protected ChartPanel chartP;
065
066    /** number of data points */
067    protected int numberOfPoints;
068
069    /** the style of chart: histogram or line */
070    private int chartStyle;
071
072    /** the maximum value of the Y axis */
073    private double ymax;
074
075    /** the minimum value of the Y axis */
076    private double ymin;
077
078    /** the maximum value of the X axis */
079    private double xmax;
080
081    /** the minimum value of the X axis */
082    private double xmin;
083
084    /** line labels */
085    private String lineLabels[];
086
087    /** line colors */
088    private Color lineColors[];
089
090    /** number of lines */
091    private int numberOfLines;
092
093    /* the data to plot against */
094    private double[] xData = null;
095
096    /**
097     * True if the original data is integer (byte, short, integer, long).
098     */
099    private boolean isInteger;
100
101    private java.text.DecimalFormat format;
102
103    /**
104     * Constructs a new ChartView given data and data ranges.
105     * <p>
106     * 
107     * @param owner
108     *            the owner frame of this dialog.
109     * @param title
110     *            the title of this dialog.
111     * @param style
112     *            the style of the chart. Valid values are: HISTOGRAM and LINE
113     * @param data
114     *            the two dimensional data array: data[linenumber][datapoints]
115     * @param xData
116     *            the range of the X values, xRange[0]=xmin, xRange[1]=xmax.
117     * @param yRange
118     *            the range of the Y values, yRange[0]=ymin, yRange[1]=ymax.
119     */
120    public Chart(Frame owner, String title, int style, double[][] data,
121            double[] xData, double[] yRange) {
122        super(owner, title, false);
123        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
124        format = new java.text.DecimalFormat("0.00E0");
125
126        if (data == null) {
127            return;
128        }
129
130        this.chartStyle = style;
131        this.data = data;
132
133        if (style == HISTOGRAM) {
134            isInteger = true;
135        }
136        else {
137            isInteger = false;
138        }
139
140        if (xData != null) {
141            int len = xData.length;
142            if (len == 2) {
143                this.xmin = xData[0];
144                this.xmax = xData[1];
145            }
146            else {
147                this.xData = xData;
148                xmin = xmax = xData[0];
149                for (int i = 0; i < len; i++) {
150                    if (xData[i] < xmin) {
151                        xmin = xData[i];
152                    }
153
154                    if (xData[i] > xmax) {
155                        xmax = xData[i];
156                    }
157                }
158            }
159        }
160        else {
161            this.xmin = 1;
162            this.xmax = data[0].length;
163        }
164
165        this.numberOfLines = Array.getLength(data);
166        this.numberOfPoints = Array.getLength(data[0]);
167        this.lineColors = LINE_COLORS;
168
169        if (yRange != null) {
170            // data range is given
171            this.ymin = yRange[0];
172            this.ymax = yRange[1];
173        }
174        else {
175            // search data range from the data
176            findDataRange();
177        }
178
179        if ((ymax < 0.0001) || (ymax > 100000)) {
180            format = new java.text.DecimalFormat("###.####E0#");
181        }
182        chartP = new ChartPanel();
183        chartP.setBackground(Color.white);
184
185        createUI();
186    }
187
188    /**
189     * Creates and layouts GUI components.
190     */
191    protected void createUI() {
192        Window owner = getOwner();
193
194        JPanel contentPane = (JPanel) getContentPane();
195        contentPane.setLayout(new BorderLayout(5, 5));
196        contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
197        int w = 640 + (ViewProperties.getFontSize() - 12) * 15;
198        int h = 400 + (ViewProperties.getFontSize() - 12) * 10;
199
200        contentPane.setPreferredSize(new Dimension(w, h));
201
202        contentPane.add(chartP, BorderLayout.CENTER);
203
204        JButton button = new JButton("Close");
205        button.addActionListener(this);
206        button.setActionCommand("Close");
207        JPanel tmp = new JPanel();
208        tmp.add(button);
209        contentPane.add(tmp, BorderLayout.SOUTH);
210
211        Point l = owner.getLocation();
212        l.x += 220;
213        l.y += 100;
214        setLocation(l);
215        pack();
216    }
217
218    public void actionPerformed(ActionEvent e) {
219        String cmd = e.getActionCommand();
220
221        if (cmd.equals("Close")) {
222            dispose();
223        }
224    }
225
226    /**
227     * Sets the color of each line of a line plot.
228     * 
229     * @param c The array of colors of each line of the line plot
230     */
231    public void setLineColors(Color c[]) {
232        lineColors = c;
233    }
234
235    /**
236     * Sets the labels of each line.
237     * 
238     * @param l The array of labels for each line of the line plot
239     */
240    public void setLineLabels(String l[]) {
241        lineLabels = l;
242    }
243
244    /** Set the data type of the plot data to be integer. */
245    public void setTypeToInteger() {
246        isInteger = true;
247    }
248
249    /** find and set the minimum and maximum values of the data */
250    private void findDataRange() {
251        if (data == null) {
252            return;
253        }
254
255        ymin = ymax = data[0][0];
256        for (int i = 0; i < numberOfLines; i++) {
257            for (int j = 0; j < numberOfPoints; j++) {
258                if (data[i][j] < ymin) {
259                    ymin = data[i][j];
260                }
261
262                if (data[i][j] > ymax) {
263                    ymax = data[i][j];
264                }
265            }
266        }
267    }
268
269    /** The canvas that paints the data lines. */
270    private class ChartPanel extends JComponent {
271        private static final long serialVersionUID = -3701826094727309097L;
272
273        /**
274         * Paints the plot components.
275         */
276        @Override
277        public void paint(Graphics g) {
278            if (numberOfLines <= 0) {
279                return; // no data
280            }
281
282            Dimension d = getSize();
283            int gap = 20;
284            int xgap = 2 * gap;
285            int ygap = 2 * gap;
286            int legendSpace = 0;
287            if ((chartStyle == LINEPLOT) && (lineLabels != null)) {
288                legendSpace = 60;
289            }
290
291            int h = d.height - gap;
292            int w = d.width - (3 * gap + legendSpace);
293            int xnpoints = Math.min(10, numberOfPoints - 1);
294            int ynpoints = 10;
295
296            // draw the X axis
297            g.drawLine(xgap, h, w + xgap, h);
298
299            // draw the Y axis
300            g.drawLine(ygap, h, ygap, 0);
301
302            // draw x labels
303            double xp = 0, x = xmin;
304            double dw = (double) w / (double) xnpoints;
305            double dx = (xmax - xmin) / xnpoints;
306            boolean gtOne = (dx >= 1);
307            for (int i = 0; i <= xnpoints; i++) {
308                x = xmin + i * dx;
309                xp = xgap + i * dw;
310                g.drawLine((int) xp, h, (int) xp, h - 5);
311                if (gtOne) {
312                    g
313                            .drawString(String.valueOf((int) x), (int) xp - 5,
314                                    h + gap);
315                }
316                else {
317                    g.drawString(String.valueOf(x), (int) xp - 5, h + gap);
318                }
319            }
320
321            // draw y labels
322            double yp = 0, y = ymin;
323            double dh = (double) h / (double) ynpoints;
324            double dy = (ymax - ymin) / (ynpoints);
325            if (dy > 1) {
326                dy = Math.round(dy * 10.0) / 10.0;
327            }
328            for (int i = 0; i <= ynpoints; i++) {
329                yp = i * dh;
330                y = i * dy + ymin;
331                g.drawLine(ygap, h - (int) yp, ygap + 5, h - (int) yp);
332                if (isInteger) {
333                    g.drawString(String.valueOf((int) y), 0, h - (int) yp + 8);
334                }
335                else {
336                    g.drawString(format.format(y), 0, h - (int) yp + 8);
337                }
338            }
339
340            Color c = g.getColor();
341            double x0, y0, x1, y1;
342            if (chartStyle == LINEPLOT) {
343                dw = (double) w / (double) (numberOfPoints - 1);
344
345                // use y = a + b* x to calculate pixel positions
346                double b = h / (ymin - ymax);
347                double a = -b * ymax;
348                boolean hasXdata = ((xData != null) && (xData.length >= numberOfPoints));
349                double xRatio = (1 / (xmax - xmin)) * w;
350                double xD = (xmin / (xmax - xmin)) * w;
351
352                // draw lines for selected spreadsheet columns
353                for (int i = 0; i < numberOfLines; i++) {
354                    if ((lineColors != null)
355                            && (lineColors.length >= numberOfLines)) {
356                        g.setColor(lineColors[i]);
357                    }
358
359                    // set up the line data for drawing one line a time
360                    if (hasXdata) {
361                        x0 = xgap + xData[0] * xRatio - xD;
362                    }
363                    else {
364                        x0 = xgap;
365                    }
366                    y0 = a + b * data[i][0];
367
368                    for (int j = 1; j < numberOfPoints; j++) {
369                        if (hasXdata) {
370                            x1 = xgap + xData[j] * xRatio - xD;
371                        }
372                        else {
373                            x1 = xgap + j * dw;
374                        }
375
376                        y1 = a + b * data[i][j];
377                        g.drawLine((int) x0, (int) y0, (int) x1, (int) y1);
378
379                        x0 = x1;
380                        y0 = y1;
381                    }
382
383                    // draw line legend
384                    if ((lineLabels != null)
385                            && (lineLabels.length >= numberOfLines)) {
386                        x0 = w + legendSpace;
387                        y0 = gap + gap * i;
388                        g.drawLine((int) x0, (int) y0, (int) x0 + 7, (int) y0);
389                        g
390                                .drawString(lineLabels[i], (int) x0 + 10,
391                                        (int) y0 + 3);
392                    }
393                }
394
395                g.setColor(c); // set the color back to its default
396
397                // draw a box on the legend
398                if ((lineLabels != null)
399                        && (lineLabels.length >= numberOfLines)) {
400                    g.drawRect(w + legendSpace - 10, 10, legendSpace, 10 * gap);
401                }
402
403            } // if (chartStyle == LINEPLOT)
404            else if (chartStyle == HISTOGRAM) {
405                // draw histogram for selected image area
406                xp = xgap;
407                yp = 0;
408                g.setColor(Color.blue);
409                int barWidth = w / numberOfPoints;
410                if (barWidth <= 0) {
411                    barWidth = 1;
412                }
413                dw = (double) w / (double) numberOfPoints;
414                for (int j = 0; j < numberOfPoints; j++) {
415                    xp = xgap + j * dw;
416                    yp = (int) (h * (data[0][j] - ymin) / (ymax - ymin));
417                    g.fillRect((int) xp, (int) (h - yp), barWidth, (int) yp);
418                }
419
420                g.setColor(c); // set the color back to its default
421            } // else if (chartStyle == HISTOGRAM)
422        } // public void paint(Graphics g)
423    } // private class ChartPanel extends Canvas
424
425}