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