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    /** Sets the color of each line of a line plot */
227    public void setLineColors(Color c[]) {
228        lineColors = c;
229    }
230
231    /** Sets the labels of each line. */
232    public void setLineLabels(String l[]) {
233        lineLabels = l;
234    }
235
236    /** Set the data type of the plot data to be integer. */
237    public void setTypeToInteger() {
238        isInteger = true;
239    }
240
241    /** find and set the minimum and maximum values of the data */
242    private void findDataRange() {
243        if (data == null) {
244            return;
245        }
246
247        ymin = ymax = data[0][0];
248        for (int i = 0; i < numberOfLines; i++) {
249            for (int j = 0; j < numberOfPoints; j++) {
250                if (data[i][j] < ymin) {
251                    ymin = data[i][j];
252                }
253
254                if (data[i][j] > ymax) {
255                    ymax = data[i][j];
256                }
257            }
258        }
259    }
260
261    /** The canvas that paints the data lines. */
262    private class ChartPanel extends JComponent {
263        private static final long serialVersionUID = -3701826094727309097L;
264
265        /**
266         * Paints the plot components.
267         */
268        @Override
269        public void paint(Graphics g) {
270            if (numberOfLines <= 0) {
271                return; // no data
272            }
273
274            Dimension d = getSize();
275            int gap = 20;
276            int xgap = 2 * gap;
277            int ygap = 2 * gap;
278            int legendSpace = 0;
279            if ((chartStyle == LINEPLOT) && (lineLabels != null)) {
280                legendSpace = 60;
281            }
282
283            int h = d.height - gap;
284            int w = d.width - (3 * gap + legendSpace);
285            int xnpoints = Math.min(10, numberOfPoints - 1);
286            int ynpoints = 10;
287
288            // draw the X axis
289            g.drawLine(xgap, h, w + xgap, h);
290
291            // draw the Y axis
292            g.drawLine(ygap, h, ygap, 0);
293
294            // draw x labels
295            double xp = 0, x = xmin;
296            double dw = (double) w / (double) xnpoints;
297            double dx = (xmax - xmin) / xnpoints;
298            boolean gtOne = (dx >= 1);
299            for (int i = 0; i <= xnpoints; i++) {
300                x = xmin + i * dx;
301                xp = xgap + i * dw;
302                g.drawLine((int) xp, h, (int) xp, h - 5);
303                if (gtOne) {
304                    g
305                            .drawString(String.valueOf((int) x), (int) xp - 5,
306                                    h + gap);
307                }
308                else {
309                    g.drawString(String.valueOf(x), (int) xp - 5, h + gap);
310                }
311            }
312
313            // draw y labels
314            double yp = 0, y = ymin;
315            double dh = (double) h / (double) ynpoints;
316            double dy = (ymax - ymin) / (ynpoints);
317            if (dy > 1) {
318                dy = Math.round(dy * 10.0) / 10.0;
319            }
320            for (int i = 0; i <= ynpoints; i++) {
321                yp = i * dh;
322                y = i * dy + ymin;
323                g.drawLine(ygap, h - (int) yp, ygap + 5, h - (int) yp);
324                if (isInteger) {
325                    g.drawString(String.valueOf((int) y), 0, h - (int) yp + 8);
326                }
327                else {
328                    g.drawString(format.format(y), 0, h - (int) yp + 8);
329                }
330            }
331
332            Color c = g.getColor();
333            double x0, y0, x1, y1;
334            if (chartStyle == LINEPLOT) {
335                dw = (double) w / (double) (numberOfPoints - 1);
336
337                // use y = a + b* x to calculate pixel positions
338                double b = h / (ymin - ymax);
339                double a = -b * ymax;
340                boolean hasXdata = ((xData != null) && (xData.length >= numberOfPoints));
341                double xRatio = (1 / (xmax - xmin)) * w;
342                double xD = (xmin / (xmax - xmin)) * w;
343
344                // draw lines for selected spreadsheet columns
345                for (int i = 0; i < numberOfLines; i++) {
346                    if ((lineColors != null)
347                            && (lineColors.length >= numberOfLines)) {
348                        g.setColor(lineColors[i]);
349                    }
350
351                    // set up the line data for drawing one line a time
352                    if (hasXdata) {
353                        x0 = xgap + xData[0] * xRatio - xD;
354                    }
355                    else {
356                        x0 = xgap;
357                    }
358                    y0 = a + b * data[i][0];
359
360                    for (int j = 1; j < numberOfPoints; j++) {
361                        if (hasXdata) {
362                            x1 = xgap + xData[j] * xRatio - xD;
363                        }
364                        else {
365                            x1 = xgap + j * dw;
366                        }
367
368                        y1 = a + b * data[i][j];
369                        g.drawLine((int) x0, (int) y0, (int) x1, (int) y1);
370
371                        x0 = x1;
372                        y0 = y1;
373                    }
374
375                    // draw line legend
376                    if ((lineLabels != null)
377                            && (lineLabels.length >= numberOfLines)) {
378                        x0 = w + legendSpace;
379                        y0 = gap + gap * i;
380                        g.drawLine((int) x0, (int) y0, (int) x0 + 7, (int) y0);
381                        g
382                                .drawString(lineLabels[i], (int) x0 + 10,
383                                        (int) y0 + 3);
384                    }
385                }
386
387                g.setColor(c); // set the color back to its default
388
389                // draw a box on the legend
390                if ((lineLabels != null)
391                        && (lineLabels.length >= numberOfLines)) {
392                    g.drawRect(w + legendSpace - 10, 10, legendSpace, 10 * gap);
393                }
394
395            } // if (chartStyle == LINEPLOT)
396            else if (chartStyle == HISTOGRAM) {
397                // draw histogram for selected image area
398                xp = xgap;
399                yp = 0;
400                g.setColor(Color.blue);
401                int barWidth = w / numberOfPoints;
402                if (barWidth <= 0) {
403                    barWidth = 1;
404                }
405                dw = (double) w / (double) numberOfPoints;
406                for (int j = 0; j < numberOfPoints; j++) {
407                    xp = xgap + j * dw;
408                    yp = (int) (h * (data[0][j] - ymin) / (ymax - ymin));
409                    g.fillRect((int) xp, (int) (h - yp), barWidth, (int) yp);
410                }
411
412                g.setColor(c); // set the color back to its default
413            } // else if (chartStyle == HISTOGRAM)
414        } // public void paint(Graphics g)
415    } // private class ChartPanel extends Canvas
416
417}