1 2 package com.github.mikephil.charting.data; 3 4 import android.graphics.Typeface; 5 import android.util.Log; 6 7 import com.github.mikephil.charting.components.YAxis.AxisDependency; 8 import com.github.mikephil.charting.formatter.IValueFormatter; 9 import com.github.mikephil.charting.highlight.Highlight; 10 import com.github.mikephil.charting.interfaces.datasets.IDataSet; 11 12 import java.util.ArrayList; 13 import java.util.List; 14 15 /** 16 * Class that holds all relevant data that represents the chart. That involves 17 * at least one (or more) DataSets, and an array of x-values. 18 * 19 * @author Philipp Jahoda 20 */ 21 public abstract class ChartData<T extends IDataSet<? extends Entry>> { 22 23 /** 24 * maximum y-value in the value array across all axes 25 */ 26 protected float mYMax = -Float.MAX_VALUE; 27 28 /** 29 * the minimum y-value in the value array across all axes 30 */ 31 protected float mYMin = Float.MAX_VALUE; 32 33 /** 34 * maximum x-value in the value array 35 */ 36 protected float mXMax = -Float.MAX_VALUE; 37 38 /** 39 * minimum x-value in the value array 40 */ 41 protected float mXMin = Float.MAX_VALUE; 42 43 44 protected float mLeftAxisMax = -Float.MAX_VALUE; 45 46 protected float mLeftAxisMin = Float.MAX_VALUE; 47 48 protected float mRightAxisMax = -Float.MAX_VALUE; 49 50 protected float mRightAxisMin = Float.MAX_VALUE; 51 52 /** 53 * array that holds all DataSets the ChartData object represents 54 */ 55 protected List<T> mDataSets; 56 57 /** 58 * Default constructor. 59 */ ChartData()60 public ChartData() { 61 mDataSets = new ArrayList<T>(); 62 } 63 64 /** 65 * Constructor taking single or multiple DataSet objects. 66 * 67 * @param dataSets 68 */ ChartData(T... dataSets)69 public ChartData(T... dataSets) { 70 mDataSets = arrayToList(dataSets); 71 notifyDataChanged(); 72 } 73 74 /** 75 * Created because Arrays.asList(...) does not support modification. 76 * 77 * @param array 78 * @return 79 */ arrayToList(T[] array)80 private List<T> arrayToList(T[] array) { 81 82 List<T> list = new ArrayList<>(); 83 84 for (T set : array) { 85 list.add(set); 86 } 87 88 return list; 89 } 90 91 /** 92 * constructor for chart data 93 * 94 * @param sets the dataset array 95 */ ChartData(List<T> sets)96 public ChartData(List<T> sets) { 97 this.mDataSets = sets; 98 notifyDataChanged(); 99 } 100 101 /** 102 * Call this method to let the ChartData know that the underlying data has 103 * changed. Calling this performs all necessary recalculations needed when 104 * the contained data has changed. 105 */ notifyDataChanged()106 public void notifyDataChanged() { 107 calcMinMax(); 108 } 109 110 /** 111 * Calc minimum and maximum y-values over all DataSets. 112 * Tell DataSets to recalculate their min and max y-values, this is only needed for autoScaleMinMax. 113 * 114 * @param fromX the x-value to start the calculation from 115 * @param toX the x-value to which the calculation should be performed 116 */ calcMinMaxY(float fromX, float toX)117 public void calcMinMaxY(float fromX, float toX) { 118 119 for (T set : mDataSets) { 120 set.calcMinMaxY(fromX, toX); 121 } 122 123 // apply the new data 124 calcMinMax(); 125 } 126 127 /** 128 * Calc minimum and maximum values (both x and y) over all DataSets. 129 */ calcMinMax()130 protected void calcMinMax() { 131 132 if (mDataSets == null) 133 return; 134 135 mYMax = -Float.MAX_VALUE; 136 mYMin = Float.MAX_VALUE; 137 mXMax = -Float.MAX_VALUE; 138 mXMin = Float.MAX_VALUE; 139 140 for (T set : mDataSets) { 141 calcMinMax(set); 142 } 143 144 mLeftAxisMax = -Float.MAX_VALUE; 145 mLeftAxisMin = Float.MAX_VALUE; 146 mRightAxisMax = -Float.MAX_VALUE; 147 mRightAxisMin = Float.MAX_VALUE; 148 149 // left axis 150 T firstLeft = getFirstLeft(mDataSets); 151 152 if (firstLeft != null) { 153 154 mLeftAxisMax = firstLeft.getYMax(); 155 mLeftAxisMin = firstLeft.getYMin(); 156 157 for (T dataSet : mDataSets) { 158 if (dataSet.getAxisDependency() == AxisDependency.LEFT) { 159 if (dataSet.getYMin() < mLeftAxisMin) 160 mLeftAxisMin = dataSet.getYMin(); 161 162 if (dataSet.getYMax() > mLeftAxisMax) 163 mLeftAxisMax = dataSet.getYMax(); 164 } 165 } 166 } 167 168 // right axis 169 T firstRight = getFirstRight(mDataSets); 170 171 if (firstRight != null) { 172 173 mRightAxisMax = firstRight.getYMax(); 174 mRightAxisMin = firstRight.getYMin(); 175 176 for (T dataSet : mDataSets) { 177 if (dataSet.getAxisDependency() == AxisDependency.RIGHT) { 178 if (dataSet.getYMin() < mRightAxisMin) 179 mRightAxisMin = dataSet.getYMin(); 180 181 if (dataSet.getYMax() > mRightAxisMax) 182 mRightAxisMax = dataSet.getYMax(); 183 } 184 } 185 } 186 } 187 188 /** ONLY GETTERS AND SETTERS BELOW THIS */ 189 190 /** 191 * returns the number of LineDataSets this object contains 192 * 193 * @return 194 */ getDataSetCount()195 public int getDataSetCount() { 196 if (mDataSets == null) 197 return 0; 198 return mDataSets.size(); 199 } 200 201 /** 202 * Returns the smallest y-value the data object contains. 203 * 204 * @return 205 */ getYMin()206 public float getYMin() { 207 return mYMin; 208 } 209 210 /** 211 * Returns the minimum y-value for the specified axis. 212 * 213 * @param axis 214 * @return 215 */ getYMin(AxisDependency axis)216 public float getYMin(AxisDependency axis) { 217 if (axis == AxisDependency.LEFT) { 218 219 if (mLeftAxisMin == Float.MAX_VALUE) { 220 return mRightAxisMin; 221 } else 222 return mLeftAxisMin; 223 } else { 224 if (mRightAxisMin == Float.MAX_VALUE) { 225 return mLeftAxisMin; 226 } else 227 return mRightAxisMin; 228 } 229 } 230 231 /** 232 * Returns the greatest y-value the data object contains. 233 * 234 * @return 235 */ getYMax()236 public float getYMax() { 237 return mYMax; 238 } 239 240 /** 241 * Returns the maximum y-value for the specified axis. 242 * 243 * @param axis 244 * @return 245 */ getYMax(AxisDependency axis)246 public float getYMax(AxisDependency axis) { 247 if (axis == AxisDependency.LEFT) { 248 249 if (mLeftAxisMax == -Float.MAX_VALUE) { 250 return mRightAxisMax; 251 } else 252 return mLeftAxisMax; 253 } else { 254 if (mRightAxisMax == -Float.MAX_VALUE) { 255 return mLeftAxisMax; 256 } else 257 return mRightAxisMax; 258 } 259 } 260 261 /** 262 * Returns the minimum x-value this data object contains. 263 * 264 * @return 265 */ getXMin()266 public float getXMin() { 267 return mXMin; 268 } 269 270 /** 271 * Returns the maximum x-value this data object contains. 272 * 273 * @return 274 */ getXMax()275 public float getXMax() { 276 return mXMax; 277 } 278 279 /** 280 * Returns all DataSet objects this ChartData object holds. 281 * 282 * @return 283 */ getDataSets()284 public List<T> getDataSets() { 285 return mDataSets; 286 } 287 288 /** 289 * Retrieve the index of a DataSet with a specific label from the ChartData. 290 * Search can be case sensitive or not. IMPORTANT: This method does 291 * calculations at runtime, do not over-use in performance critical 292 * situations. 293 * 294 * @param dataSets the DataSet array to search 295 * @param label 296 * @param ignorecase if true, the search is not case-sensitive 297 * @return 298 */ getDataSetIndexByLabel(List<T> dataSets, String label, boolean ignorecase)299 protected int getDataSetIndexByLabel(List<T> dataSets, String label, 300 boolean ignorecase) { 301 302 if (ignorecase) { 303 for (int i = 0; i < dataSets.size(); i++) 304 if (label.equalsIgnoreCase(dataSets.get(i).getLabel())) 305 return i; 306 } else { 307 for (int i = 0; i < dataSets.size(); i++) 308 if (label.equals(dataSets.get(i).getLabel())) 309 return i; 310 } 311 312 return -1; 313 } 314 315 /** 316 * Returns the labels of all DataSets as a string array. 317 * 318 * @return 319 */ getDataSetLabels()320 public String[] getDataSetLabels() { 321 322 String[] types = new String[mDataSets.size()]; 323 324 for (int i = 0; i < mDataSets.size(); i++) { 325 types[i] = mDataSets.get(i).getLabel(); 326 } 327 328 return types; 329 } 330 331 /** 332 * Get the Entry for a corresponding highlight object 333 * 334 * @param highlight 335 * @return the entry that is highlighted 336 */ getEntryForHighlight(Highlight highlight)337 public Entry getEntryForHighlight(Highlight highlight) { 338 if (highlight.getDataSetIndex() >= mDataSets.size()) 339 return null; 340 else { 341 return mDataSets.get(highlight.getDataSetIndex()).getEntryForXValue(highlight.getX(), highlight.getY()); 342 } 343 } 344 345 /** 346 * Returns the DataSet object with the given label. Search can be case 347 * sensitive or not. IMPORTANT: This method does calculations at runtime. 348 * Use with care in performance critical situations. 349 * 350 * @param label 351 * @param ignorecase 352 * @return 353 */ getDataSetByLabel(String label, boolean ignorecase)354 public T getDataSetByLabel(String label, boolean ignorecase) { 355 356 int index = getDataSetIndexByLabel(mDataSets, label, ignorecase); 357 358 if (index < 0 || index >= mDataSets.size()) 359 return null; 360 else 361 return mDataSets.get(index); 362 } 363 getDataSetByIndex(int index)364 public T getDataSetByIndex(int index) { 365 366 if (mDataSets == null || index < 0 || index >= mDataSets.size()) 367 return null; 368 369 return mDataSets.get(index); 370 } 371 372 /** 373 * Adds a DataSet dynamically. 374 * 375 * @param d 376 */ addDataSet(T d)377 public void addDataSet(T d) { 378 379 if (d == null) 380 return; 381 382 calcMinMax(d); 383 384 mDataSets.add(d); 385 } 386 387 /** 388 * Removes the given DataSet from this data object. Also recalculates all 389 * minimum and maximum values. Returns true if a DataSet was removed, false 390 * if no DataSet could be removed. 391 * 392 * @param d 393 */ removeDataSet(T d)394 public boolean removeDataSet(T d) { 395 396 if (d == null) 397 return false; 398 399 boolean removed = mDataSets.remove(d); 400 401 // if a DataSet was removed 402 if (removed) { 403 notifyDataChanged(); 404 } 405 406 return removed; 407 } 408 409 /** 410 * Removes the DataSet at the given index in the DataSet array from the data 411 * object. Also recalculates all minimum and maximum values. Returns true if 412 * a DataSet was removed, false if no DataSet could be removed. 413 * 414 * @param index 415 */ removeDataSet(int index)416 public boolean removeDataSet(int index) { 417 418 if (index >= mDataSets.size() || index < 0) 419 return false; 420 421 T set = mDataSets.get(index); 422 return removeDataSet(set); 423 } 424 425 /** 426 * Adds an Entry to the DataSet at the specified index. 427 * Entries are added to the end of the list. 428 * 429 * @param e 430 * @param dataSetIndex 431 */ addEntry(Entry e, int dataSetIndex)432 public void addEntry(Entry e, int dataSetIndex) { 433 434 if (mDataSets.size() > dataSetIndex && dataSetIndex >= 0) { 435 436 IDataSet set = mDataSets.get(dataSetIndex); 437 // add the entry to the dataset 438 if (!set.addEntry(e)) 439 return; 440 441 calcMinMax(e, set.getAxisDependency()); 442 443 } else { 444 Log.e("addEntry", "Cannot add Entry because dataSetIndex too high or too low."); 445 } 446 } 447 448 /** 449 * Adjusts the current minimum and maximum values based on the provided Entry object. 450 * 451 * @param e 452 * @param axis 453 */ calcMinMax(Entry e, AxisDependency axis)454 protected void calcMinMax(Entry e, AxisDependency axis) { 455 456 if (mYMax < e.getY()) 457 mYMax = e.getY(); 458 if (mYMin > e.getY()) 459 mYMin = e.getY(); 460 461 if (mXMax < e.getX()) 462 mXMax = e.getX(); 463 if (mXMin > e.getX()) 464 mXMin = e.getX(); 465 466 if (axis == AxisDependency.LEFT) { 467 468 if (mLeftAxisMax < e.getY()) 469 mLeftAxisMax = e.getY(); 470 if (mLeftAxisMin > e.getY()) 471 mLeftAxisMin = e.getY(); 472 } else { 473 if (mRightAxisMax < e.getY()) 474 mRightAxisMax = e.getY(); 475 if (mRightAxisMin > e.getY()) 476 mRightAxisMin = e.getY(); 477 } 478 } 479 480 /** 481 * Adjusts the minimum and maximum values based on the given DataSet. 482 * 483 * @param d 484 */ calcMinMax(T d)485 protected void calcMinMax(T d) { 486 487 if (mYMax < d.getYMax()) 488 mYMax = d.getYMax(); 489 if (mYMin > d.getYMin()) 490 mYMin = d.getYMin(); 491 492 if (mXMax < d.getXMax()) 493 mXMax = d.getXMax(); 494 if (mXMin > d.getXMin()) 495 mXMin = d.getXMin(); 496 497 if (d.getAxisDependency() == AxisDependency.LEFT) { 498 499 if (mLeftAxisMax < d.getYMax()) 500 mLeftAxisMax = d.getYMax(); 501 if (mLeftAxisMin > d.getYMin()) 502 mLeftAxisMin = d.getYMin(); 503 } else { 504 if (mRightAxisMax < d.getYMax()) 505 mRightAxisMax = d.getYMax(); 506 if (mRightAxisMin > d.getYMin()) 507 mRightAxisMin = d.getYMin(); 508 } 509 } 510 511 /** 512 * Removes the given Entry object from the DataSet at the specified index. 513 * 514 * @param e 515 * @param dataSetIndex 516 */ removeEntry(Entry e, int dataSetIndex)517 public boolean removeEntry(Entry e, int dataSetIndex) { 518 519 // entry null, outofbounds 520 if (e == null || dataSetIndex >= mDataSets.size()) 521 return false; 522 523 IDataSet set = mDataSets.get(dataSetIndex); 524 525 if (set != null) { 526 // remove the entry from the dataset 527 boolean removed = set.removeEntry(e); 528 529 if (removed) { 530 notifyDataChanged(); 531 } 532 533 return removed; 534 } else 535 return false; 536 } 537 538 /** 539 * Removes the Entry object closest to the given DataSet at the 540 * specified index. Returns true if an Entry was removed, false if no Entry 541 * was found that meets the specified requirements. 542 * 543 * @param xValue 544 * @param dataSetIndex 545 * @return 546 */ removeEntry(float xValue, int dataSetIndex)547 public boolean removeEntry(float xValue, int dataSetIndex) { 548 549 if (dataSetIndex >= mDataSets.size()) 550 return false; 551 552 IDataSet dataSet = mDataSets.get(dataSetIndex); 553 Entry e = dataSet.getEntryForXValue(xValue, Float.NaN); 554 555 if (e == null) 556 return false; 557 558 return removeEntry(e, dataSetIndex); 559 } 560 561 /** 562 * Returns the DataSet that contains the provided Entry, or null, if no 563 * DataSet contains this Entry. 564 * 565 * @param e 566 * @return 567 */ getDataSetForEntry(Entry e)568 public T getDataSetForEntry(Entry e) { 569 570 if (e == null) 571 return null; 572 573 for (int i = 0; i < mDataSets.size(); i++) { 574 575 T set = mDataSets.get(i); 576 577 for (int j = 0; j < set.getEntryCount(); j++) { 578 if (e.equalTo(set.getEntryForXValue(e.getX(), e.getY()))) 579 return set; 580 } 581 } 582 583 return null; 584 } 585 586 /** 587 * Returns all colors used across all DataSet objects this object 588 * represents. 589 * 590 * @return 591 */ getColors()592 public int[] getColors() { 593 594 if (mDataSets == null) 595 return null; 596 597 int clrcnt = 0; 598 599 for (int i = 0; i < mDataSets.size(); i++) { 600 clrcnt += mDataSets.get(i).getColors().size(); 601 } 602 603 int[] colors = new int[clrcnt]; 604 int cnt = 0; 605 606 for (int i = 0; i < mDataSets.size(); i++) { 607 608 List<Integer> clrs = mDataSets.get(i).getColors(); 609 610 for (Integer clr : clrs) { 611 colors[cnt] = clr; 612 cnt++; 613 } 614 } 615 616 return colors; 617 } 618 619 /** 620 * Returns the index of the provided DataSet in the DataSet array of this data object, or -1 if it does not exist. 621 * 622 * @param dataSet 623 * @return 624 */ getIndexOfDataSet(T dataSet)625 public int getIndexOfDataSet(T dataSet) { 626 return mDataSets.indexOf(dataSet); 627 } 628 629 /** 630 * Returns the first DataSet from the datasets-array that has it's dependency on the left axis. 631 * Returns null if no DataSet with left dependency could be found. 632 * 633 * @return 634 */ getFirstLeft(List<T> sets)635 protected T getFirstLeft(List<T> sets) { 636 for (T dataSet : sets) { 637 if (dataSet.getAxisDependency() == AxisDependency.LEFT) 638 return dataSet; 639 } 640 return null; 641 } 642 643 /** 644 * Returns the first DataSet from the datasets-array that has it's dependency on the right axis. 645 * Returns null if no DataSet with right dependency could be found. 646 * 647 * @return 648 */ getFirstRight(List<T> sets)649 public T getFirstRight(List<T> sets) { 650 for (T dataSet : sets) { 651 if (dataSet.getAxisDependency() == AxisDependency.RIGHT) 652 return dataSet; 653 } 654 return null; 655 } 656 657 /** 658 * Sets a custom IValueFormatter for all DataSets this data object contains. 659 * 660 * @param f 661 */ setValueFormatter(IValueFormatter f)662 public void setValueFormatter(IValueFormatter f) { 663 if (f == null) 664 return; 665 else { 666 for (IDataSet set : mDataSets) { 667 set.setValueFormatter(f); 668 } 669 } 670 } 671 672 /** 673 * Sets the color of the value-text (color in which the value-labels are 674 * drawn) for all DataSets this data object contains. 675 * 676 * @param color 677 */ setValueTextColor(int color)678 public void setValueTextColor(int color) { 679 for (IDataSet set : mDataSets) { 680 set.setValueTextColor(color); 681 } 682 } 683 684 /** 685 * Sets the same list of value-colors for all DataSets this 686 * data object contains. 687 * 688 * @param colors 689 */ setValueTextColors(List<Integer> colors)690 public void setValueTextColors(List<Integer> colors) { 691 for (IDataSet set : mDataSets) { 692 set.setValueTextColors(colors); 693 } 694 } 695 696 /** 697 * Sets the Typeface for all value-labels for all DataSets this data object 698 * contains. 699 * 700 * @param tf 701 */ setValueTypeface(Typeface tf)702 public void setValueTypeface(Typeface tf) { 703 for (IDataSet set : mDataSets) { 704 set.setValueTypeface(tf); 705 } 706 } 707 708 /** 709 * Sets the size (in dp) of the value-text for all DataSets this data object 710 * contains. 711 * 712 * @param size 713 */ setValueTextSize(float size)714 public void setValueTextSize(float size) { 715 for (IDataSet set : mDataSets) { 716 set.setValueTextSize(size); 717 } 718 } 719 720 /** 721 * Enables / disables drawing values (value-text) for all DataSets this data 722 * object contains. 723 * 724 * @param enabled 725 */ setDrawValues(boolean enabled)726 public void setDrawValues(boolean enabled) { 727 for (IDataSet set : mDataSets) { 728 set.setDrawValues(enabled); 729 } 730 } 731 732 /** 733 * Enables / disables highlighting values for all DataSets this data object 734 * contains. If set to true, this means that values can 735 * be highlighted programmatically or by touch gesture. 736 */ setHighlightEnabled(boolean enabled)737 public void setHighlightEnabled(boolean enabled) { 738 for (IDataSet set : mDataSets) { 739 set.setHighlightEnabled(enabled); 740 } 741 } 742 743 /** 744 * Returns true if highlighting of all underlying values is enabled, false 745 * if not. 746 * 747 * @return 748 */ isHighlightEnabled()749 public boolean isHighlightEnabled() { 750 for (IDataSet set : mDataSets) { 751 if (!set.isHighlightEnabled()) 752 return false; 753 } 754 return true; 755 } 756 757 /** 758 * Clears this data object from all DataSets and removes all Entries. Don't 759 * forget to invalidate the chart after this. 760 */ clearValues()761 public void clearValues() { 762 if (mDataSets != null) { 763 mDataSets.clear(); 764 } 765 notifyDataChanged(); 766 } 767 768 /** 769 * Checks if this data object contains the specified DataSet. Returns true 770 * if so, false if not. 771 * 772 * @param dataSet 773 * @return 774 */ contains(T dataSet)775 public boolean contains(T dataSet) { 776 777 for (T set : mDataSets) { 778 if (set.equals(dataSet)) 779 return true; 780 } 781 782 return false; 783 } 784 785 /** 786 * Returns the total entry count across all DataSet objects this data object contains. 787 * 788 * @return 789 */ getEntryCount()790 public int getEntryCount() { 791 792 int count = 0; 793 794 for (T set : mDataSets) { 795 count += set.getEntryCount(); 796 } 797 798 return count; 799 } 800 801 /** 802 * Returns the DataSet object with the maximum number of entries or null if there are no DataSets. 803 * 804 * @return 805 */ getMaxEntryCountSet()806 public T getMaxEntryCountSet() { 807 808 if (mDataSets == null || mDataSets.isEmpty()) 809 return null; 810 811 T max = mDataSets.get(0); 812 813 for (T set : mDataSets) { 814 815 if (set.getEntryCount() > max.getEntryCount()) 816 max = set; 817 } 818 819 return max; 820 } 821 } 822