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