001    package org.LiveGraph.settings;
002    
003    import static org.LiveGraph.settings.SettingsEvent.*;
004    
005    import java.awt.Color;
006    import java.io.FileInputStream;
007    import java.io.FileOutputStream;
008    import java.io.IOException;
009    import java.util.Properties;
010    
011    import org.LiveGraph.LiveGraph;
012    import org.LiveGraph.events.Event;
013    
014    import com.softnetConsult.utils.math.MathTools;
015    import com.softnetConsult.utils.string.StringTools;
016    
017    /**
018     * Encapsulates settings concerned with the data graphs and the plot canvas.
019     * 
020     * <p style="font-size:smaller;">This product includes software developed by the
021     *    <strong>LiveGraph</strong> project and its contributors.<br />
022     *    (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>)<br />
023     *    Copyright (c) 2007-2008 G. Paperin.<br />
024     *    All rights reserved.
025     * </p>
026     * <p style="font-size:smaller;">File: GraphSettings.java</p> 
027     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
028     *    without modification, are permitted provided that the following terms and conditions are met:
029     * </p>
030     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
031     *    acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
032     *    this list of conditions and the following disclaimer.<br />
033     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
034     *    LiveGraph project and its web-site, the above copyright notice, this list of conditions
035     *    and the following disclaimer in the documentation and/or other materials provided with
036     *    the distribution.<br />
037     *    3. All advertising materials mentioning features or use of this software or any derived
038     *    software must display the following acknowledgement:<br />
039     *    <em>This product includes software developed by the LiveGraph project and its
040     *    contributors.<br />(http://www.live-graph.org)</em><br />
041     *    4. All advertising materials distributed in form of HTML pages or any other technology
042     *    permitting active hyper-links that mention features or use of this software or any
043     *    derived software must display the acknowledgment specified in condition 3 of this
044     *    agreement, and in addition, include a visible and working hyper-link to the LiveGraph
045     *    homepage (http://www.live-graph.org).
046     * </p>
047     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
048     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
049     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
050     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
051     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
052     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
053     * </p>
054     * 
055     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
056     * @version {@value org.LiveGraph.LiveGraph#version}
057     */
058    public class GraphSettings extends ObservableSettings {
059    
060    /**
061     * Standard file extension.
062     */
063    public static final String preferredFileExtension = ".lggs";
064    
065    /**
066     * Possible types for the vertical grid.
067     */
068    public static enum VGridType {
069            /**
070             * No vertical grid.
071             */
072            VGrid_None,
073            
074            /**
075             * Vertical grid is based on dataset indices.
076             */
077            VGrid_DSNumAligned,
078            
079            /**
080             * Vertical grid is based on X data values. 
081             */
082            VGrid_XAUnitAligned
083    };
084    
085    /**
086     * Possible types for the horizontal grid.
087     */
088    public static enum HGridType {
089            /**
090             * No horisontal grid.
091             */
092            HGrid_None,
093            
094            /**
095             * Horisontal grid is based on Y data values.
096             */
097            HGrid_Simple
098    };
099    
100    /**
101     * Possible types for the x-axis.
102     */
103    public static enum XAxisType {
104            /**
105             * Use dataset numbers as x-axis units.
106             */
107            XAxis_DSNum,
108            
109            /**
110             * Plot against a specified data series
111             * (use the values of a specified data series as x-axis values).
112             */
113            XAxis_DataValSimple,
114            
115            /**
116             * Plot against a specified data series transformed into the unit interval
117             * (use the values of a specified data series linearly transformed into the
118             * interval [0..1] as x-axis values).
119             */
120            XAxis_DataValTrans0to1,
121            
122            /**
123             * Plot against a specified data series scaled by a specified constant
124             * (use the values of a specified data series multiplied by a specified value as x-axis values).
125             */
126            XAxis_DataValScaleBySetVal,
127            
128            /**
129             * Plot against the logarithm of a specified data series to a specified base
130             * (use the values of logarithms of a specified data series taken to a specified base as x-axis values). 
131             */
132            XAxis_DataValLogToSetBase,
133            
134            /**
135             * Plot against time
136             * (assume that the values of a specified data series represent seconds taken to a specified power
137             * of 10 (e.g. if {@code -3} is specified, the values represent milliseconds); plot against
138             * that axis while appropriately formatting the time (e.g. {@code hhh:mm:ss.xxx}).
139             */
140            XAxis_DataValSecsToSetPower
141    };
142    
143    
144    /**
145     * Bottom edge of the visible data area.
146     * Default value: {@code Double.NaN} ({@code Auto}).
147     */
148    private double minY = Double.NaN;
149    
150    /**
151     * Top edge of the visible data area.
152     * Default value: {@code Double.NaN} ({@code Auto}).
153     */
154    private double maxY = Double.NaN;
155    
156    /**
157     * Left edge of the visible data area.
158     * Default value: {@code Double.NaN} ({@code Auto}).
159     */
160    private double minX = Double.NaN;
161    
162    /**
163     * Right edge of the visible data area.
164     * Default value: {@code Double.NaN} ({@code Auto}).
165     */
166    private double maxX = Double.NaN;
167    
168    /**
169     * Type of the vertical grid.
170     * Default value: {@code VGridType.VGrid_None}.
171     */
172    private VGridType vGridType = VGridType.VGrid_None;
173    
174    /**
175     * Spacing of the vertical grid in data coordinates.
176     * Default value: {@code 50}.
177     */
178    private double vGridSize = 50.0;
179    
180    /**
181     * Color of the vertical grid.
182     * Default value: {@code #C0C0C0}.
183     */
184    private Color vGridColour = new Color(Integer.parseInt("C0", 16),
185                                                                              Integer.parseInt("C0", 16),
186                                                                              Integer.parseInt("C0", 16));
187    
188    /**
189     * Type of the horisontal grid.
190     * Default value: {@code HGridType.HGrid_None}.
191     */
192    private HGridType hGridType = HGridType.HGrid_None;
193    
194    /**
195     * Spacing of the horisontal grid in data coordinates.
196     * Default value: {@code 50}.
197     */
198    private double hGridSize = 50.0;
199    
200    /**
201     * Color of the horisontal grid.
202     * Default value: {@code #C0C0C0}.
203     */
204    private Color hGridColour = new Color(Integer.parseInt("C0", 16),
205                                                                              Integer.parseInt("C0", 16),
206                                                                              Integer.parseInt("C0", 16));
207    
208    /**
209     * Against what to plot the values.
210     * Default value: {@code XAxisType.XAxis_DSNum}.
211     */
212    private XAxisType xAxisType = XAxisType.XAxis_DSNum;
213    
214    /**
215     * Against which data series to plot (index).
216     * Default value: {@code 0}.
217     */
218    private int xAxisSeriesIndex = 0;
219    
220    /**
221     * Parameter for transformation of x-values.
222     * Default value: {@code 1}.
223     */
224    private double xAxisParamValue = 1.0;
225    
226    /**
227     * Whether to highlight data points as mouse is moved over the graph.
228     * Default value: {@code true}.
229     */
230    private boolean highlightDataPoints = true;
231    
232    
233    /**
234     * Creates a new graph settings object with the default settings.
235     */
236    public GraphSettings() {
237            super();
238            checkDisableHighlightingForOldJava();
239    }
240    
241    /**
242     * Creates a new graph settings object and loads the settings from the specified file.
243     * 
244     * @param fileName File to load the settigs from.
245     */
246    public GraphSettings(String fileName) {
247            this();
248            load(fileName);
249    }
250    
251    /**
252     * Loads the settings from the specified file.
253     * 
254     * @param fileName File to load the settigs from.
255     * @return {@code true} if the settings were loaded, {@code false} if an exception occured.
256     */
257    public boolean load(String fileName) {
258            
259            // Check observers:
260            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_Load, fileName);
261            if (null == actionEvent)
262                    return false;
263            
264            // Used for loading:
265            Properties values = new Properties();
266            
267            // Read from file:
268            try {
269                    FileInputStream in = new FileInputStream(fileName);
270                    try {
271                            values.loadFromXML(in);
272                    } finally {
273                            in.close();
274                    }
275            } catch(IOException e) {
276                    e.printStackTrace();
277                    return false;
278            }
279                            
280            // Parse values and set:
281                            
282            String s = values.getProperty("MinY", "Auto");
283            if ("Auto".equalsIgnoreCase(s)) {
284                    minY = Double.NaN;
285            } else {
286                    try {
287                            minY =  StringTools.parseDouble(s);
288                    } catch (NumberFormatException e) {
289                            minY =  Double.parseDouble(s);
290                    }
291            }
292            
293            
294            s = values.getProperty("MaxY", "Auto");
295            if ("Auto".equalsIgnoreCase(s)) {
296                    maxY = Double.NaN;
297            } else {
298                    try {
299                            maxY =  StringTools.parseDouble(s);
300                    } catch (NumberFormatException e) {
301                            maxY =  Double.parseDouble(s);
302                    }
303            }
304            
305            
306            s = values.getProperty("MinX", "Auto");
307            if ("Auto".equalsIgnoreCase(s)) {
308                    minX = Double.NaN;
309            } else {
310                    try {
311                            minX =  StringTools.parseDouble(s);
312                    } catch (NumberFormatException e) {
313                            minX =  Double.parseDouble(s);
314                    }
315            }
316            
317            
318            s = values.getProperty("MaxX", "Auto");
319            if ("Auto".equalsIgnoreCase(s)) {
320                    maxX = Double.NaN;
321            } else {
322                    try {
323                            maxX =  StringTools.parseDouble(s);
324                    } catch (NumberFormatException e) {
325                            maxX =  Double.parseDouble(s);
326                    }
327            }
328            
329            
330            s = values.getProperty("VGridType", "VGrid_None");
331            vGridType = VGridType.VGrid_None;
332            for (VGridType t : VGridType.values()) {
333                    if (t.name().equalsIgnoreCase(s)) {
334                            vGridType = t;
335                            break;
336                    }
337            }
338            
339            
340            s = values.getProperty("VGridSize", "50");
341            try {
342                    vGridSize = StringTools.parseDouble(s);
343            } catch (NumberFormatException e) {
344                    vGridSize =  Double.parseDouble(s);
345            }
346            if (Double.isNaN(vGridSize) || Double.isInfinite(vGridSize) || 0.0 > vGridSize)
347                    vGridSize = 0.0;        
348            
349            
350            s = values.getProperty("VGridColour", "C0C0C0");
351            int r, g, b;
352            try { r = Integer.parseInt(s.substring(0, 2), 16); } catch(NumberFormatException e) { r = 0; }
353            try { g = Integer.parseInt(s.substring(2, 4), 16); } catch(NumberFormatException e) { g = 0; }
354            try { b = Integer.parseInt(s.substring(4, 6), 16); } catch(NumberFormatException e) { b = 0; }
355            vGridColour = new Color(r, g, b);
356            
357            
358            s = values.getProperty("HGridType", "HGrid_None");
359            hGridType = HGridType.HGrid_None;
360            for (HGridType t : HGridType.values()) {
361                    if (t.name().equalsIgnoreCase(s)) {
362                            hGridType = t;
363                            break;
364                    }
365            }
366            
367            
368            s = values.getProperty("HGridSize", "50");
369            try {
370                    hGridSize = StringTools.parseDouble(s);
371            } catch (NumberFormatException e) {
372                    hGridSize =  Double.parseDouble(s);
373            }
374            if (Double.isNaN(hGridSize) || Double.isInfinite(hGridSize) || 0.0 > hGridSize)
375                    hGridSize = 0.0;        
376    
377            
378            s = values.getProperty("HGridColour", "C0C0C0");
379            try { r = Integer.parseInt(s.substring(0, 2), 16); } catch(NumberFormatException e) { r = 0; }
380            try { g = Integer.parseInt(s.substring(2, 4), 16); } catch(NumberFormatException e) { g = 0; }
381            try { b = Integer.parseInt(s.substring(4, 6), 16); } catch(NumberFormatException e) { b = 0; }
382            hGridColour = new Color(r, g, b);
383            
384            
385            s = values.getProperty("XAxisType", "XAxis_DSNum");
386            xAxisType = XAxisType.XAxis_DSNum;
387            for (XAxisType t : XAxisType.values()) {
388                    if (t.name().equalsIgnoreCase(s)) {
389                            xAxisType = t;
390                            break;
391                    }
392            }
393            if (s.equalsIgnoreCase("XAxis_DataValScaledSet")) // Support ver 1.1.2 and prev:
394                    xAxisType = XAxisType.XAxis_DataValScaleBySetVal;
395            
396            
397            s = values.getProperty("XAxisSeriesIndex", "0");
398            xAxisSeriesIndex = Integer.parseInt(values.getProperty("XAxisSeriesIndex"));
399            if (0 > xAxisSeriesIndex)
400                    xAxisSeriesIndex = 0;   
401            
402            
403            s = values.getProperty("XAxisParamValue");
404            if (null == s) // Compatibility with version 1.1.2 and before:
405                    values.getProperty("XAxisScaleValue");
406            if (null == s)
407                    s = "1";
408            try {
409                    xAxisParamValue = StringTools.parseDouble(s);
410            } catch (NumberFormatException e) {
411                    xAxisParamValue =  Double.parseDouble(s);
412            }
413            if (Double.isNaN(xAxisParamValue) || Double.isInfinite(xAxisParamValue))
414                    xAxisParamValue = 1.0;
415            xAxisParamValue = ensureGoodXAxisParameter(xAxisType, xAxisParamValue);
416    
417            
418            s = values.getProperty("HighlightDataPoints", "1");
419            highlightDataPoints = "1".equalsIgnoreCase(s); 
420            if (values.containsKey("SavedWithIncompatibleJavaVersion")
421                            && "1".equalsIgnoreCase(values.getProperty("SavedWithIncompatibleJavaVersion"))
422                            && !highlightDataPoints
423                            && LiveGraph.application().runsCorrectJavaVersion()) {
424                    highlightDataPoints = true;
425            }       
426            checkDisableHighlightingForOldJava();
427            
428            
429            notifyObservers(actionEvent);
430            return true;
431    }
432    
433    /**
434     * Saves the settings to a specified file.
435     * 
436     * @param fileName The file to save the settings to.
437     * @return {@code true} if the settings were saved, {@code false} if an exception occured. 
438     */
439    public boolean save(String fileName) {
440            
441            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_Save, fileName);
442            if (null == actionEvent)
443                    return false;
444            
445            Properties values = new Properties();
446            
447            if(!LiveGraph.application().runsCorrectJavaVersion())
448                    values.setProperty("SavedWithIncompatibleJavaVersion", "1");
449            
450            values.setProperty("MinY", Double.isNaN(getMinY()) ? "Auto" : StringTools.toString(getMinY()));
451            values.setProperty("MaxY", Double.isNaN(getMaxY()) ? "Auto" : StringTools.toString(getMaxY()));
452            values.setProperty("MinX", Double.isNaN(getMinX()) ? "Auto" : StringTools.toString(getMinX()));
453            values.setProperty("MaxX", Double.isNaN(getMaxX()) ? "Auto" : StringTools.toString(getMaxX()));
454            
455            values.setProperty("VGridType", getVGridType().name());
456            values.setProperty("VGridSize", StringTools.toString(getVGridSize()));
457            values.setProperty("VGridColour", String.format("%02x%02x%02x",
458                                                                                                            getVGridColour().getRed(),
459                                                                                                            getVGridColour().getGreen(),
460                                                                                                            getVGridColour().getBlue()));
461            
462            values.setProperty("HGridType", getHGridType().name());
463            values.setProperty("HGridSize", StringTools.toString(getHGridSize()));
464            values.setProperty("HGridColour", String.format("%02x%02x%02x",
465                                                                                                            getHGridColour().getRed(),
466                                                                                                            getHGridColour().getGreen(),
467                                                                                                            getHGridColour().getBlue()));
468            
469            values.setProperty("XAxisType", getXAxisType().name());
470            values.setProperty("XAxisSeriesIndex", Integer.toString(getXAxisSeriesIndex()));
471            values.setProperty("XAxisParamValue", StringTools.toString(getXAxisParamValue()));
472            
473            values.setProperty("HighlightDataPoints", getHighlightDataPoints() ? "1" : "0");
474            
475            try {
476                    
477                    FileOutputStream out = new FileOutputStream(fileName);
478                    try {
479                            values.storeToXML(out, "LiveGraph version " + LiveGraph.version + ". GraphSettings.");
480                    } finally {
481                            out.close();
482                    }
483                    
484                    notifyObservers(actionEvent);
485                    return true;
486                    
487            } catch(IOException e) {
488                    e.printStackTrace();
489                    return false;
490            }
491    }
492    
493    /**
494     * Gets the minimum Y value for the plot viewport.
495     * 
496     * @return The minimum value along the Y axis for the graph view
497     * or {@code java.lang.Double.NaN} if the current global minimum of all data series
498     * should be used instead.  
499     */
500    public double getMinY() {
501            return minY;
502    }
503    
504    /**
505     * Sets the minimum Y value for the plot viewport.
506     * 
507     * @param v The minimum value along the Y axis for the graph view
508     * or {@code java.lang.Double.NaN} if the global minimum of all data series
509     * should be used at all times.  
510     */
511    public void setMinY(double v) {
512            
513            if (v == getMinY())
514                    return;
515            
516            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_MinY, v);
517            if (null == actionEvent)
518                    return;
519            
520            minY = v;
521            notifyObservers(actionEvent);
522    }
523    
524    /**
525     * Gets the maximum Y value for the plot viewport.
526     * 
527     * @return The maximum value along the Y axis for the graph view
528     * or {@code java.lang.Double.NaN} if the current global maximum of all data series
529     * should be used instead.  
530     */
531    public double getMaxY() {
532            return maxY;
533    }
534    
535    /**
536     * Sets the maximum Y value for the plot viewport.
537     * 
538     * @param v The maximum value along the Y axis for the graph view
539     * or {@code java.lang.Double.NaN} if the global maximum of all data series
540     * should be used at all times.  
541     */
542    public void setMaxY(double v) {
543            
544            if (v == getMaxY())
545                    return;
546            
547            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_MaxY, v);
548            if (null == actionEvent)
549                    return;
550            
551            maxY = v;
552            notifyObservers(actionEvent);
553    }
554    
555    /**
556     * Gets the minimum X value for the plot viewport.
557     * 
558     * @return The minimum value along the X axis for the graph view
559     * or {@code java.lang.Double.NaN} if the currently smallest X value of all data series
560     * should be used instead.  
561     */
562    public double getMinX() {
563            return minX;
564    }
565    
566    /**
567     * Sets the minimum X value for the plot viewport.
568     * 
569     * @param v The minimum value along the X axis for the graph view
570     * or {@code java.lang.Double.NaN} if the smallest X value of all data series
571     * should be used at all times.  
572     */
573    public void setMinX(double v) {
574            
575            if (v == getMinX())
576                    return;
577            
578            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_MinX, v);
579            if (null == actionEvent)
580                    return;
581            
582            minX = v;
583            notifyObservers(actionEvent);
584    }
585    
586    /**
587     * Gets the maximum X value for the plot viewport.
588     * 
589     * @return The maximum value along the X axis for the graph view
590     * or {@code java.lang.Double.NaN} if the currently largest X value of all data series
591     * should be used instead.  
592     */
593    public double getMaxX() {
594            return maxX;
595    }
596    
597    /**
598     * Sets the maximum X value for the plot viewport.
599     * 
600     * @param v The maximum value along the X axis for the graph view
601     * or {@code java.lang.Double.NaN} if the largest X value of all data series
602     * should be used at all times.  
603     */
604    public void setMaxX(double v) {
605            
606            if (v == getMaxX())
607                    return;
608            
609            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_MaxX, v);
610            if (null == actionEvent)
611                    return;
612            
613            maxX = v;
614            notifyObservers(actionEvent);
615    }
616    
617    /**
618     * Sets the vertical grid type.
619     * 
620     * @return The vertical grid type.
621     */
622    public VGridType getVGridType() {
623            return vGridType;
624    }
625    
626    /**
627     * Gets the vertical grid type.
628     * 
629     * @param v The vertical grid type.
630     */
631    public void setVGridType(VGridType v) {
632            
633            if (null == v)
634                    v = VGridType.VGrid_None;
635            
636            if (v == getVGridType())
637                    return;
638            
639            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_VGridType, v);
640            if (null == actionEvent)
641                    return;
642            
643            vGridType = v;
644            notifyObservers(actionEvent);
645    }
646    
647    /**
648     * Gets the interval between the vertical grid bars.
649     * 
650     * @return The interval between the vertical grid bars.
651     */
652    public double getVGridSize() {
653            return vGridSize;
654    }
655    
656    /**
657     * Sets the interval between the vertical grid bars.
658     * 
659     * @param val The interval between the vertical grid bars.
660     */
661    public void setVGridSize(double val) {
662            
663            if (Double.isNaN(val) || Double.isInfinite(val) || 0.0 > val)
664                    val = 0.0;
665            
666            if (val == getVGridSize())
667                    return;
668            
669            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_VGridSize, val);
670            if (null == actionEvent)
671                    return;
672            
673            
674            vGridSize = val;
675            notifyObservers(actionEvent);
676    }
677    
678    /**
679     * Gets the colour to use for drawing the vertical grid bars.
680     * 
681     * @return The colour to use for drawing the vertical grid bars.
682     */
683    public Color getVGridColour() {
684            return vGridColour;
685    }
686    
687    /**
688     * Sets the colour to use for drawing the vertical grid bars.
689     * @param c The colour to use for drawing the vertical grid bars.
690     */
691    public void setVGridColour(Color c) {
692            
693            if (null == c)
694                    c = new Color(Integer.parseInt("C0", 16), Integer.parseInt("C0", 16), Integer.parseInt("C0", 16));
695            
696            if (c == getVGridColour())
697                    return;
698            
699            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_VGridColour, c);
700            if (null == actionEvent)
701                    return;
702            
703            vGridColour = c;
704            notifyObservers(actionEvent);
705    }
706    
707    /**
708     * Gets the horizontal grid type.
709     * @return The horizontal grid type.
710     */
711    public HGridType getHGridType() {       
712            return hGridType;
713    }
714    
715    /**
716     * Sets the horizontal grid type.
717     * @param v The horizontal grid type.
718     */
719    public void setHGridType(HGridType v) {
720            
721            if (null == v)
722                    v = HGridType.HGrid_None;
723            
724            if (v == getHGridType())
725                    return;
726            
727            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_HGridType, v);
728            if (null == actionEvent)
729                    return;
730            
731            hGridType = v;
732            notifyObservers(actionEvent);
733    }
734    
735    /**
736     * Gets the interval between the horizontal grib bars.
737     * 
738     * @return The interval between the horizontal grib bars.
739     */
740    public double getHGridSize() {
741            return hGridSize;
742    }
743    
744    /**
745     * Sets the interval between the horizontal grib bars.
746     * @param val The interval between the horizontal grib bars.
747     */
748    public void setHGridSize(double val) {  
749            
750            if (Double.isNaN(val) || Double.isInfinite(val) || 0.0 > val)
751                    val = 0.0;
752            
753            if (val == getHGridSize())
754                    return;
755            
756            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_HGridSize, val);
757            if (null == actionEvent)
758                    return;
759            
760            hGridSize = val;
761            notifyObservers(actionEvent);
762    }
763    
764    
765    /**
766     * Gets the colour for drawing the horizontal grid bars.
767     * 
768     * @return The colour for drawing the horizontal grid bars.
769     */
770    public Color getHGridColour() {
771            return hGridColour;
772    }
773    
774    /**
775     * Sets the colour for drawing the horizontal grid bars.
776     * 
777     * @param c The colour for drawing the horizontal grid bars.
778     */
779    public void setHGridColour(Color c) {   
780            
781            if (null == c)
782                    c = new Color(Integer.parseInt("C0", 16), Integer.parseInt("C0", 16), Integer.parseInt("C0", 16));
783            
784            if (c == getHGridColour())
785                    return;
786            
787            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_HGridColour, c);
788            if (null == actionEvent)
789                    return;
790            
791            hGridColour = c;
792            notifyObservers(actionEvent);
793    }
794    
795    /**
796     * Gets the type for the x-axis.
797     * 
798     * @return The type for the x-axis.
799     */
800    public XAxisType getXAxisType() {
801            return xAxisType;
802    }
803    
804    /**
805     * Sets the type for the x-axis.
806     * 
807     * @param v The type for the x-axis.
808     */
809    public void setXAxisType(XAxisType v) {
810            
811            if (null == v)
812                    v = XAxisType.XAxis_DSNum;
813            
814            if (v == getXAxisType())
815                    return;
816            
817            double p = getXAxisParamValue();
818            double np = ensureGoodXAxisParameter(v, p);
819            
820            int xAxisSerInd = getXAxisSeriesIndex();
821            
822            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_XAxisType, false, xAxisSerInd, np, v);
823            if (null == actionEvent)
824                    return;
825            
826            if (p != np && null == checkObservers(GS_XAxisParamValue, false, xAxisSerInd, np, v))
827                    return;
828            
829            xAxisType = v;
830            
831            if (p != np)
832                    setXAxisParamValue(np);
833            
834            notifyObservers(actionEvent);
835    }
836    
837    /**
838     * Gets the index of the data series to use as the x-axis.
839     * 
840     * @return The index of the data series to use as the x-axis.
841     */
842    public int getXAxisSeriesIndex() {
843            return xAxisSeriesIndex;
844    }
845    
846    /**
847     * Sets the index of the data series to use as the x-axis.
848     * 
849     * @param val The index of the data series to use as the x-axis.
850     */
851    public void setXAxisSeriesIndex(int val) {
852            
853            if (0 > val)
854                    val = 0;
855            
856            if (val == getXAxisSeriesIndex())
857                    return;
858            
859            double axisParam = getXAxisParamValue();
860            XAxisType axisType = getXAxisType();
861            
862            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_XAxisSeriesIndex,
863                                                                                                                                    false, val, axisParam, axisType);
864            if (null == actionEvent)
865                    return;
866            
867            xAxisSeriesIndex = val;
868            notifyObservers(actionEvent);
869    }
870    
871    /**
872     * Gets the x-axis transformation parameter.
873     * 
874     * @return The x-axis transformation parameter.
875     */
876    public double getXAxisParamValue() {
877            return xAxisParamValue;
878    }
879    
880    /**
881     * Sets x-axis transformation parameter.
882     * 
883     * @param val x-axis transformation parameter.
884     */
885    public void setXAxisParamValue(double val) {
886            
887            if (Double.isNaN(val) || Double.isInfinite(val))
888                    val = 1.0;
889            
890            if (val == getXAxisParamValue())
891                    return;
892            
893            XAxisType axisType = getXAxisType();
894            
895            val = ensureGoodXAxisParameter(axisType, val);
896            
897            int xAxisSeriesIndex = getXAxisSeriesIndex();
898            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_XAxisParamValue,
899                                                                                                                                    false, xAxisSeriesIndex, val, axisType);
900            if (null == actionEvent)
901                    return;
902            
903            xAxisParamValue = val;
904            notifyObservers(actionEvent);
905    }
906    
907    /**
908     * Gets whether the data points in the vicinity of the mouse cursor should be highlighted.
909     * (Note, this is a hidden setting and cannot be changes via the GUI.)
910     * 
911     * @return {@code true} if the data points in the vicinity of the mouse cursor should be highlighted,
912     * {@code false} otherwise.
913     */
914    public boolean getHighlightDataPoints() {
915            return highlightDataPoints;
916    }
917    
918    /**
919     * Gets whether the data points in the vicinity of the mouse cursor should be highlighted.
920     * (Note, this is a hidden setting and cannot be changes via the GUI.)
921     * 
922     * @param v {@code true} if the data points in the vicinity of the mouse cursor are to be highlighted,
923     * {@code false} otherwise.
924     */
925    public void setHighlightDataPoints(boolean v) {
926            
927            if (v == getHighlightDataPoints())
928                    return;
929            
930            Event<? extends SettingsEvent> actionEvent = checkObservers(GS_HighlightDataPoints, v);
931            if (null == actionEvent)
932                    return;
933            
934            boolean prev = highlightDataPoints;
935            highlightDataPoints = v;
936            checkDisableHighlightingForOldJava();
937            
938            if (prev != highlightDataPoints)
939                    notifyObservers(actionEvent);
940    }
941    
942    /**
943     * If the Java version is too old, data point highlighting is disabled.
944     */
945    private void checkDisableHighlightingForOldJava() {
946            if (!LiveGraph.application().runsCorrectJavaVersion())
947                    highlightDataPoints = false;
948    }
949    
950    /**
951     * Ensure that the transformation parameter has a legal value for the given
952     * x-axis type. The transformation parameter must be a real number. In addition,
953     * if the x-axis type is {@code XAxis_DataValLogToSetBase}, it must be
954     * non-negative and not 1.
955     * 
956     * @param xAxisType The x-axis type for which to verify the parameter.
957     * @param parameter The transfom parameter to check.
958     * @return The corrected transform parameter.
959     */
960    private double ensureGoodXAxisParameter(XAxisType xAxisType, double parameter) {
961            
962            if (Double.isInfinite(parameter))
963                    parameter = (parameter > 0. ? 1. : -1.);
964            
965            if (Double.isNaN(parameter))
966                    parameter = 0.;
967    
968            if (xAxisType == XAxisType.XAxis_DataValLogToSetBase) {
969                    
970                    double d = MathTools.log(parameter, 1.);
971                    if (Double.isNaN(d) || Double.isInfinite(d)) {
972                            
973                            if (0 > parameter)
974                                    parameter = -parameter;
975                            if (1. == parameter)
976                                    parameter = 0.;
977                    }
978            }
979            
980            return parameter;
981    }
982    
983    } // public class GraphSettings