001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2022, by David Gilbert and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ------------------------- 028 * OHLCSeriesCollection.java 029 * ------------------------- 030 * (C) Copyright 2006-2022, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.data.time.ohlc; 038 039import java.io.Serializable; 040import java.util.List; 041import java.util.Objects; 042 043import org.jfree.chart.internal.HashUtils; 044import org.jfree.chart.internal.CloneUtils; 045import org.jfree.chart.internal.Args; 046import org.jfree.data.general.DatasetChangeEvent; 047import org.jfree.data.time.RegularTimePeriod; 048import org.jfree.data.time.TimePeriodAnchor; 049import org.jfree.data.xy.AbstractXYDataset; 050import org.jfree.data.xy.OHLCDataset; 051import org.jfree.data.xy.XYDataset; 052 053/** 054 * A collection of {@link OHLCSeries} objects. 055 * 056 * @since 1.0.4 057 * 058 * @see OHLCSeries 059 */ 060public class OHLCSeriesCollection extends AbstractXYDataset 061 implements OHLCDataset, Serializable { 062 063 /** Storage for the data series. */ 064 private List data; 065 066 private TimePeriodAnchor xPosition = TimePeriodAnchor.MIDDLE; 067 068 /** 069 * Creates a new instance of {@code OHLCSeriesCollection}. 070 */ 071 public OHLCSeriesCollection() { 072 this.data = new java.util.ArrayList(); 073 } 074 075 /** 076 * Returns the position within each time period that is used for the X 077 * value when the collection is used as an {@link XYDataset}. 078 * 079 * @return The anchor position (never {@code null}). 080 * 081 * @since 1.0.11 082 */ 083 public TimePeriodAnchor getXPosition() { 084 return this.xPosition; 085 } 086 087 /** 088 * Sets the position within each time period that is used for the X values 089 * when the collection is used as an {@link XYDataset}, then sends a 090 * {@link DatasetChangeEvent} is sent to all registered listeners. 091 * 092 * @param anchor the anchor position ({@code null} not permitted). 093 * 094 * @since 1.0.11 095 */ 096 public void setXPosition(TimePeriodAnchor anchor) { 097 Args.nullNotPermitted(anchor, "anchor"); 098 this.xPosition = anchor; 099 notifyListeners(new DatasetChangeEvent(this, this)); 100 } 101 102 /** 103 * Adds a series to the collection and sends a {@link DatasetChangeEvent} 104 * to all registered listeners. 105 * 106 * @param series the series ({@code null} not permitted). 107 */ 108 public void addSeries(OHLCSeries series) { 109 Args.nullNotPermitted(series, "series"); 110 this.data.add(series); 111 series.addChangeListener(this); 112 fireDatasetChanged(); 113 } 114 115 /** 116 * Returns the number of series in the collection. 117 * 118 * @return The series count. 119 */ 120 @Override 121 public int getSeriesCount() { 122 return this.data.size(); 123 } 124 125 /** 126 * Returns a series from the collection. 127 * 128 * @param series the series index (zero-based). 129 * 130 * @return The series. 131 * 132 * @throws IllegalArgumentException if {@code series} is not in the 133 * range {@code 0} to {@code getSeriesCount() - 1}. 134 */ 135 public OHLCSeries getSeries(int series) { 136 if ((series < 0) || (series >= getSeriesCount())) { 137 throw new IllegalArgumentException("Series index out of bounds"); 138 } 139 return (OHLCSeries) this.data.get(series); 140 } 141 142 /** 143 * Returns the key for a series. 144 * 145 * @param series the series index (in the range {@code 0} to 146 * {@code getSeriesCount() - 1}). 147 * 148 * @return The key for a series. 149 * 150 * @throws IllegalArgumentException if {@code series} is not in the 151 * specified range. 152 */ 153 @Override 154 public Comparable getSeriesKey(int series) { 155 // defer argument checking 156 return getSeries(series).getKey(); 157 } 158 159 /** 160 * Returns the number of items in the specified series. 161 * 162 * @param series the series (zero-based index). 163 * 164 * @return The item count. 165 * 166 * @throws IllegalArgumentException if {@code series} is not in the 167 * range {@code 0} to {@code getSeriesCount() - 1}. 168 */ 169 @Override 170 public int getItemCount(int series) { 171 // defer argument checking 172 return getSeries(series).getItemCount(); 173 } 174 175 /** 176 * Returns the x-value for a time period. 177 * 178 * @param period the time period ({@code null} not permitted). 179 * 180 * @return The x-value. 181 */ 182 protected synchronized long getX(RegularTimePeriod period) { 183 long result = 0L; 184 if (this.xPosition == TimePeriodAnchor.START) { 185 result = period.getFirstMillisecond(); 186 } 187 else if (this.xPosition == TimePeriodAnchor.MIDDLE) { 188 result = period.getMiddleMillisecond(); 189 } 190 else if (this.xPosition == TimePeriodAnchor.END) { 191 result = period.getLastMillisecond(); 192 } 193 return result; 194 } 195 196 /** 197 * Returns the x-value for an item within a series. 198 * 199 * @param series the series index. 200 * @param item the item index. 201 * 202 * @return The x-value. 203 */ 204 @Override 205 public double getXValue(int series, int item) { 206 OHLCSeries s = (OHLCSeries) this.data.get(series); 207 OHLCItem di = (OHLCItem) s.getDataItem(item); 208 RegularTimePeriod period = di.getPeriod(); 209 return getX(period); 210 } 211 212 /** 213 * Returns the x-value for an item within a series. 214 * 215 * @param series the series index. 216 * @param item the item index. 217 * 218 * @return The x-value. 219 */ 220 @Override 221 public Number getX(int series, int item) { 222 return getXValue(series, item); 223 } 224 225 /** 226 * Returns the y-value for an item within a series. 227 * 228 * @param series the series index. 229 * @param item the item index. 230 * 231 * @return The y-value. 232 */ 233 @Override 234 public Number getY(int series, int item) { 235 OHLCSeries s = (OHLCSeries) this.data.get(series); 236 OHLCItem di = (OHLCItem) s.getDataItem(item); 237 return di.getYValue(); 238 } 239 240 /** 241 * Returns the open-value for an item within a series. 242 * 243 * @param series the series index. 244 * @param item the item index. 245 * 246 * @return The open-value. 247 */ 248 @Override 249 public double getOpenValue(int series, int item) { 250 OHLCSeries s = (OHLCSeries) this.data.get(series); 251 OHLCItem di = (OHLCItem) s.getDataItem(item); 252 return di.getOpenValue(); 253 } 254 255 /** 256 * Returns the open-value for an item within a series. 257 * 258 * @param series the series index. 259 * @param item the item index. 260 * 261 * @return The open-value. 262 */ 263 @Override 264 public Number getOpen(int series, int item) { 265 return getOpenValue(series, item); 266 } 267 268 /** 269 * Returns the close-value for an item within a series. 270 * 271 * @param series the series index. 272 * @param item the item index. 273 * 274 * @return The close-value. 275 */ 276 @Override 277 public double getCloseValue(int series, int item) { 278 OHLCSeries s = (OHLCSeries) this.data.get(series); 279 OHLCItem di = (OHLCItem) s.getDataItem(item); 280 return di.getCloseValue(); 281 } 282 283 /** 284 * Returns the close-value for an item within a series. 285 * 286 * @param series the series index. 287 * @param item the item index. 288 * 289 * @return The close-value. 290 */ 291 @Override 292 public Number getClose(int series, int item) { 293 return getCloseValue(series, item); 294 } 295 296 /** 297 * Returns the high-value for an item within a series. 298 * 299 * @param series the series index. 300 * @param item the item index. 301 * 302 * @return The high-value. 303 */ 304 @Override 305 public double getHighValue(int series, int item) { 306 OHLCSeries s = (OHLCSeries) this.data.get(series); 307 OHLCItem di = (OHLCItem) s.getDataItem(item); 308 return di.getHighValue(); 309 } 310 311 /** 312 * Returns the high-value for an item within a series. 313 * 314 * @param series the series index. 315 * @param item the item index. 316 * 317 * @return The high-value. 318 */ 319 @Override 320 public Number getHigh(int series, int item) { 321 return getHighValue(series, item); 322 } 323 324 /** 325 * Returns the low-value for an item within a series. 326 * 327 * @param series the series index. 328 * @param item the item index. 329 * 330 * @return The low-value. 331 */ 332 @Override 333 public double getLowValue(int series, int item) { 334 OHLCSeries s = (OHLCSeries) this.data.get(series); 335 OHLCItem di = (OHLCItem) s.getDataItem(item); 336 return di.getLowValue(); 337 } 338 339 /** 340 * Returns the low-value for an item within a series. 341 * 342 * @param series the series index. 343 * @param item the item index. 344 * 345 * @return The low-value. 346 */ 347 @Override 348 public Number getLow(int series, int item) { 349 return getLowValue(series, item); 350 } 351 352 /** 353 * Returns {@code null} always, because this dataset doesn't record 354 * any volume data. 355 * 356 * @param series the series index (ignored). 357 * @param item the item index (ignored). 358 * 359 * @return {@code null}. 360 */ 361 @Override 362 public Number getVolume(int series, int item) { 363 return null; 364 } 365 366 /** 367 * Returns {@code Double.NaN} always, because this dataset doesn't 368 * record any volume data. 369 * 370 * @param series the series index (ignored). 371 * @param item the item index (ignored). 372 * 373 * @return {@code Double.NaN}. 374 */ 375 @Override 376 public double getVolumeValue(int series, int item) { 377 return Double.NaN; 378 } 379 380 /** 381 * Removes the series with the specified index and sends a 382 * {@link DatasetChangeEvent} to all registered listeners. 383 * 384 * @param index the series index. 385 * 386 * @since 1.0.14 387 */ 388 public void removeSeries(int index) { 389 OHLCSeries series = getSeries(index); 390 if (series != null) { 391 removeSeries(series); 392 } 393 } 394 395 /** 396 * Removes the specified series from the dataset and sends a 397 * {@link DatasetChangeEvent} to all registered listeners. 398 * 399 * @param series the series ({@code null} not permitted). 400 * 401 * @return {@code true} if the series was removed, and 402 * {@code false} otherwise. 403 * 404 * @since 1.0.14 405 */ 406 public boolean removeSeries(OHLCSeries series) { 407 Args.nullNotPermitted(series, "series"); 408 boolean removed = this.data.remove(series); 409 if (removed) { 410 series.removeChangeListener(this); 411 fireDatasetChanged(); 412 } 413 return removed; 414 } 415 416 /** 417 * Removes all the series from the collection and sends a 418 * {@link DatasetChangeEvent} to all registered listeners. 419 * 420 * @since 1.0.14 421 */ 422 public void removeAllSeries() { 423 424 if (this.data.isEmpty()) { 425 return; // nothing to do 426 } 427 428 // deregister the collection as a change listener to each series in the 429 // collection 430 for (int i = 0; i < this.data.size(); i++) { 431 OHLCSeries series = (OHLCSeries) this.data.get(i); 432 series.removeChangeListener(this); 433 } 434 435 // remove all the series from the collection and notify listeners. 436 this.data.clear(); 437 fireDatasetChanged(); 438 439 } 440 441 /** 442 * Tests this instance for equality with an arbitrary object. 443 * 444 * @param obj the object ({@code null} permitted). 445 * 446 * @return A boolean. 447 */ 448 @Override 449 public boolean equals(Object obj) { 450 if (obj == this) { 451 return true; 452 } 453 if (!(obj instanceof OHLCSeriesCollection)) { 454 return false; 455 } 456 OHLCSeriesCollection that = (OHLCSeriesCollection) obj; 457 if (!this.xPosition.equals(that.xPosition)) { 458 return false; 459 } 460 return Objects.equals(this.data, that.data); 461 } 462 463 /** 464 * Returns a hash code for this instance. 465 * 466 * @return A hash code. 467 */ 468 @Override 469 public int hashCode() { 470 int result = 137; 471 result = HashUtils.hashCode(result, this.xPosition); 472 for (int i = 0; i < this.data.size(); i++) { 473 result = HashUtils.hashCode(result, this.data.get(i)); 474 } 475 return result; 476 } 477 478 /** 479 * Returns a clone of this instance. 480 * 481 * @return A clone. 482 * 483 * @throws CloneNotSupportedException if there is a problem. 484 */ 485 @Override 486 public Object clone() throws CloneNotSupportedException { 487 OHLCSeriesCollection clone 488 = (OHLCSeriesCollection) super.clone(); 489 clone.data = CloneUtils.cloneList(this.data); 490 return clone; 491 } 492 493}