Source code: jplot/DataFile.java
1 /*
2 * JPLOT -- Java Plotting Interface for any programme or
3 * as an independent GUI
4 *
5 * Copyright (C) 1999 Jan van der Lee
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 * 02111-1307, USA.
21 *
22 * Send bugs, suggestions or queries to <jplot@cig.ensmp.fr>
23 * The latest releases are found at
24 * http://www.cig.ensmp.fr/~vanderlee/jplot.html
25 *
26 * Initally developed for use by the Centre d'Informatique Geologique
27 * Ecole des Mines de Paris, Fontainebleau, France.
28 */
29
30 package jplot;
31
32 import java.io.*;
33 import java.util.*;
34
35 /**
36 * This class contains all the info specific for one datafile.
37 * The PlotPanel provides the columns to-be-plotted for
38 * a specific file, with a line-style class for each column.
39 * This class keeps track of this information.
40 */
41 public class DataFile {
42 private File file;
43 private Vector items;
44 private Vector columns;
45 //private Vector columnNames;
46 private int xColumn;
47 private Vector styles;
48 private String abbreviatedFilename;
49 private String filenameWithoutPath;
50 private final int maxLen=45;
51 private int index;
52 private int graphType;
53
54 private GraphPars gp;
55
56 /**
57 * Builds the class from a filename. As soon as the datafile is
58 * build, read the header of the file and make a list of all the
59 * available columns.
60 * @param file file for this set
61 * @param index index, kind of identifier of this instance,
62 * @param labels vector to store the labels defined in the datafile
63 * corresponding to the index of the tabbedPanes.
64 */
65 DataFile(File file, int index, GraphPars gp) {
66 this.file = file;
67 this.index = index;
68 this.gp = gp;
69 columns = new Vector();
70 styles = new Vector();
71 items = new Vector();
72 graphType = gp.GRAPHTYPE_2D;
73 int len = file.toString().length();
74 if (len > maxLen) {
75 abbreviatedFilename = "..." + file.toString().substring(len-maxLen,len);
76 }
77 else abbreviatedFilename = file.toString();
78 int i = abbreviatedFilename.lastIndexOf(JPlot.FS,0);
79 len = abbreviatedFilename.length();
80 if (i > -1) filenameWithoutPath = abbreviatedFilename.substring(i,len);
81 else filenameWithoutPath = abbreviatedFilename;
82 loadColumnNames(gp.getLabels());
83 }
84
85 /**
86 * Reads the datafile for data. The data is put in a specific purpose
87 * DataArray object, which contains x- and y values. Note that all
88 * blank lines or lines starting with '#' are skipped. One exception:
89 * lines starting with '# column 1:' etc. are supposed to provide
90 * the name of the column, hence '# column 1: nodes' means that column
91 * 1 has the name 'nodes', which will be used by this program in order
92 * to easily select columns.
93 *
94 * If the list of column names existed already, and if the new list
95 * of names is identical, return true. Else delete the current
96 * selection and return false.
97 *
98 * @param labels vector to store the labels defined in the datafile
99 * @return true if the current selection can be maintained, false otherwise.
100 */
101 public boolean loadColumnNames(Vector labels) {
102 boolean res = false;
103 Vector newItems = new Vector();
104
105 // mark the labels eventually present and given by a dataset
106 // with CHECK : if they are found in the new dataset, we rename
107 // their type to 'DATA' which allows us to keep the settings
108 // such as font, color etc.
109 Vector newLabels = new Vector();
110 for (Enumeration e=labels.elements(); e.hasMoreElements();) {
111 GraphLabel g = (GraphLabel) e.nextElement();
112 if (g.equals(GraphLabel.DATA) && g.getFile() == file) {
113 g.setID(GraphLabel.CHECK);
114 }
115 newLabels.add(g);
116 }
117
118 try {
119 BufferedReader in = new BufferedReader(new FileReader(file));
120 String s;
121 int k = 1;
122 while ((s=in.readLine()) != null) {
123 s = s.trim();
124 if (s.length() == 0) continue;
125 StringTokenizer st = new StringTokenizer(s," \t,:");
126 String token = st.nextToken();
127 if (token.startsWith("#")) {
128 if (!st.hasMoreTokens()) continue;
129 String nextToken = st.nextToken();
130 if (st.hasMoreTokens()) {
131 if (nextToken.startsWith("graph")) {
132 token = st.nextToken();
133 if (token.startsWith("piper")) graphType = gp.GRAPHTYPE_PIPER;
134 else if (token.startsWith("multi")) {
135
136 // it's a multidata plot. We'll disable the
137 // legend by default for this type of graphs:
138 graphType = gp.GRAPHTYPE_MULTI;
139 gp.setDrawLegend(false);
140 }
141 else graphType = gp.GRAPHTYPE_2D;
142 continue;
143 }
144 else if (nextToken.equals("column") ||
145 nextToken.equals("row")) {
146 st.nextToken(); // eat column number, e.g. '2:'
147 token = st.nextToken();
148 while (st.hasMoreTokens()) token += " " + st.nextToken();
149 newItems.addElement(token);
150 continue;
151 }
152
153 else if (nextToken.equals("dataset")) {
154 st.nextToken(); // eat column number, e.g. '2:'
155 token = getLabel(st);
156 newItems.addElement(token);
157 continue;
158 }
159
160 // get the x-label from the dataset, if any
161 else if (nextToken.startsWith("xlabel") && st.hasMoreTokens()) {
162 GraphLabel gl =
163 new GraphLabel(GraphLabel.XLABEL,getLabel(st));
164 addLabel(gl,newLabels);
165 continue;
166 }
167
168 // get the y-label from the dataset, if any
169 else if (nextToken.startsWith("ylabel") && st.hasMoreTokens()) {
170 GraphLabel gl =
171 new GraphLabel(GraphLabel.YLABEL,getLabel(st));
172 gl.setRotation(Math.PI*1.5);
173 addLabel(gl,newLabels);
174 continue;
175 }
176
177 // random labels:
178 else if (nextToken.startsWith("label") && st.hasMoreTokens()) {
179 GraphLabel gl =
180 new GraphLabel(GraphLabel.DATA,getLabel(st));
181 gl.setFile(file);
182 if (st.hasMoreTokens()) {
183 double xx,yy;
184 xx = Double.parseDouble(st.nextToken());
185 if (st.hasMoreTokens()) {
186 yy = Double.parseDouble(st.nextToken());
187 gl.setDataLocation(xx,yy);
188 if (st.hasMoreTokens()) {
189 gl.setDataRotation(Math.PI*Double.parseDouble(st.nextToken())/180.0);
190 }
191 }
192 else gl.setRotation(Math.PI*xx/180);
193 }
194 addLabel(gl,newLabels);
195 continue;
196 }
197 else continue;
198 }
199 }
200 else if (newItems.size() == 0) {
201 newItems.addElement("column " + Integer.toString(k++));
202 while (st.hasMoreTokens()) {
203 token = st.nextToken();
204 if (token.startsWith("*")) break;
205 newItems.addElement("column " + Integer.toString(k++));
206 }
207 break;
208 }
209 }
210 in.close();
211 }
212 catch (Exception e) {
213 Utils.oops(null,"Something's wrong with file '" + getName() + "'!");
214 return false;
215 }
216
217 if (items.size() > 0) {
218 if (items.size() == newItems.size()) {
219 res = true;
220 Enumeration e1 = items.elements();
221 Enumeration e2 = newItems.elements();
222 while (e1.hasMoreElements()) {
223 if (!((String)e1.nextElement()).equals((String)e2.nextElement())) {
224 res = false;
225 break;
226 }
227 }
228 }
229 else res = false;
230 }
231 items = newItems;
232 if (!res) {
233 if (columns.size() > 0) columns.removeAllElements();
234 if (styles.size() > 0) styles.removeAllElements();
235 }
236 if (items.size() == 2) gp.setDrawLegend(false);
237
238 // if labels read, delete all labels which were marked
239 // 'CHECK' and not found in the current dataset:
240 if (labels.size() > 0) labels.removeAllElements();
241 if (newLabels.size() > 0) {
242 for (Enumeration e=newLabels.elements(); e.hasMoreElements();) {
243 GraphLabel g = (GraphLabel) e.nextElement();
244 if (!g.equals(GraphLabel.CHECK)) labels.add(g);
245 }
246 newLabels.removeAllElements();
247 newLabels = null;
248 }
249 return res;
250 }
251
252 private String getLabel(StringTokenizer st) {
253 String token = st.nextToken();
254 if (token.startsWith("\"")) { // is a label between " and "
255 while (!token.endsWith("\"")) token += " " + st.nextToken();
256 token = token.substring(1,token.length()-1);
257 }
258 return token;
259 }
260
261 /*
262 * Add a label to the vector of labels.
263 * The rule is, for the moment: if the user reloads the
264 * datafile (and hence passes through this procedure), the
265 * label is declared. But if the label already exists (with,
266 * perhaps, another color, font or position), than we keep
267 * this label instead BUT we use the position as defined
268 * here in the data set.
269 */
270 private void addLabel(GraphLabel gl, Vector newLabels) {
271 boolean labelFound = false;
272 for (Enumeration e=newLabels.elements(); e.hasMoreElements();) {
273 GraphLabel g = (GraphLabel) e.nextElement();
274 if (gl.equals(g.getText()) && (g.equals(GraphLabel.CHECK))) {
275 g.setUsePosition(false);
276 g.setDataLocation(gl.getXPos(),gl.getYPos());
277 g.setID(gl.getID());
278 labelFound = true;
279 break;
280 }
281 }
282 if (!labelFound) newLabels.add(gl);
283 }
284
285
286 /**
287 * @return the number of items present in the item vector.
288 */
289 public int getNumberOfItems() {
290 return items.size();
291 }
292
293 /**
294 * @return the item corresponding to index i.
295 */
296 public String getItem(int i) {
297 return (String) items.get(i);
298 }
299
300 /**
301 * Adds an item to the vector.
302 * @param item name of the item added.
303 */
304 public void addItem(String item) {
305 items.add(item);
306 }
307
308 /*
309 * remove a column at a specified index:
310 */
311 private void delCol(Integer index) {
312 int k=0;
313 for (Enumeration e=columns.elements(); e.hasMoreElements(); k++) {
314 Integer i = (Integer) e.nextElement();
315 if (i.equals(index)) break;
316 }
317 if (k < columns.size()) {
318 columns.remove(k);
319 styles.remove(k);
320 }
321 }
322
323 /**
324 * adds a column to the vector of columns.
325 * The column is added <strong>even</strong> if the column
326 * index is already selected (hence selecting several times the
327 * same column is allowed). This is useful if the user wants
328 * to plot the same data several times but in a different way.
329 * (histogram + lines, for example).
330 * @param columnIndex index of the column
331 * @param lp parameters used to draw the line
332 */
333 public void addColumn(int columnIndex, LinePars lp) {
334 Integer index = new Integer(columnIndex);
335 columns.add(index);
336 styles.add(lp);
337 }
338
339 /**
340 * sets the index of the column used for the X-axis
341 * @param columnIndex index of the column
342 */
343 public void setXColumn(int columnIndex) {
344 xColumn = columnIndex;
345 }
346
347 /**
348 * returns the index of the column used for the X-axis
349 * @return index of the column
350 */
351 public int getXColumn() {
352 return xColumn;
353 }
354
355 /**
356 * returns the index of the column used for the Y-axis
357 * @param i index of the column item
358 * @return index of the column
359 */
360 public int getYColumn(int i) {
361 return ((Integer)columns.get(i)).intValue();
362 }
363
364 /**
365 * returns the linestyle of one of the vector items.
366 * @param index index of the column
367 */
368 public LinePars getLinePars(int index) {
369 if (styles.size() == 0) return new LinePars();
370 if (index < styles.size()) return (LinePars)styles.get(index);
371 return (LinePars)styles.get(0);
372 }
373
374 /**
375 * sets the linestyle of one of the vector items.
376 * @param index index of the column
377 * @param lp linepars instance (can't be null!)
378 */
379 public void setLinePars(int index, LinePars lp) {
380 styles.set(index,lp);
381 }
382
383 /**
384 * sets the linestyle of all the vector items.
385 * @param lp linepars instance
386 */
387 public void setLinePars(LinePars lp) {
388 if (styles.size() > 0) {
389 styles.removeAllElements();
390 }
391 for (int k=0; k<columns.size(); k++) {
392 lp = new LinePars(lp);
393 if (lp.slideColor) lp.nextColor(columns.size());
394 styles.add(lp);
395 }
396 }
397
398 public String getLegend(int index) {
399 return getLinePars(index).getName();
400 }
401
402 /**
403 * remove a column from the vector of columns.
404 * @param columnIndex index of the column
405 */
406 public void removeColumn(int columnIndex) {
407 if (columnIndex < columns.size()) {
408 columns.remove(columnIndex);
409 styles.remove(columnIndex);
410 }
411 }
412
413 /**
414 * Return the number of columns (selected items)
415 * @return number of Y-columns
416 */
417 public int getNumberOfColumns() {
418 return columns.size();
419 }
420
421 /**
422 * Return the file
423 * @return file
424 */
425 public File getFile() {
426 return file;
427 }
428
429 /**
430 * Return the abbreviated filename.
431 * This name is different from the file itselves if it exceeds a
432 * a certain number of characters (about 45). Otherwise, the name
433 * is too long to fit in decent textboxes...
434 * @return an abbreviated version of the filename, if the latter is too long.
435 */
436 public String getAbbreviatedFilename() {
437 return abbreviatedFilename;
438 }
439
440 /**
441 * Return the filename with the preceeding path stripped off.
442 * @return an abbreviated version of the filename, without an eventual path.
443 */
444 public String getFilenameWithoutPath() {
445 return filenameWithoutPath;
446 }
447
448 /**
449 * Return the filename as such.
450 * @return the filename.
451 */
452 public String getName() {
453 return file.toString();
454 }
455
456 /**
457 * Checks whether the argument is a column selected for output.
458 * @param index index which is sought for.
459 */
460 public int isSelected(int index) {
461 int k = 0;
462 for (Enumeration e=columns.elements(); e.hasMoreElements(); k++) {
463 if (((Integer)e.nextElement()).intValue() == index) {
464 //System.out.print("... " + index + " is found ...");
465 return k;
466 }
467 }
468 return -1;
469 }
470
471 /**
472 * Returns the name of one of the columns present here.
473 * @return name of column with index i
474 */
475 public String getColumnName(int i) {
476 int index = ((Integer)columns.get(i)).intValue();
477 return (String)items.get(index);
478 }
479
480 /**
481 * Sets the current datafile index to another value.
482 * @param i new index
483 */
484 public void setIndex(int i) {
485 index = i;
486 }
487
488 /**
489 * @return the graphstyle of the current dataset
490 */
491 public int getGraphType() {
492 return graphType;
493 }
494
495 /**
496 * Reads the current file and returns the data arrays.
497 * @param data arrays of X,Y data
498 * @param dataArrays vector with data-arrays of type DataArray
499 * @param type graph type (2D, piper, multiplot...)
500 * @return false if some weird error might occur during parsing.
501 */
502 public boolean getDataArrays(Vector dataArrays, int type) {
503 int nCol = getNumberOfColumns();
504 int nIts = getNumberOfItems();
505 double x=0.0;
506 int nY = (type == GraphPars.GRAPHTYPE_PIPER)? nIts : nCol;
507 double[] y = new double [nY];
508 boolean newDataSet = true;
509 boolean isFirstLabel = true;
510 String s;
511
512 // used by the multiplot graph style:
513 int currentDataset = 0;
514
515 try {
516 BufferedReader in = new BufferedReader(new FileReader(getFile()));
517
518 // more than nY items, in case of the user selecting several
519 // times the same item... NullPointerException if he/she selects
520 // more than 2*nY items :-(
521 DataArray[] data = new DataArray [2*nY];
522 int k=0;
523
524 // read a line from the file. Ignore all comments and blank lines:
525 while ((s=in.readLine()) != null) {
526 s = s.trim();
527 if (s.length() == 0) continue;
528 else if (s.startsWith("#")) continue;
529 StringTokenizer st = new StringTokenizer(s," \t,");
530 if (st.countTokens() < 2) continue;
531
532 // if the current graph-type is a standard (X,Y1,Y2 etc) graph:
533 if (type == GraphPars.GRAPHTYPE_2D) {
534
535 // start new dataset here
536 if (newDataSet) {
537 if (JPlot.debug) System.out.println("starting a new dataset with " + nCol + " columns...");
538 for (int i=0; i<nCol; i++) {
539 data[i] = new DataArray(101,getLinePars(i));
540 }
541 newDataSet = false;
542 }
543 boolean liftPen = false;
544 for (int i=0; st.hasMoreTokens(); i++) {
545 String t = st.nextToken();
546 if (t.startsWith("*")) liftPen = true;
547 if (i == getXColumn()) x = Double.parseDouble(t);
548 for (k=0; k<nCol; k++) {
549 if (getYColumn(k) == i) y[k] = Double.parseDouble(t);
550 }
551 }
552 for (int i=0; i<nCol; i++) data[i].addPoint(x,y[i],liftPen);
553 }
554 else {
555
556 // not a standard graph. Can be a piper diagram or some kind
557 // of multiplot graph. The file format of these special graphs
558 // is kind of rigid, refer to the docs for info.
559 if (type == GraphPars.GRAPHTYPE_PIPER) {
560
561 // it's a piper diagram. Very rigid format:
562 data[0] = new DataArray(3,getLinePars(k++));
563 if (st.countTokens() < 4) {
564 Utils.oops(null,"Incorrect format of Piper diagram datafile '" + getAbbreviatedFilename() + "'!");
565 return false;
566 }
567 else {
568 data[0].addPoint(Double.parseDouble(st.nextToken()),
569 Double.parseDouble(st.nextToken()));
570 data[0].addPoint(Double.parseDouble(st.nextToken()),
571 Double.parseDouble(st.nextToken()));
572
573 // if there's one more point, it must be the TDS:
574 if (st.hasMoreTokens()) {
575 data[0].addPoint(Double.parseDouble(st.nextToken()),0.0);
576 }
577 dataArrays.add(data[0]);
578 }
579 }
580 else {
581
582 // It's a multiplot type of graph.
583 // Note that the multiplot graph can use only two columns (x,y),
584 // all eventually following columns are silently ignored
585 if (newDataSet) {
586 data[0] = new DataArray(10,getLinePars(k));
587 newDataSet = false;
588 }
589 data[0].addPoint(Double.parseDouble(st.nextToken()),
590 Double.parseDouble(st.nextToken()));
591 while (st.hasMoreTokens()) {
592 if (st.nextToken().equals("**")) {
593 for (int i=0; i<nCol; i++) {
594 if (getYColumn(i) == currentDataset) {
595 k++;
596 dataArrays.add(data[0]);
597 }
598 }
599 currentDataset++;
600 newDataSet = true;
601 break;
602 }
603 }
604 }
605 }
606 }
607 if (type == GraphPars.GRAPHTYPE_2D) {
608 for (int i=0; i<nCol; i++) dataArrays.add(data[i]);
609 }
610 in.close();
611 }
612 catch (Exception exception) {
613 Utils.oops(null,"Something's wrong with file '" +
614 getAbbreviatedFilename() +
615 "'.\nCheck the file's format and its content.");
616 return false;
617 }
618 return true;
619 }
620 }