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 import org.LiveGraph.events.Event;
013
014 import com.softnetConsult.utils.collections.Pair;
015 import com.softnetConsult.utils.math.MathTools;
016 import com.softnetConsult.utils.string.StringTools;
017
018 import static org.LiveGraph.settings.SettingsEvent.*;
019
020
021 /**
022 * Ecapsulates the settings concerned with plotting each of the data series.
023 *
024 * <p style="font-size:smaller;">This product includes software developed by the
025 * <strong>LiveGraph</strong> project and its contributors.<br />
026 * (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>)<br />
027 * Copyright (c) 2007-2008 G. Paperin.<br />
028 * All rights reserved.
029 * </p>
030 * <p style="font-size:smaller;">File: DataSeriesSettings.java</p>
031 * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
032 * without modification, are permitted provided that the following terms and conditions are met:
033 * </p>
034 * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
035 * acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
036 * this list of conditions and the following disclaimer.<br />
037 * 2. Redistributions in binary form must reproduce the above acknowledgement of the
038 * LiveGraph project and its web-site, the above copyright notice, this list of conditions
039 * and the following disclaimer in the documentation and/or other materials provided with
040 * the distribution.<br />
041 * 3. All advertising materials mentioning features or use of this software or any derived
042 * software must display the following acknowledgement:<br />
043 * <em>This product includes software developed by the LiveGraph project and its
044 * contributors.<br />(http://www.live-graph.org)</em><br />
045 * 4. All advertising materials distributed in form of HTML pages or any other technology
046 * permitting active hyper-links that mention features or use of this software or any
047 * derived software must display the acknowledgment specified in condition 3 of this
048 * agreement, and in addition, include a visible and working hyper-link to the LiveGraph
049 * homepage (http://www.live-graph.org).
050 * </p>
051 * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY
052 * OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
053 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
054 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
055 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
056 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
057 * </p>
058 *
059 * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
060 * @version {@value org.LiveGraph.LiveGraph#version}
061 */
062 public class DataSeriesSettings extends ObservableSettings {
063
064 /**
065 * The default file extension.
066 */
067 public static final String preferredFileExtension = ".lgdss";
068
069 /**
070 * The transformation mode to the series values.
071 */
072 public static enum TransformMode {
073
074 /**
075 * Display the actual data value.
076 */
077 Transform_None,
078
079 /**
080 * Linearly transform the whole data series into the [0, 1] interval.
081 */
082 Transform_In0to1,
083
084 /**
085 * Multiply (scale) each data value by a specified constant.
086 */
087 Transform_ScaleBySetVal,
088
089 /**
090 * Take the logarithm of each data value to a specified base.
091 */
092 Transform_Logarithm
093 };
094
095
096 /**
097 * Holds the current settigs.
098 */
099 private List<SeriesParameters> settings = null;
100
101 /**
102 * Holds the default colours.
103 */
104 private List<Color> defaultColours = null;
105
106
107 /**
108 * Creates a new data series settings object and initialises it with default values.
109 */
110 public DataSeriesSettings() {
111 createDefaultColours();
112 settings = new ArrayList<SeriesParameters>();
113 }
114
115 /**
116 * Creates a new data series settings object and loads the settigs from the specified file.
117 *
118 * @param fileName The file name to use.
119 */
120 public DataSeriesSettings(String fileName) {
121 this();
122 load(fileName);
123 }
124
125 /**
126 * Creates a set of "nice" default colours for the plot.
127 */
128 private void createDefaultColours() {
129 final int DEF_COLOURS_COUNT = 14;
130
131 defaultColours = new ArrayList<Color>(DEF_COLOURS_COUNT);
132
133 for (int i = 0; i < DEF_COLOURS_COUNT; i++) {
134
135 float h = (2.f / (float) DEF_COLOURS_COUNT) * ((float) i);
136 float s = (0 == (2 * i / DEF_COLOURS_COUNT) % 2 ? 1.f : .5f);
137 float b = (0 == i % 2 ? .7f : 1.f);
138
139 Color col = Color.getHSBColor(h, s, b);
140 defaultColours.add(col);
141 }
142 }
143
144 // Default values for the series if none other spacified:
145
146 private boolean getDefaultShow(int s) { return true; }
147 private Color getDefaultColour(int s) { return defaultColours.get(s % defaultColours.size()); }
148 private TransformMode getDefaultTransformMode(int s) { return TransformMode.Transform_None; }
149 private double getDefaultTransformParam(int s) { return 1.0; }
150
151
152 /**
153 * Ensures that this settings container contains at least the settings for the data
154 * series with the specified index and all indices before that. If this settings
155 * object does not yet contain any settings for any of the series with these indices,
156 * new settings data structures will be created and initialised with default values.
157 *
158 * @param maxSeriesIndex It will be ensured that this container contains settings for
159 * at least all data series up to this index.
160 */
161 private void ensureLength(int maxSeriesIndex) {
162 while (settings.size() < maxSeriesIndex + 1) {
163 int newSerInd = settings.size();
164 SeriesParameters params = new SeriesParameters(getDefaultShow(newSerInd),
165 getDefaultColour(newSerInd),
166 getDefaultTransformMode(newSerInd),
167 getDefaultTransformParam(newSerInd));
168 settings.add(params);
169 }
170 }
171
172 /**
173 * Loads the settings from a specified file.
174 *
175 * @param fileName The file to load the settings from.
176 * @return {@code true} if the settings were loaded, {@code false} if an exception occured.
177 */
178 public boolean load(String fileName) {
179
180 Event<? extends SettingsEvent> actionEvent = checkObservers(DSS_Load, fileName);
181 if (null == actionEvent)
182 return false;
183
184 Properties props = new Properties();
185 try {
186 FileInputStream in = new FileInputStream(fileName);
187 try { props.loadFromXML(in); }
188 finally { in.close(); }
189 } catch(IOException e) {
190 e.printStackTrace();
191 return false;
192 }
193
194 int describedSeriesCount = 0;
195 try {
196 describedSeriesCount = Integer.parseInt(props.getProperty("DescribedSeriesCount"));
197 } catch (NumberFormatException e) {
198 return false;
199 }
200
201 settings.clear();
202 for (int i = 0; i < describedSeriesCount; i++) {
203 try {
204
205 boolean show = "1".equals(props.getProperty("Show."+i));
206
207 String colS = props.getProperty("Colour."+i);
208 int r = Integer.parseInt(colS.substring(0, 2), 16);
209 int g = Integer.parseInt(colS.substring(2, 4), 16);
210 int b = Integer.parseInt(colS.substring(4, 6), 16);
211 Color col = new Color(r, g, b);
212
213 String transModeS = props.getProperty("TransformMode."+i);
214 TransformMode transMode = TransformMode.Transform_None;
215 if (TransformMode.Transform_In0to1.toString().equalsIgnoreCase(transModeS))
216 transMode = TransformMode.Transform_In0to1;
217 else if (TransformMode.Transform_ScaleBySetVal.toString().equalsIgnoreCase(transModeS))
218 transMode = TransformMode.Transform_ScaleBySetVal;
219 else if ("Transform_SetVal".equalsIgnoreCase(transModeS)) // For compatibility with ver 1.1.2.
220 transMode = TransformMode.Transform_ScaleBySetVal;
221 else if (TransformMode.Transform_Logarithm.toString().equalsIgnoreCase(transModeS))
222 transMode = TransformMode.Transform_Logarithm;
223
224 double param = 1.0;
225 String paramStr = props.getProperty("TransformParam."+i);
226 // For compatibility with ver 1.1.2 and prior:
227 if (null == paramStr && props.containsKey("ScaleFactor."+i))
228 paramStr = props.getProperty("ScaleFactor."+i);
229
230 if (null != paramStr) {
231 try {
232 param = StringTools.parseDouble(paramStr);
233 } catch (NumberFormatException e) {
234 param = Double.parseDouble(paramStr);
235 }
236 }
237
238 param = ensureGoodTransformParam(transMode, param);
239
240 settings.add(new SeriesParameters(show, col, transMode, param));
241
242 } catch (NumberFormatException e) { }
243 }
244
245 notifyObservers(actionEvent);
246 return true;
247 }
248
249
250 /**
251 * Saves the settings to a specified file.
252 *
253 * @param fileName The file to save the settings to.
254 * @return {@code true} if the settings were saved, {@code false} if an exception occured.
255 */
256 public boolean save(String fileName) {
257
258 Event<? extends SettingsEvent> actionEvent = checkObservers(DSS_Save, fileName);
259 if (null == actionEvent)
260 return false;
261
262 Properties props = new Properties();
263 props.setProperty("DescribedSeriesCount", Integer.toString(settings.size()));
264 for (int i = 0; i < settings.size(); i++) {
265 SeriesParameters series = settings.get(i);
266 props.setProperty("Show."+i, series.show ? "1" : "0");
267 props.setProperty("Colour."+i, String.format("%02x%02x%02x", series.colour.getRed(),
268 series.colour.getGreen(),
269 series.colour.getBlue()));
270 props.setProperty("TransformMode."+i, series.transMode.toString());
271 props.setProperty("TransformParam."+i, StringTools.toString(series.param));
272 }
273
274 try {
275 FileOutputStream out = new FileOutputStream(fileName);
276 try { props.storeToXML(out, "LiveGraph version " + LiveGraph.version + ". DataSeriesSettings."); }
277 finally { out.close(); }
278 notifyObservers(actionEvent);
279 return true;
280 } catch(IOException e) {
281 e.printStackTrace();
282 return false;
283 }
284 }
285
286 /**
287 * Sets whether the data series with the specified index should be included in the plot.
288 *
289 * @param seriesIndex A data series index (corresponds to the column index in the data file).
290 * @param show {@code true} if the data series with the specified index is to be included in the plot,
291 * {@code false} otherwise.
292 */
293 public void setShow(int seriesIndex, boolean show) {
294
295 if (seriesIndex < 0)
296 return;
297
298 if (show == getShow(seriesIndex))
299 return;
300
301 Event<? extends SettingsEvent> actionEvent = checkObservers(DSS_Series_Visibility,
302 show, seriesIndex, Double.NaN, null);
303 if (null == actionEvent)
304 return;
305
306 ensureLength(seriesIndex);
307 settings.get(seriesIndex).show = show;
308 notifyObservers(actionEvent);
309 }
310
311 /**
312 * Sets whether the data series between the specified indices should be included in the plot.
313 *
314 * @param from Starting data series index (inclusive).
315 * @param to Finishing data series index (inclusive).
316 * @param show {@code true} if the data series with the specified index is to be included in tthe plot,
317 * {@code false} otherwise.
318 */
319 public void setShowAll(int from, int to, boolean show) {
320
321 from = Math.max(0, from);
322 to = Math.max(0, to);
323
324 if (from > to) {
325 int t = from; from = to; to = t;
326 }
327
328 Pair<Integer, Integer> bounds = new Pair<Integer, Integer>(from, to);
329 Event<? extends SettingsEvent> actionEvent = checkObservers(DSS_SeriesRange_Visibility,
330 false, 0L, Double.NaN, bounds);
331 if (null == actionEvent)
332 return;
333
334 ensureLength(to);
335 for (int i = from; i <= to; i++)
336 settings.get(i).show = show;
337 notifyObservers(actionEvent);
338 }
339
340 /**
341 * Toggles whether the data series between the specified indices should be included in the plot.
342 *
343 * @param from Starting data series index (inclusive).
344 * @param to Finishing data series index (inclusive).
345 */
346 public void setShowToggleAll(int from, int to) {
347
348 from = Math.max(0, from);
349 to = Math.max(0, to);
350
351 if (from > to) {
352 int t = from; from = to; to = t;
353 }
354
355 Pair<Integer, Integer> bounds = new Pair<Integer, Integer>(from, to);
356 Event<? extends SettingsEvent> actionEvent = checkObservers(DSS_SeriesRange_Visibility,
357 false, 0L, Double.NaN, bounds);
358 if (null == actionEvent)
359 return;
360
361 ensureLength(to);
362 for (int i = from; i <= to; i++)
363 settings.get(i).show = !settings.get(i).show;
364 notifyObservers(actionEvent);
365 }
366
367 /**
368 * Sets the colour for the plot of the data series with the specified index.
369 *
370 * @param seriesIndex A data series index (corresponds to the column index in the data file).
371 * @param colour The colour for the plot of the data series with the specified index.
372 */
373 public void setColour(int seriesIndex, Color colour) {
374
375 if (seriesIndex < 0)
376 return;
377
378 if (null == colour)
379 throw new NullPointerException("Null colour is not allowed.");
380
381 if (colour == getColour(seriesIndex))
382 return;
383
384 Event<? extends SettingsEvent> actionEvent = checkObservers(DSS_Series_Colour,
385 false, seriesIndex, Double.NaN, colour);
386 if (null == actionEvent)
387 return;
388
389 ensureLength(seriesIndex);
390 settings.get(seriesIndex).colour = colour;
391 notifyObservers(actionEvent);
392 }
393
394 /**
395 * Sets the transformation mode for the plotted values of the data series with the specified index.
396 *
397 * @param seriesIndex A data series index (corresponds to the column index in the data file).
398 * @param transformMode The transformation mode for the plotted values of the data series with
399 * the specified index.
400 */
401 public void setTransformMode(int seriesIndex, TransformMode transformMode) {
402
403 if (seriesIndex < 0)
404 return;
405
406 if (null == transformMode)
407 throw new NullPointerException("Null transformation mode mode is not allowed.");
408
409 if (transformMode == getTransformMode(seriesIndex))
410 return;
411
412 double p = getTransformParam(seriesIndex);
413 double np = ensureGoodTransformParam(transformMode, p);
414
415 Event<? extends SettingsEvent> actionEvent = checkObservers(DSS_Series_TransformMode,
416 false, seriesIndex, np, transformMode);
417 if (null == actionEvent)
418 return;
419
420 if (p != np && null == checkObservers(DSS_Series_TransformParam, false, seriesIndex, np, transformMode))
421 return;
422
423 ensureLength(seriesIndex);
424
425 settings.get(seriesIndex).transMode = transformMode;
426 notifyObservers(actionEvent);
427
428 if (p != np)
429 setTransformParam(seriesIndex, np);
430 }
431
432 /**
433 * Sets the parameter for the transformation of the plotted values of the data
434 * series with the specified index; this parameter is currently required only for the
435 * mode {@code Transform_ScaleBySetVal};
436
437 * @param seriesIndex A data series index (corresponds to the column index in the data file).
438 * @param parameter The parameter for the transformation of the plotted values of the data series with
439 * the specified index.
440 */
441 public void setTransformParam(int seriesIndex, double parameter) {
442
443 if (seriesIndex < 0)
444 return;
445
446 TransformMode transMode = getTransformMode(seriesIndex);
447 parameter = ensureGoodTransformParam(transMode, parameter);
448
449 if (parameter == getTransformParam(seriesIndex))
450 return;
451
452 Event<? extends SettingsEvent> actionEvent = checkObservers(DSS_Series_TransformParam,
453 false, seriesIndex, parameter, transMode);
454 if (null == actionEvent)
455 return;
456
457 ensureLength(seriesIndex);
458 settings.get(seriesIndex).param = parameter;
459
460 notifyObservers(actionEvent);
461 }
462
463 /**
464 * Setts whether the data series with the specified index should be included in tthe plot.
465 * If no setting value has been defined for the specified series, a defalut value will be
466 * returned as specified by {@link #getDefaultShow(int)}.
467 *
468 * @param seriesIndex A data series index (corresponds to the column index in the data file).
469 * @return {@code true} if the data series with the specified index is to be included in tthe plot,
470 * {@code false} otherwise.
471 * @see #getDefaultShow(int)
472 */
473 public boolean getShow(int seriesIndex) {
474 if (seriesIndex < 0 || settings.size() <= seriesIndex)
475 return getDefaultShow(seriesIndex);
476 return settings.get(seriesIndex).show;
477 }
478
479 /**
480 * Gets the colour for the plot of the data series with the specified index.
481 * If no setting value has been defined for the specified series, a defalut value will be
482 * returned as specified by {@link #getDefaultColour(int)}.
483 *
484 * @param seriesIndex A data series index (corresponds to the column index in the data file).
485 * @return The colour for the plot of the data series with the specified index.
486 * @see #getDefaultColour(int)
487 */
488 public Color getColour(int seriesIndex) {
489 if (seriesIndex < 0 || settings.size() <= seriesIndex)
490 return getDefaultColour(seriesIndex);
491 return settings.get(seriesIndex).colour;
492 }
493
494 /**
495 * Gets the transformation mode for the plotted values of the data series with the specified index.
496 * If no setting value has been defined for the specified series, a defalut value will be
497 * returned as specified by {@link #getDefaultTransformMode(int)}.
498 *
499 * @param seriesIndex A data series index (corresponds to the column index in the data file).
500 * @return The transformation mode for the plotted values of the data series with the specified index.
501 * @see #getDefaultTransformMode(int)
502 */
503 public TransformMode getTransformMode(int seriesIndex) {
504 if (seriesIndex < 0 || settings.size() <= seriesIndex)
505 return getDefaultTransformMode(seriesIndex);
506 return settings.get(seriesIndex).transMode;
507 }
508
509 /**
510 * Gets the parameter for the transformation of the plotted values of the data series with
511 * the specified index; this parameter is currently required only for the mode {@code Transform_ScaleBySetVal}.
512 * If no setting value has been defined for the specified series, a defalut value will be
513 * returned as specified by {@link #getDefaultTransformParam(int)}.
514 *
515 * @param seriesIndex A data series index (corresponds to the column index in the data file).
516 * @return The parameter for the transformation of the plotted values of the data series with
517 * the specified index.
518 * @see #getDefaultTransformParam(int)
519 */
520 public double getTransformParam(int seriesIndex) {
521 if (seriesIndex < 0 || settings.size() <= seriesIndex)
522 return getDefaultTransformParam(seriesIndex);
523 return settings.get(seriesIndex).param;
524 }
525
526
527 /**
528 * Ensure that the transformation parameter has a legal value for the given
529 * transformation mode. The transformation parameter must be a real number and if
530 * the transformation mode is {@code Transform_Logarithm}, it must be
531 * non-negative and not 1.
532 *
533 * @param transformMode The transform mode for which to verify the parameter.
534 * @param parameter The transfom parameter to check.
535 * @return The corrected transform parameter.
536 */
537 private double ensureGoodTransformParam(TransformMode transformMode, double parameter) {
538
539 if (Double.isInfinite(parameter))
540 parameter = (parameter > 0. ? 1. : -1.);
541
542 if (Double.isNaN(parameter))
543 parameter = 0.;
544
545 if (transformMode == TransformMode.Transform_Logarithm) {
546
547 double d = MathTools.log(parameter, 1.);
548 if (Double.isNaN(d) || Double.isInfinite(d)) {
549
550 if (0 > parameter)
551 parameter = -parameter;
552 if (1. == parameter)
553 parameter = 0.;
554 }
555 }
556
557 return parameter;
558 }
559
560 /**
561 * This struct-class is used to group the settings for one data series in a single
562 * data structure.
563 */
564 private static class SeriesParameters {
565
566 /**
567 * Whether this data series should be shown at all.
568 */
569 private boolean show = false;
570
571 /**
572 * Colour to use for this series.
573 */
574 private Color colour = null;
575
576 /**
577 * Transformation mode for series values.
578 */
579 private TransformMode transMode = null;
580
581 /**
582 * Parameter for series' values transformation.
583 */
584 private double param = Double.NaN;
585
586 /**
587 * Creates an uninitialised series settings data structure.
588 */
589 private SeriesParameters() {}
590
591 /**
592 * Creates an series settings data structure and initialises it with the specified values.
593 *
594 * @param show Display?
595 * @param colour Line colour.
596 * @param transMode Values transformation.
597 * @param param Transformation parameter.
598 */
599 private SeriesParameters(boolean show, Color colour, TransformMode transMode, double param) {
600 this.show = show;
601 this.colour = colour;
602 this.transMode = transMode;
603 this.param = param;
604 }
605 } // private class SeriesParameters
606
607 } // public class DataSeriesSettings