1 /* Copyright (c) 2002-2012 The University of the West Indies
2  *
3  * Contact: robert.lancashire@uwimona.edu.jm
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public
7  *  License as published by the Free Software Foundation; either
8  *  version 2.1 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package jspecview.common;
21 
22 import java.util.Hashtable;
23 import java.util.Map;
24 
25 import javajs.api.GenericColor;
26 import javajs.util.Lst;
27 import javajs.util.PT;
28 
29 
30 import org.jmol.util.Logger;
31 
32 import jspecview.source.JDXDataObject;
33 import jspecview.source.JDXSourceStreamTokenizer;
34 
35 /**
36  * <code>JDXSpectrum</code> implements the Interface Spectrum for the display of
37  * JDX Files.
38  *
39  * @author Bob Hanson
40  * @author Debbie-Ann Facey
41  * @author Khari A. Bryan
42  * @author Prof Robert J. Lancashire
43  */
44 public class Spectrum extends JDXDataObject {
45 
46   public String id = "";
47 
48   GenericColor fillColor;
49 
50   /**
51    * spectra that can never be displayed independently, or at least not by
52    * default; 2D slices, for example
53    */
54   private Lst<Spectrum> subSpectra;
55   private Lst<PeakInfo> peakList = new Lst<PeakInfo>();
56   private String peakXLabel, peakYLabel;
57   private PeakInfo selectedPeak, highlightedPeak;
58   private Spectrum convertedSpectrum;
59 
60   private double specShift = 0;
61   private double userYFactor = 1;
62 
63   private int currentSubSpectrumIndex;
64 
65   private boolean isForcedSubset;
66   private boolean exportXAxisLeftToRight;
67 
68 
69 
70 
71   public enum IRMode {
72     NO_CONVERT, TO_TRANS, TO_ABS, TOGGLE;
getMode(String value)73     public static IRMode getMode(String value) {
74     	switch (value == null ? 'I' : value.toUpperCase().charAt(0)) {
75     	case 'A':
76     		return TO_ABS;
77     	case 'T':
78     		return (value.equalsIgnoreCase("TOGGLE") ? TOGGLE : TO_TRANS);
79     	case 'N':
80     		return NO_CONVERT;
81     	default:
82     		return TOGGLE;
83     	}
84     }
85   }
86 
dispose()87   public void dispose() {
88   	// NO! NEVER DO THIS!!!
89   	// Just because a spectrum is no longer needed by a graphSet does not mean it
90   	// is gone.
91 //   if (subSpectra != null)
92 //    for (int i = 0; i < subSpectra.size(); i++)
93 //      if (subSpectra.get(i) != this)
94 //        subSpectra.get(i).dispose();
95 //    subSpectra = null;
96 //    parent = null;
97 //    peakList = null;
98 //    selectedPeak = null;
99   }
100 
isForcedSubset()101   public boolean isForcedSubset() {
102     return isForcedSubset;
103   }
104 
setId(String id)105   public void setId(String id) {
106     this.id = id;
107   }
108 
109   /**
110    * Constructor
111    */
Spectrum()112   public Spectrum() {
113     //System.out.println("initialize JDXSpectrum " + this);
114     headerTable = new Lst<String[]>();
115     xyCoords = new Coordinate[0];
116     parent = this;
117   }
118 
119   /**
120    * specifically for Abs/Trans conversion. Note that we do NOT carry over piUnitsY
121    *
122    * @return a copy of this <code>JDXSpectrum</code>
123    */
copy()124   public Spectrum copy() {
125     Spectrum newSpectrum = new Spectrum();
126     copyTo(newSpectrum);
127     newSpectrum.setPeakList(peakList, peakXLabel, null);
128     newSpectrum.fillColor = fillColor;
129     return newSpectrum;
130   }
131 
132   /**
133    * Returns the array of coordinates
134    *
135    * @return the array of coordinates
136    */
getXYCoords()137   public Coordinate[] getXYCoords() {
138     return getCurrentSubSpectrum().xyCoords;
139   }
140 
141 
getPeakList()142   public Lst<PeakInfo> getPeakList() {
143     return peakList;
144   }
145 
setPeakList(Lst<PeakInfo> list, String peakXLabel, String peakYLabel)146   public int setPeakList(Lst<PeakInfo> list, String peakXLabel, String peakYLabel) {
147     peakList = list;
148     this.peakXLabel = peakXLabel;
149     this.peakYLabel = peakYLabel;
150     for (int i = list.size(); --i >= 0; )
151       peakList.get(i).spectrum = this;
152     if (Logger.debugging)
153       Logger.info("Spectrum " + getTitle() + " peaks: " + list.size());
154     return list.size();
155   }
156 
selectPeakByFileIndex(String filePath, String index, String atomKey)157 	public PeakInfo selectPeakByFileIndex(String filePath, String index,
158 			String atomKey) {
159 		if (peakList != null && peakList.size() > 0
160 				&& (atomKey == null || sourceID.equals(index)))
161 			for (int i = 0; i < peakList.size(); i++)
162 				if (peakList.get(i).checkFileIndex(filePath, index, atomKey)) {
163 					System.out.println("selecting peak by FileIndex " + this + " "
164 							+ peakList.get(i));
165 					return (selectedPeak = peakList.get(i));
166 				}
167 		return null;
168 	}
169 
selectPeakByFilePathTypeModel(String filePath, String type, String model)170   public PeakInfo selectPeakByFilePathTypeModel(String filePath, String type, String model) {
171     if (peakList != null && peakList.size() > 0)
172       for (int i = 0; i < peakList.size(); i++)
173         if (peakList.get(i).checkFileTypeModel(filePath, type, model)) {
174           System.out.println("selecting peak byFilePathTypeModel " + this + " " + peakList.get(i));
175           return (selectedPeak = peakList.get(i));
176         }
177     return null;
178   }
179 
180   /**
181    * Find a matching spectrum by type (IR, 1HNMR,...) and model name
182    *
183    * @param type if null, then model is a sourceID to match
184    * @param model
185    * @return true for match
186    */
matchesPeakTypeModel(String type, String model)187   public boolean matchesPeakTypeModel(String type, String model) {
188   	if (type.equals("ID"))
189   		return (sourceID.equalsIgnoreCase(model));
190     if (peakList != null && peakList.size() > 0)
191       for (int i = 0; i < peakList.size(); i++)
192         if (peakList.get(i).checkTypeModel(type, model))
193           return true;
194     return false;
195   }
196 
197 
setSelectedPeak(PeakInfo peak)198   public void setSelectedPeak(PeakInfo peak) {
199     selectedPeak = peak;
200   }
201 
setHighlightedPeak(PeakInfo peak)202   public void setHighlightedPeak(PeakInfo peak) {
203     highlightedPeak = peak;
204   }
205 
getSelectedPeak()206   public PeakInfo getSelectedPeak() {
207     return selectedPeak;
208   }
209 
getModelPeakInfoForAutoSelectOnLoad()210   public PeakInfo getModelPeakInfoForAutoSelectOnLoad() {
211   	if (peakList != null)
212 	    for (int i = 0; i < peakList.size(); i++)
213 	      if (peakList.get(i).autoSelectOnLoad())
214 	        return peakList.get(i);
215     return null;
216   }
217 
218 
getAssociatedPeakInfo(int xPixel, Coordinate coord)219   public PeakInfo getAssociatedPeakInfo(int xPixel, Coordinate coord) {
220     selectedPeak = findPeakByCoord(xPixel, coord);
221     return (selectedPeak == null ? getBasePeakInfo() : selectedPeak);
222   }
223 
findPeakByCoord(int xPixel, Coordinate coord)224   public PeakInfo findPeakByCoord(int xPixel, Coordinate coord) {
225     if (coord != null && peakList != null && peakList.size() > 0) {
226       double xVal = coord.getXVal();
227       int iBest = -1;
228       double dBest = 1e100;
229       for (int i = 0; i < peakList.size(); i++) {
230         double d = peakList.get(i).checkRange(xPixel, xVal);
231         if (d < dBest) {
232         	dBest = d;
233         	iBest = i;
234         }
235       }
236       if (iBest >= 0)
237       	return peakList.get(iBest);
238     }
239     return null;
240   }
241 
getPeakTitle()242   public String getPeakTitle() {
243     return (selectedPeak != null ? selectedPeak.getTitle() : highlightedPeak != null ? highlightedPeak.getTitle() : getTitleLabel());
244   }
245 
246   private String titleLabel;
247 
getTitleLabel()248   public String getTitleLabel() {
249     if (titleLabel != null)
250       return titleLabel;
251     String type = (peakList == null || peakList.size() == 0 ?
252     		getQualifiedDataType() :
253     			peakList.get(0).getType());
254     if (type != null && type.startsWith("NMR")) {
255     	if (nucleusY != null && !nucleusY.equals("?")) {
256     		type = "2D" + type;
257     	} else {
258     		type = getNominalSpecFreq(nucleusX, getObservedFreq()) + " MHz " + nucleusX + " " + type;
259     	}
260     }
261     return titleLabel = (type != null && type.length() > 0 ? type + " " : "")
262     + getTitle();
263   }
264 
setNextPeak(Coordinate coord, int istep)265   public int setNextPeak(Coordinate coord, int istep) {
266     if (peakList == null || peakList.size() == 0)
267       return -1;
268     double x0 = coord.getXVal() + istep * 0.000001;
269     int ipt1 = -1;
270     int ipt2 = -1;
271     double dmin1 = Double.MAX_VALUE * istep;
272     double dmin2 = 0;
273     for (int i = peakList.size(); --i >= 0;) {
274       double x = peakList.get(i).getX();
275       if (istep > 0) {
276         if (x > x0 && x < dmin1) {
277           // nearest on right
278           ipt1 = i;
279           dmin1 = x;
280         } else if (x < x0 && x - x0 < dmin2) {
281           // farthest on left
282           ipt2 = i;
283           dmin2 = x - x0;
284         }
285       } else {
286         if (x < x0 && x > dmin1) {
287           // nearest on left
288           ipt1 = i;
289           dmin1 = x;
290         } else if (x > x0 && x - x0 > dmin2) {
291           // farthest on right
292           ipt2 = i;
293           dmin2 = x - x0;
294         }
295       }
296     }
297 
298     if (ipt1 < 0) {
299       if (ipt2 < 0)
300         return -1;
301       ipt1 = ipt2;
302     }
303     return ipt1;
304   }
305 
getPercentYValueAt(double x)306   public double getPercentYValueAt(double x) {
307     if (!isContinuous())
308       return Double.NaN;
309     return getYValueAt(x);
310   }
311 
getYValueAt(double x)312   public double getYValueAt(double x) {
313     return Coordinate.getYValueAt(xyCoords, x);
314   }
315 
316 
317   /**
318    * Set by JSViewer
319    *
320    * @param userYFactor
321    */
setUserYFactor(double userYFactor)322   public void setUserYFactor(double userYFactor) {
323     this.userYFactor = userYFactor;
324   }
325 
getUserYFactor()326   public double getUserYFactor() {
327     return userYFactor;
328   }
329 
330   public static final double MAXABS = 4; // maximum absorbance allowed
331 
getConvertedSpectrum()332   public Spectrum getConvertedSpectrum() {
333     return convertedSpectrum;
334   }
335 
setConvertedSpectrum(Spectrum spectrum)336   public void setConvertedSpectrum(Spectrum spectrum) {
337     convertedSpectrum = spectrum;
338   }
339 
taConvert(Spectrum spectrum, IRMode mode)340   public static Spectrum taConvert(Spectrum spectrum, IRMode mode) {
341     if (!spectrum.isContinuous())
342       return spectrum;
343     switch (mode) {
344     case NO_CONVERT:
345       return spectrum;
346     case TO_ABS:
347       if (!spectrum.isTransmittance())
348         return spectrum;
349       break;
350     case TO_TRANS:
351       if (!spectrum.isAbsorbance())
352         return spectrum;
353       break;
354     case TOGGLE:
355       break;
356     }
357     Spectrum spec = spectrum.getConvertedSpectrum();
358     return (spec != null ? spec : spectrum.isAbsorbance() ? toT(spectrum) : toA(spectrum));
359   }
360 
361   /**
362    * Converts a spectrum from Absorbance to Transmittance
363    *
364    * @param spectrum
365    *        the JDXSpectrum
366    * @return the converted spectrum
367    */
368 
toT(Spectrum spectrum)369   private static Spectrum toT(Spectrum spectrum) {
370     if (!spectrum.isAbsorbance())
371       return null;
372     Coordinate[] xyCoords = spectrum.getXYCoords();
373     Coordinate[] newXYCoords = new Coordinate[xyCoords.length];
374     if (!Coordinate.isYInRange(xyCoords, 0, MAXABS))
375       xyCoords = Coordinate.normalise(xyCoords, 0, MAXABS);
376     for (int i = 0; i < xyCoords.length; i++)
377       newXYCoords[i] = new Coordinate().set(xyCoords[i].getXVal(),
378           toTransmittance(xyCoords[i].getYVal()));
379     return newSpectrum(spectrum, newXYCoords, "TRANSMITTANCE");
380   }
381 
382   /**
383    * Converts a spectrum from Transmittance to Absorbance
384    *
385    * @param spectrum
386    *        the JDXSpectrum
387    * @return the converted spectrum
388    */
toA(Spectrum spectrum)389   private static Spectrum toA(Spectrum spectrum) {
390     if (!spectrum.isTransmittance())
391       return null;
392     Coordinate[] xyCoords = spectrum.getXYCoords();
393     Coordinate[] newXYCoords = new Coordinate[xyCoords.length];
394     boolean isPercent = Coordinate.isYInRange(xyCoords, -2, 2);
395     for (int i = 0; i < xyCoords.length; i++)
396       newXYCoords[i] = new Coordinate().set(xyCoords[i].getXVal(),
397           toAbsorbance(xyCoords[i].getYVal(), isPercent));
398     return newSpectrum(spectrum, newXYCoords, "ABSORBANCE");
399   }
400 
401   /**
402    * copy spectrum with new coordinates
403    *
404    * @param spectrum
405    * @param newXYCoords
406    * @param units
407    * @return new spectrum
408    */
newSpectrum(Spectrum spectrum, Coordinate[] newXYCoords, String units)409   public static Spectrum newSpectrum(Spectrum spectrum,
410                                          Coordinate[] newXYCoords,
411                                          String units) {
412     Spectrum specNew = spectrum.copy();
413     specNew.setOrigin("JSpecView Converted");
414     specNew.setOwner("JSpecView Generated");
415     specNew.setXYCoords(newXYCoords);
416     specNew.setYUnits(units);
417     spectrum.setConvertedSpectrum(specNew);
418     specNew.setConvertedSpectrum(spectrum);
419     return specNew;
420   }
421 
422   /**
423    * Converts a value in Transmittance to Absorbance -- max of MAXABS (4)
424    *
425    *
426    * @param x
427    * @param isPercent
428    * @return the value in Absorbance
429    */
toAbsorbance(double x, boolean isPercent)430   private static double toAbsorbance(double x, boolean isPercent) {
431     return (Math.min(MAXABS, isPercent ? 2 - log10(x) : -log10(x)));
432   }
433 
434   /**
435    * Converts a value from Absorbance to Transmittance
436    *
437    * @param x
438    * @return the value in Transmittance
439    */
toTransmittance(double x)440   private static double toTransmittance(double x) {
441     return (x <= 0 ? 1 : Math.pow(10, -x));
442   }
443 
444   /**
445    * Returns the log of a value to the base 10
446    *
447    * @param value
448    *        the input value
449    * @return the log of a value to the base 10
450    */
log10(double value)451   private static double log10(double value) {
452     return Math.log(value) / Math.log(10);
453   }
454 
process(Lst<Spectrum> specs, IRMode irMode)455   public static boolean process(Lst<Spectrum> specs, IRMode irMode) {
456     if (irMode == IRMode.TO_ABS || irMode == IRMode.TO_TRANS)
457       for (int i = 0; i < specs.size(); i++)
458         specs.set(i, taConvert(specs.get(i), irMode));
459     return true;
460   }
461 
getSubSpectra()462   public Lst<Spectrum> getSubSpectra() {
463     return subSpectra;
464   }
465 
getCurrentSubSpectrum()466   public Spectrum getCurrentSubSpectrum() {
467     return (subSpectra == null ? this : subSpectra.get(currentSubSpectrumIndex));
468   }
469 
advanceSubSpectrum(int dir)470   public int advanceSubSpectrum(int dir) {
471     return setCurrentSubSpectrum(currentSubSpectrumIndex + dir);
472   }
473 
setCurrentSubSpectrum(int n)474   public int setCurrentSubSpectrum(int n) {
475     return (currentSubSpectrumIndex = Coordinate.intoRange(n, 0, subSpectra.size() - 1));
476   }
477 
478   /**
479    * adds an nD subspectrum and titles it "Subspectrum <n>"
480    * These spectra can be iterated over using the UP and DOWN keys.
481    *
482    * @param spectrum
483    * @param forceSub
484    * @return true if was possible
485    */
addSubSpectrum(Spectrum spectrum, boolean forceSub)486   public boolean addSubSpectrum(Spectrum spectrum, boolean forceSub) {
487     if (!forceSub && (is1D() || blockID != spectrum.blockID)
488         || !allowSubSpec(this, spectrum))
489       return false;
490     isForcedSubset = forceSub; // too many blocks (>100)
491     if (subSpectra == null) {
492       subSpectra = new Lst<Spectrum>();
493       addSubSpectrum(this, true);
494     }
495     subSpectra.addLast(spectrum);
496     spectrum.parent = this;
497     //System.out.println("Added subspectrum " + subSpectra.size() + ": " + spectrum.y2D);
498     return true;
499   }
500 
getSubIndex()501   public int getSubIndex() {
502     return (subSpectra == null ? -1 : currentSubSpectrumIndex);
503   }
504 
setExportXAxisDirection(boolean leftToRight)505   public void setExportXAxisDirection(boolean leftToRight) {
506     exportXAxisLeftToRight = leftToRight;
507   }
isExportXAxisLeftToRight()508   public boolean isExportXAxisLeftToRight() {
509     return exportXAxisLeftToRight;
510   }
511 
getInfo(String key)512   public Map<String, Object> getInfo(String key) {
513     Map<String, Object> info = new Hashtable<String, Object>();
514     if ("id".equalsIgnoreCase(key)) {
515     	info.put(key, id);
516     	return info;
517     }
518     String keys = null;
519     if ("".equals(key)) {
520     	keys = "id specShift header";
521     }
522     info.put("id", id);
523     Parameters.putInfo(key, info, "specShift", Double.valueOf(specShift));
524     boolean justHeader = ("header".equals(key));
525     if (!justHeader && key != null && keys == null) {
526       for (int i = headerTable.size(); --i >= 0;) {
527       	String[] entry = headerTable.get(i);
528       	if (entry[0].equalsIgnoreCase(key) || entry[2].equalsIgnoreCase(key)) {
529       		info.put(key, entry[1]);
530       		return info;
531       	}
532       }
533     }
534     Map<String, Object> head = new Hashtable<String, Object>();
535     String[][] list = getHeaderRowDataAsArray();
536     for (int i = 0; i < list.length; i++) {
537       String label = JDXSourceStreamTokenizer.cleanLabel(list[i][0]);
538       if (keys != null) {
539       	keys += " " + label;
540       	continue;
541       }
542       if (key != null && !justHeader && !label.equals(key))
543         continue;
544       Object val = fixInfoValue(list[i][1]);
545       if (key == null) {
546         Map<String, Object> data = new Hashtable<String, Object>();
547         data.put("value", val);
548         data.put("index", Integer.valueOf(i + 1));
549         info.put(label, data);
550       } else {
551         info.put(label, val);
552       }
553     }
554     if (head.size() > 0)
555       info.put("header", head);
556     if (!justHeader) {
557     	if (keys != null) {
558     	keys += "  titleLabel type isHZToPPM subSpectrumCount";
559     	}
560     	else {
561 
562       Parameters.putInfo(key, info, "titleLabel", getTitleLabel());
563       Parameters.putInfo(key, info, "type", getDataType());
564       Parameters.putInfo(key, info, "isHZToPPM", Boolean.valueOf(isHZtoPPM()));
565       Parameters.putInfo(key, info, "subSpectrumCount", Integer
566           .valueOf(subSpectra == null ? 0 : subSpectra.size()));
567     	}
568     }
569     if (keys != null)
570     	info.put("KEYS", keys);
571     return info;
572   }
573 
fixInfoValue(String info)574   private static Object fixInfoValue(String info) {
575     try { return (Integer.valueOf(info)); } catch (Exception e) {}
576     try { return (Double.valueOf(info)); } catch (Exception e) {}
577     return info;
578   }
579 
findMatchingPeakInfo(PeakInfo pi)580   public PeakInfo findMatchingPeakInfo(PeakInfo pi) {
581     for (int i = 0; i < peakList.size(); i++)
582       if (peakList.get(i).checkTypeMatch(pi))
583         return peakList.get(i);
584     return null;
585   }
586 
getBasePeakInfo()587   public PeakInfo getBasePeakInfo() {
588     return (peakList.size() == 0 ? new PeakInfo() :
589       new PeakInfo(" baseModel=\"\" " + peakList.get(0)));
590   }
591 
592   /**
593    * checks in order: (1) Peaks tag attribute xUnits/yUnits,
594    * then (2) ##XLABEL/##YLABEL,
595    * then (3) ##XUNITS/##YUNITS
596    * @param isX
597    * @return  suitable label or ""
598    */
getAxisLabel(boolean isX)599   public String getAxisLabel(boolean isX) {
600     String label = (isX ? peakXLabel : peakYLabel);
601     if (label == null)
602       label = (isX ? xLabel : yLabel);
603     if (label == null)
604       label = (isX ? xUnits : yUnits);
605     return (label == null ? ""
606     		: label.equalsIgnoreCase("WAVENUMBERS") ? "1/cm"
607     	  : label.equalsIgnoreCase("nanometers") ? "nm"
608    			: label);
609   }
610 
findXForPeakNearest(double x)611 	public double findXForPeakNearest(double x) {
612 		return Coordinate.findXForPeakNearest(xyCoords, x, isInverted());
613 	}
614 
addSpecShift(double dx)615 	public double addSpecShift(double dx) {
616 		if (dx != 0) {
617 			specShift += dx;
618 			Coordinate.shiftX(xyCoords, dx);
619 			if (subSpectra != null)
620 				for (int i = subSpectra.size(); --i >= 0;) {
621 					Spectrum spec = subSpectra.get(i);
622 					if (spec != this && spec != parent)
623 						spec.addSpecShift(dx);
624 				}
625 		}
626 		return specShift;
627 	}
628 
allowSubSpec(Spectrum s1, Spectrum s2)629 	public static boolean allowSubSpec(Spectrum s1, Spectrum s2) {
630 		return (s1.is1D() == s2.is1D()
631 				&& s1.xUnits.equalsIgnoreCase(s2.xUnits)
632 				&& s1.isHNMR() == s2.isHNMR());
633 	}
634 
areXScalesCompatible(Spectrum s1, Spectrum s2, boolean isSubspecCheck, boolean isLinkCheck)635 	public static boolean areXScalesCompatible(Spectrum s1, Spectrum s2,
636 			boolean isSubspecCheck, boolean isLinkCheck) {
637 		boolean isNMR1 = s1.isNMR();
638 		// must be both NMR or both not NMR,
639 		// and both must be continuous (because of X scaling)
640 		// and must have same xUnits if not a link check
641 		if (isNMR1 != s2.isNMR()
642 				|| s1.isContinuous() != s2.isContinuous()
643 				|| !isLinkCheck && !areUnitsCompatible(s1.xUnits, s2.xUnits))
644 			return false;
645 		if (isSubspecCheck) {
646 			// must both be 1D (or both be 2D?) for adding subspectra
647 			if (s1.is1D() != s2.is1D())
648 				return false;
649 		} else if (isLinkCheck) {
650 			if (!isNMR1)
651 				return true;
652 			// we allow 1D/2D here
653 		} else if (!s1.is1D() || !s2.is1D()) {
654 			// otherwise we don't want to consider any 2D spectra
655 			return false;
656 		}
657 		// done if this is not NMR comparison
658 		// or check same nuclei // for now not going 1D-->2D
659 		return (!isNMR1 || s2.is1D() && s1.parent.nucleusX.equals(s2.parent.nucleusX));
660 	}
661 
areUnitsCompatible(String u1, String u2)662 	private static boolean areUnitsCompatible(String u1, String u2) {
663 		if (u1.equalsIgnoreCase(u2))
664 			return true;
665 		u1 = u1.toUpperCase();
666 		u2 = u2.toUpperCase();
667 		return (u1.equals("HZ") && u2.equals("PPM")
668 				|| u1.equals("PPM") && u2.equals("HZ"));
669 	}
670 
areLinkableX(Spectrum s1, Spectrum s2)671 	public static boolean areLinkableX(Spectrum s1, Spectrum s2) {
672 		return (s1.isNMR() && s2.isNMR() && s1.nucleusX.equals(s2.nucleusX));
673 	}
674 
areLinkableY(Spectrum s1, Spectrum s2)675 	public static boolean areLinkableY(Spectrum s1, Spectrum s2) {
676 		return (s1.isNMR() && s2.isNMR() && s1.nucleusX.equals(s2.nucleusY));
677 	}
678 
679 	// analysis fields
680 
getPeakWidth()681 	public float getPeakWidth() {
682 		double w = getLastX() - getFirstX();
683 		return (float) (w/100);
684 	}
685 
setSimulated(String filePath)686 	public void setSimulated(String filePath) {
687 		isSimulation = true;
688 		String s = sourceID;
689 		if (s.length() == 0)
690 			s = PT.rep(filePath, JSVFileManager.SIMULATION_PROTOCOL, "");
691 		if (s.indexOf("MOL=") >= 0)
692 			s = "";
693 		title = "SIMULATED " + PT.rep(s, "$", "");
694 
695 	}
696 
setFillColor(GenericColor color)697 	public void setFillColor(GenericColor color) {
698 		fillColor = color;
699 		if (convertedSpectrum != null)
700 			convertedSpectrum.fillColor = color;
701 	}
702 
703   @Override
toString()704   public String toString() {
705     return getTitleLabel() + (xyCoords == null ? "" : " xyCoords.length=" + xyCoords.length);
706   }
707 
708 
709 }