001    package org.LiveGraph.settings;
002    
003    import java.awt.Color;
004    import java.io.FileInputStream;
005    import java.io.FileOutputStream;
006    import java.io.IOException;
007    import java.util.ArrayList;
008    import java.util.List;
009    import java.util.Properties;
010    
011    import org.LiveGraph.LiveGraph;
012    
013    
014    /**
015     * Ecapsulates the settings concerned with plotting each of the data series.
016     * 
017     * <p style="font-size:smaller;">This product includes software developed by the
018     *    <strong>LiveGraph</strong> project and its contributors.<br />
019     *    (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>)<br />
020     *    Copyright (c) 2007 G. Paperin.<br />
021     *    All rights reserved.
022     * </p>
023     * <p style="font-size:smaller;">File: DataSeriesSettings.java</p> 
024     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
025     *    without modification, are permitted provided that the following terms and conditions are met:
026     * </p>
027     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
028     *    acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
029     *    this list of conditions and the following disclaimer.<br />
030     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
031     *    LiveGraph project and its web-site, the above copyright notice, this list of conditions
032     *    and the following disclaimer in the documentation and/or other materials provided with
033     *    the distribution.<br />
034     *    3. All advertising materials mentioning features or use of this software or any derived
035     *    software must display the following acknowledgement:<br />
036     *    <em>This product includes software developed by the LiveGraph project and its
037     *    contributors.<br />(http://www.live-graph.org)</em><br />
038     *    4. All advertising materials distributed in form of HTML pages or any other technology
039     *    permitting active hyper-links that mention features or use of this software or any
040     *    derived software must display the acknowledgment specified in condition 3 of this
041     *    agreement, and in addition, include a visible and working hyper-link to the LiveGraph
042     *    homepage (http://www.live-graph.org).
043     * </p>
044     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
045     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
046     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
047     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
048     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
049     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
050     * </p>
051     * 
052     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
053     * @version {@value org.LiveGraph.LiveGraph#version}
054     */
055    public class DataSeriesSettings extends ObservableSettings implements SettingsObserver {
056    
057    /**
058     * The default file extension.
059     */
060    public static final String preferredFileExtension = ".lgdss";
061    
062    /**
063     * The transformation mode to the series values.
064     */
065    public static enum TransformMode { Transform_None, Transform_In0to1, Transform_SetVal };
066    
067    /**
068     * Holds the current settigs.
069     */
070    private List<SeriesParameters> settings = null;
071    
072    /**
073     * Holds the default colours.
074     */
075    private List<Color> defaultColours = null;
076    
077    
078    /**
079     * Creates a new data series settings object and initialises it with default values.
080     */
081    public DataSeriesSettings() {   
082            createDefaultColours();
083            settings = new ArrayList<SeriesParameters>();
084    }
085    
086    /**
087     * Creates a new data series settings object and loads the settigs from the specified file.
088     * 
089     * @param fileName The file name to use.
090     */
091    public DataSeriesSettings(String fileName) {
092            this();
093            load(fileName);
094    }
095    
096    /**
097     * Creates a set of "nice" default colours for the plot.
098     */
099    private void createDefaultColours() {
100            final int DEF_COLOURS_COUNT = 14;
101            
102            defaultColours = new ArrayList<Color>(DEF_COLOURS_COUNT);
103            
104            for (int i = 0; i < DEF_COLOURS_COUNT; i++) {
105                    
106                    float h = (2.f / (float) DEF_COLOURS_COUNT) * ((float) i);
107                    float s = (0 == (2 * i / DEF_COLOURS_COUNT) % 2 ? 1.f : .5f);
108                    float b = (0 == i % 2 ? .7f : 1.f);
109                    
110                    Color col = Color.getHSBColor(h, s, b);
111                    defaultColours.add(col);
112            }
113    }
114    
115    // Default values for the series is none other spacified:
116    
117    private boolean                 getDefaultShow(int serInd)                      { return true; }
118    private Color                   getDefaultColour(int serInd)            { return defaultColours.get(serInd % defaultColours.size()); }
119    private TransformMode   getDefaultTransformMode(int serInd)     { return TransformMode.Transform_None; }
120    private double                  getDefaultScaleFactor(int serInd)       { return 100.; }
121    
122    
123    /**
124     * Ensures that this settings container contains at least the settings for the data
125     * series with the specified index and all indices before that. If this settings
126     * object does not yet contain any settings for any of the series with these indices,
127     * new settings data structures will be created and initialised with default values. 
128     *  
129     * @param maxSeriesIndex It will be ensured that this container contains settings for
130     * at least all data series up to this index.
131     */
132    private void ensureLength(int maxSeriesIndex) {
133            while (settings.size() < maxSeriesIndex + 1) {
134                    int newSerInd = settings.size();
135                    SeriesParameters params = new SeriesParameters(getDefaultShow(newSerInd),
136                                                                                                               getDefaultColour(newSerInd),
137                                                                                                               getDefaultTransformMode(newSerInd),
138                                                                                                               getDefaultScaleFactor(newSerInd));           
139                    settings.add(params);
140                    notifyObservers("NewDefault." + newSerInd);
141            }
142    }
143    
144    /**
145     * Loads the settings from a specified file.
146     * 
147     * @param fileName The file to load the settings from.
148     * @return {@code true} if the settings were loaded, {@code false} if an exception occured. 
149     */
150    public boolean load(String fileName) {
151            Properties props = new Properties();
152            try {
153                    FileInputStream in = new FileInputStream(fileName);
154                    try { props.loadFromXML(in); }
155                    finally { in.close(); }         
156            } catch(IOException e) {
157                    e.printStackTrace();
158                    return false;
159            }
160            
161            int describedSeriesCount = 0;
162            try { describedSeriesCount = Integer.parseInt(props.getProperty("DescribedSeriesCount")); }
163            catch (NumberFormatException e) { return false; }
164            
165            settings.clear();
166            for (int i = 0; i < describedSeriesCount; i++) {
167                    try {
168                            
169                            boolean show = "1".equals(props.getProperty("Show."+i));
170                            
171                            String colS = props.getProperty("Colour."+i);
172                            int r = Integer.parseInt(colS.substring(0, 2), 16);
173                            int g = Integer.parseInt(colS.substring(2, 4), 16);
174                            int b = Integer.parseInt(colS.substring(4, 6), 16);
175                            Color col = new Color(r, g, b);
176                            
177                            String scaleS = props.getProperty("TransformMode."+i);
178                            TransformMode scale = TransformMode.Transform_None;
179                            if (TransformMode.Transform_In0to1.toString().equalsIgnoreCase(scaleS))
180                                    scale = TransformMode.Transform_In0to1;
181                            else if (TransformMode.Transform_SetVal.toString().equalsIgnoreCase(scaleS))
182                                    scale = TransformMode.Transform_SetVal;
183                            
184                            double param = Double.parseDouble(props.getProperty("ScaleFactor."+i));
185                            
186                            settings.add(new SeriesParameters(show, col, scale, param));
187                            
188                    } catch (NumberFormatException e) { }
189            }
190            
191            notifyObservers("load");
192            return true;
193    }
194    
195    
196    /**
197     * Saves the settings to a specified file.
198     * 
199     * @param fileName The file to save the settings to.
200     * @return {@code true} if the settings were saved, {@code false} if an exception occured. 
201     */
202    public boolean save(String fileName) {
203            
204            Properties props = new Properties();
205            props.setProperty("DescribedSeriesCount", Integer.toString(settings.size()));
206            for (int i = 0; i < settings.size(); i++) {
207                    SeriesParameters series = settings.get(i);
208                    props.setProperty("Show."+i,      series.show ? "1" : "0");
209                    props.setProperty("Colour."+i,    String.format("%02x%02x%02x", series.colour.getRed(),
210                                                                                                                                                    series.colour.getGreen(),
211                                                                                                                                                    series.colour.getBlue()));
212                    props.setProperty("TransformMode."+i, series.scale.toString());
213                    props.setProperty("ScaleFactor."+i, Double.toString(series.param));             
214            }       
215            
216            try {
217                    FileOutputStream out = new FileOutputStream(fileName);  
218                    try { props.storeToXML(out, "LiveGraph version " + LiveGraph.version + ". DataSeriesSettings."); }
219                    finally { out.close(); }
220                    return true;
221            } catch(IOException e) {
222                    e.printStackTrace();
223                    return false;
224            }
225    }
226    
227    /**
228     * Sets whether the data series with the specified index should be included in tthe plot.
229     * 
230     * @param seriesIndex A data series index (corresponds to the column index in the data file).
231     * @param show {@code true} if the data series with the specified index is to be included in the plot,
232     * {@code false} otherwise.
233     */
234    public void setShow(int seriesIndex, boolean show) {
235            ensureLength(seriesIndex);
236            settings.get(seriesIndex).show = show;
237            notifyObservers("Show." + seriesIndex);
238    }
239    
240    /**
241     * Sets whether the data series between the specified indices should be included in the plot.
242     * 
243     * @param from Starting data series index (inclusive).
244     * @param to Finishing data series index (inclusive).
245     * @param show {@code true} if the data series with the specified index is to be included in tthe plot,
246     * {@code false} otherwise.
247     */
248    public void setShowAll(int from, int to, boolean show) {
249            if (from > to) {
250                    int t = from; from = to; to = t;
251            }
252                    
253            ensureLength(to);
254            for (int i = from; i <= to; i++)
255                    settings.get(i).show = show;
256            notifyObservers("Show");
257    }
258    
259    /**
260     * Toggles whether the data series between the specified indices should be included in the plot.
261     * 
262     * @param from Starting data series index (inclusive).
263     * @param to Finishing data series index (inclusive).
264     */
265    public void setShowToggleAll(int from, int to) {
266            if (from > to) {
267                    int t = from; from = to; to = t;
268            }
269                    
270            ensureLength(to);
271            for (int i = from; i <= to; i++)
272                    settings.get(i).show = !settings.get(i).show;
273            notifyObservers("Show");
274    }
275    
276    /**
277     * Sets the colour for the plot of the data series with the specified index.
278     * 
279     * @param seriesIndex A data series index (corresponds to the column index in the data file).
280     * @param colour The colour for the plot of the data series with the specified index.
281     */
282    public void setColour(int seriesIndex, Color colour) {
283            if (null == colour)
284                    throw new NullPointerException("Null colour is not allowed.");
285            ensureLength(seriesIndex);
286            settings.get(seriesIndex).colour = colour;
287            notifyObservers("Colour." + seriesIndex);
288    }
289    
290    /**
291     * Sets the transformation mode for the plotted values of the data series with the specified index.
292     *  
293     * @param seriesIndex A data series index (corresponds to the column index in the data file).
294     * @param transformMode The transformation mode for the plotted values of the data series with the specified index.
295     */
296    public void setTransformMode(int seriesIndex, TransformMode transformMode) {
297            if (null == transformMode)
298                    throw new NullPointerException("Null scale mode is not allowed.");
299            ensureLength(seriesIndex);
300            settings.get(seriesIndex).scale = transformMode;
301            notifyObservers("TransformMode." + seriesIndex);
302    }
303    
304    /**
305     * Sets the parameter for the transformation of the plotted values of the data series with the specified index;
306     * this parameter is currently required only for the mode {@code Transform_SetVal};
307    
308     * @param seriesIndex A data series index (corresponds to the column index in the data file).
309     * @param parameter The parameter for the transformation of the plotted values of the data series with
310     * the specified index.
311     */
312    public void setScaleFactor(int seriesIndex, double parameter) {
313            ensureLength(seriesIndex);
314            settings.get(seriesIndex).param = parameter;
315            notifyObservers("ScaleFactor." + seriesIndex);
316    }
317    
318    /**
319     * Setts whether the data series with the specified index should be included in tthe plot.
320     * If no setting value has been defined for the specified series, a defalut value will be
321     * returned as specified by {@link #getDefaultShow(int)}.
322     * 
323     * @param seriesIndex A data series index (corresponds to the column index in the data file).
324     * @return {@code true} if the data series with the specified index is to be included in tthe plot,
325     * {@code false} otherwise.
326     * @see #getDefaultShow(int)
327     */
328    public boolean getShow(int seriesIndex) {
329            if (seriesIndex >= settings.size())
330                    return getDefaultShow(seriesIndex);
331            return settings.get(seriesIndex).show;
332    }
333    
334    /**
335     * Gets the colour for the plot of the data series with the specified index.
336     * If no setting value has been defined for the specified series, a defalut value will be
337     * returned as specified by {@link #getDefaultColour(int)}.
338     * 
339     * @param seriesIndex A data series index (corresponds to the column index in the data file).
340     * @return The colour for the plot of the data series with the specified index.
341     * @see #getDefaultColour(int)
342     */
343    public Color getColour(int seriesIndex) {
344            if (seriesIndex >= settings.size())
345                    return getDefaultColour(seriesIndex);
346            return settings.get(seriesIndex).colour;
347    }
348    
349    /**
350     * Gets the transformation mode for the plotted values of the data series with the specified index.
351     * If no setting value has been defined for the specified series, a defalut value will be
352     * returned as specified by {@link #getDefaultTransformMode(int)}.
353     * 
354     * @param seriesIndex A data series index (corresponds to the column index in the data file).
355     * @return The transformation mode for the plotted values of the data series with the specified index.
356     * @see #getDefaultTransformMode(int)
357     */
358    public TransformMode getTransformMode(int seriesIndex) {
359            if (seriesIndex >= settings.size())
360                    return getDefaultTransformMode(seriesIndex);
361            return settings.get(seriesIndex).scale;
362    }
363    
364    /**
365     * Gets the parameter for the transformation of the plotted values of the data series with
366     * the specified index; this parameter is currently required only for the mode {@code Transform_SetVal}.
367     * If no setting value has been defined for the specified series, a defalut value will be
368     * returned as specified by {@link #getDefaultScaleFactor(int)}.
369     * 
370     * @param seriesIndex A data series index (corresponds to the column index in the data file).
371     * @return The parameter for the transformation of the plotted values of the data series with
372     * the specified index.
373     * @see #getDefaultScaleFactor(int)
374     */
375    public double getScaleFactor(int seriesIndex) {
376            if (seriesIndex >= settings.size())
377                    return getDefaultScaleFactor(seriesIndex);
378            return settings.get(seriesIndex).param;
379    }
380    
381    /**
382     * Data seties settings objects may listen to other settings objects in order to
383     * react to changed in other settings; currently data series settings react when
384     * a data series is set to be used as the x-axis.
385     * This method calls {@link #setSeriesMarkedAsXAxis(int)} to process this event.
386     * <br /><br />
387     * The corresponding series is then set to invisible, because practice suggests
388     * that users rarely want to plot the series against which all other series are
389     * plotted. If users want to have the series plotted anyway, they can switch
390     * it back on at any time.  
391     * 
392     * @see #setSeriesMarkedAsXAxis(int) 
393     */
394    public void settingHasChanged(ObservableSettings settings, Object info) {
395            
396            if (null == info || null == settings)
397                    return;
398            
399            if (! (settings instanceof GraphSettings))
400                    return;
401            
402            if (! (info instanceof String))
403                    return;
404            
405            if (!((String) info).equals("XAxisType") && !((String) info).equals("XAxisSeriesIndex"))
406                    return;
407            
408            GraphSettings gs = (GraphSettings) settings;
409            
410            if (GraphSettings.XAxisType.XAxis_DSNum == gs.getXAxisType())
411                    return;
412                            
413            setSeriesMarkedAsXAxis(gs.getXAxisSeriesIndex());
414    }
415    
416    /**
417     * Getts called when this settings container was registered as an observer to a
418     * graph settings container and some data series was set as being used as the
419     * x-axis in the observed graph settings container.
420     * <br /><br />
421     * The specified data series is set to invisible, because practice suggests
422     * that users rarely want to plot the series against which all other series are
423     * plotted. If users want to have the series plotted anyway, they can switch
424     * it back on at any time.
425     * 
426     * @param seriesIndex The index of the data series to be set to invisible
427     * (corresponds to the column index in the data file).
428     * @see #settingHasChanged(ObservableSettings, Object)
429     */
430    private void setSeriesMarkedAsXAxis(int seriesIndex) {
431            if (getShow(seriesIndex))
432                    setShow(seriesIndex, false);    
433    }
434    
435    /**
436     * This struct-class is used to group the settings for one data series in a single
437     * data structure.
438     */
439    public class SeriesParameters {
440    
441            /**
442             * Whether this data series should be shown at all.
443             */
444            public boolean show = false;
445            
446            /**
447             * Colour to use for this series.
448             */
449            public Color colour = null;
450            
451            /**
452             * Transformation mode for series values.
453             */
454            public TransformMode scale = null;
455            
456            /**
457             * Parameter for series' values transformation. 
458             */
459            public double param = Double.NaN;
460            
461            /**
462             * Creates an uninitialised series settings data structure.
463             */
464            public SeriesParameters() {}
465            
466            /**
467             * Creates an series settings data structure and initialises it with the specified values.
468             * 
469             * @param show Display?
470             * @param colour Line colour.
471             * @param scale Values transformation.
472             * @param param Transformation parameter.
473             */
474            public SeriesParameters(boolean show, Color colour, TransformMode scale, double param) {
475                    this.show = show;
476                    this.colour = colour;
477                    this.scale = scale;
478                    this.param = param;
479            }
480    } // private class SeriesParameters
481    
482    } // public class DataSeriesSettings