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