00001
00002
00003
00004
00005
00006
00007 package org.swtchart.internal.axis;
00008
00009 import java.util.ArrayList;
00010 import java.util.List;
00011
00012 import org.eclipse.swt.SWT;
00013 import org.eclipse.swt.widgets.Event;
00014 import org.swtchart.Chart;
00015 import org.swtchart.IAxis;
00016 import org.swtchart.IDisposeListener;
00017 import org.swtchart.IGrid;
00018 import org.swtchart.ISeries;
00019 import org.swtchart.ITitle;
00020 import org.swtchart.Range;
00021 import org.swtchart.internal.Grid;
00022 import org.swtchart.internal.series.Series;
00023 import org.swtchart.internal.series.SeriesSet;
00024
00028 public class Axis implements IAxis {
00029
00031 public final static int MARGIN = 5;
00032
00034 public final static double DEFAULT_MIN = 0d;
00035
00037 public final static double DEFAULT_MAX = 1d;
00038
00040 public final static double DEFAULT_LOG_SCALE_MIN = 0.1d;
00041
00043 public final static double DEFAULT_LOG_SCALE_MAX = 1d;
00044
00046 private static final double ZOOM_RATIO = 0.2;
00047
00049 private static final double SCROLL_RATIO = 0.1;
00050
00052 private static final double MAX_RESOLUTION = 13;
00053
00055 private int id;
00056
00058 private Direction direction;
00059
00061 private Position position;
00062
00064 private double min;
00065
00067 private double max;
00068
00070 private AxisTitle title;
00071
00073 private AxisTick tick;
00074
00076 private Grid grid;
00077
00079 private Chart chart;
00080
00082 private boolean logScaleEnabled;
00083
00085 private boolean categoryAxisEnabled;
00086
00088 private String[] categorySeries;
00089
00091 private int numRisers;
00092
00094 private boolean isHorizontalAxis;
00095
00097 private int width;
00098
00100 private int height;
00101
00103 private List<IDisposeListener> listeners;
00104
00115 public Axis(int id, Direction direction, Chart chart) {
00116 this.id = id;
00117 this.direction = direction;
00118 this.chart = chart;
00119
00120 grid = new Grid(this);
00121 title = new AxisTitle(chart, SWT.NONE, this, direction);
00122 tick = new AxisTick(chart, this);
00123 listeners = new ArrayList<IDisposeListener>();
00124
00125
00126 position = Position.Primary;
00127 min = DEFAULT_MIN;
00128 max = DEFAULT_MAX;
00129 if (direction == Direction.X) {
00130 title.setText("X axis");
00131 } else if (direction == Direction.Y) {
00132 title.setText("Y axis");
00133 }
00134 logScaleEnabled = false;
00135 categoryAxisEnabled = false;
00136 }
00137
00138
00139
00140
00141 public int getId() {
00142 return id;
00143 }
00144
00145
00146
00147
00148 public Direction getDirection() {
00149 return direction;
00150 }
00151
00152
00153
00154
00155 public Position getPosition() {
00156 return position;
00157 }
00158
00159
00160
00161
00162 public void setPosition(Position position) {
00163 if (position == null) {
00164 SWT.error(SWT.ERROR_NULL_ARGUMENT);
00165 }
00166
00167 if (this.position == position) {
00168 return;
00169 }
00170
00171 this.position = position;
00172
00173 chart.updateLayout();
00174 }
00175
00176
00177
00178
00179 public void setRange(Range range) {
00180 setRange(range, true);
00181 }
00182
00191 public void setRange(Range range, boolean update) {
00192 if (range == null) {
00193 SWT.error(SWT.ERROR_NULL_ARGUMENT);
00194 return;
00195 }
00196
00197 if (Double.isNaN(range.lower) || Double.isNaN(range.upper)
00198 || Double.isInfinite(range.lower)
00199 || Double.isInfinite(range.upper) || range.lower > range.upper) {
00200 throw new IllegalArgumentException("Illegal range: " + range);
00201 }
00202
00203 if (min == range.lower && max == range.upper) {
00204 return;
00205 }
00206
00207 if (isValidCategoryAxis()) {
00208 min = (int) range.lower;
00209 max = (int) range.upper;
00210 if (min < 0) {
00211 min = 0;
00212 }
00213 if (max > categorySeries.length - 1) {
00214 max = categorySeries.length - 1;
00215 }
00216 } else {
00217 if (range.lower == range.upper) {
00218 throw new IllegalArgumentException("Given range is invalid");
00219 }
00220
00221 if (logScaleEnabled && range.lower <= 0) {
00222 range.lower = min;
00223 }
00224
00225 if (Math.abs(range.lower / (range.upper - range.lower)) > Math.pow(
00226 10, MAX_RESOLUTION)) {
00227 return;
00228 }
00229
00230 min = range.lower;
00231 max = range.upper;
00232 }
00233
00234 if (update) {
00235 chart.updateLayout();
00236 }
00237 }
00238
00239
00240
00241
00242 public Range getRange() {
00243 return new Range(min, max);
00244 }
00245
00246
00247
00248
00249 public ITitle getTitle() {
00250 return title;
00251 }
00252
00253
00254
00255
00256 public AxisTick getTick() {
00257 return tick;
00258 }
00259
00260
00261
00262
00263 public void enableLogScale(boolean enabled) throws IllegalStateException {
00264
00265 if (logScaleEnabled == enabled) {
00266 return;
00267 }
00268
00269 if (enabled) {
00270
00271 double minSeriesValue = getMinSeriesValue();
00272 if (minSeriesValue <= 0) {
00273 throw new IllegalStateException(
00274 "Series contain zero or negative value.");
00275 }
00276
00277
00278 if (min <= 0) {
00279 min = Double.isNaN(minSeriesValue) ? DEFAULT_LOG_SCALE_MIN
00280 : minSeriesValue;
00281 }
00282 if (max < min) {
00283 max = DEFAULT_LOG_SCALE_MAX;
00284 }
00285
00286
00287 if (categoryAxisEnabled) {
00288 categoryAxisEnabled = false;
00289 ((SeriesSet) chart.getSeriesSet()).updateCompressor(this);
00290 }
00291 }
00292
00293 logScaleEnabled = enabled;
00294
00295 chart.updateLayout();
00296
00297 ((SeriesSet) chart.getSeriesSet()).compressAllSeries();
00298 }
00299
00305 private double getMinSeriesValue() {
00306 double minimum = Double.NaN;
00307 for (ISeries series : chart.getSeriesSet().getSeries()) {
00308 if (series.getYSeries().length == 0) {
00309 continue;
00310 }
00311
00312 double lower;
00313 if (direction == Direction.X && series.getXAxisId() == getId()) {
00314 lower = ((Series) series).getXRange().lower;
00315 } else if (direction == Direction.Y
00316 && series.getYAxisId() == getId()) {
00317 lower = ((Series) series).getYRange().lower;
00318 } else {
00319 continue;
00320 }
00321
00322 if (Double.isNaN(minimum) || lower < minimum) {
00323 minimum = lower;
00324 }
00325 }
00326 return minimum;
00327 }
00328
00329
00330
00331
00332 public boolean isLogScaleEnabled() {
00333 return logScaleEnabled;
00334 }
00335
00336
00337
00338
00339 public IGrid getGrid() {
00340 return grid;
00341 }
00342
00343
00344
00345
00346 public void adjustRange() {
00347 adjustRange(true);
00348 }
00349
00356 public void adjustRange(boolean update) {
00357 if (isValidCategoryAxis()) {
00358 setRange(new Range(0, categorySeries.length - 1));
00359 return;
00360 }
00361
00362 double minimum = Double.NaN;
00363 double maximum = Double.NaN;
00364 for (ISeries series : chart.getSeriesSet().getSeries()) {
00365 int axisId = direction == Direction.X ? series.getXAxisId()
00366 : series.getYAxisId();
00367 if (!series.isVisible() || getId() != axisId) {
00368 continue;
00369 }
00370
00371
00372 int length;
00373 if (isHorizontalAxis) {
00374 length = chart.getPlotArea().getSize().x;
00375 } else {
00376 length = chart.getPlotArea().getSize().y;
00377 }
00378
00379
00380 Range range = ((Series) series).getAdjustedRange(this, length);
00381 if (Double.isNaN(minimum) || range.lower < minimum) {
00382 minimum = range.lower;
00383 }
00384 if (Double.isNaN(maximum) || range.upper > maximum) {
00385 maximum = range.upper;
00386 }
00387 }
00388
00389
00390 if (!Double.isNaN(minimum) && !Double.isNaN(maximum)) {
00391 if (minimum == maximum) {
00392 double margin = (minimum == 0)? 1d : Math.abs(minimum / 2d);
00393 minimum -= margin;
00394 maximum += margin;
00395 }
00396 setRange(new Range(minimum, maximum), update);
00397 }
00398 }
00399
00400
00401
00402
00403 public void zoomIn() {
00404 zoomIn((max + min) / 2d);
00405 }
00406
00407
00408
00409
00410 public void zoomIn(double coordinate) {
00411 double lower = min;
00412 double upper = max;
00413 if (isValidCategoryAxis()) {
00414 if (lower != upper) {
00415 if ((min + max) / 2d < coordinate) {
00416 lower = min + 1;
00417 } else if (coordinate < (min + max) / 2d) {
00418 upper = max - 1;
00419 } else {
00420 lower = min + 1;
00421 upper = max - 1;
00422 }
00423 }
00424 } else if (isLogScaleEnabled()) {
00425 double digitMin = Math.log10(min);
00426 double digitMax = Math.log10(max);
00427 double digitCoordinate = Math.log10(coordinate);
00428 lower = Math.pow(10, digitMin + 2 * SCROLL_RATIO
00429 * (digitCoordinate - digitMin));
00430 upper = Math.pow(10, digitMax + 2 * SCROLL_RATIO
00431 * (digitCoordinate - digitMax));
00432 } else {
00433 lower = min + 2 * ZOOM_RATIO * (coordinate - min);
00434 upper = max + 2 * ZOOM_RATIO * (coordinate - max);
00435 }
00436
00437 setRange(new Range(lower, upper));
00438 }
00439
00440
00441
00442
00443 public void zoomOut() {
00444 zoomOut((min + max) / 2d);
00445 }
00446
00447
00448
00449
00450 public void zoomOut(double coordinate) {
00451 double lower = min;
00452 double upper = max;
00453 if (isValidCategoryAxis()) {
00454 if ((min + max) / 2d < coordinate && min != 0) {
00455 lower = min - 1;
00456 } else if (coordinate < (min + max) / 2d
00457 && max != categorySeries.length - 1) {
00458 upper = max + 1;
00459 } else {
00460 lower = min - 1;
00461 upper = max + 1;
00462 }
00463 } else if (isLogScaleEnabled()) {
00464 double digitMin = Math.log10(min);
00465 double digitMax = Math.log10(max);
00466 double digitCoordinate = Math.log10(coordinate);
00467 lower = Math.pow(10, (digitMin - ZOOM_RATIO * digitCoordinate)
00468 / (1 - ZOOM_RATIO));
00469 upper = Math.pow(10, (digitMax - ZOOM_RATIO * digitCoordinate)
00470 / (1 - ZOOM_RATIO));
00471 } else {
00472 lower = (min - 2 * ZOOM_RATIO * coordinate) / (1 - 2 * ZOOM_RATIO);
00473 upper = (max - 2 * ZOOM_RATIO * coordinate) / (1 - 2 * ZOOM_RATIO);
00474 }
00475
00476 setRange(new Range(lower, upper));
00477 }
00478
00479
00480
00481
00482 public void scrollUp() {
00483 double lower = min;
00484 double upper = max;
00485 if (isValidCategoryAxis()) {
00486 if (upper < categorySeries.length - 1) {
00487 lower = min + 1;
00488 upper = max + 1;
00489 }
00490 } else if (isLogScaleEnabled()) {
00491 double digitMax = Math.log10(upper);
00492 double digitMin = Math.log10(lower);
00493 upper = Math.pow(10, digitMax + (digitMax - digitMin)
00494 * SCROLL_RATIO);
00495 lower = Math.pow(10, digitMin + (digitMax - digitMin)
00496 * SCROLL_RATIO);
00497 } else {
00498 lower = min + (max - min) * SCROLL_RATIO;
00499 upper = max + (max - min) * SCROLL_RATIO;
00500 }
00501
00502 setRange(new Range(lower, upper));
00503 }
00504
00505
00506
00507
00508 public void scrollDown() {
00509 double lower = min;
00510 double upper = max;
00511 if (isValidCategoryAxis()) {
00512 if (lower >= 1) {
00513 lower = min - 1;
00514 upper = max - 1;
00515 }
00516 } else if (isLogScaleEnabled()) {
00517 double digitMax = Math.log10(upper);
00518 double digitMin = Math.log10(lower);
00519 upper = Math.pow(10, digitMax - (digitMax - digitMin)
00520 * SCROLL_RATIO);
00521 lower = Math.pow(10, digitMin - (digitMax - digitMin)
00522 * SCROLL_RATIO);
00523 } else {
00524 lower = min - (max - min) * SCROLL_RATIO;
00525 upper = max - (max - min) * SCROLL_RATIO;
00526 }
00527
00528 setRange(new Range(lower, upper));
00529 }
00530
00531
00532
00533
00534 public boolean isCategoryEnabled() {
00535 return categoryAxisEnabled;
00536 }
00537
00543 public boolean isValidCategoryAxis() {
00544 return categoryAxisEnabled && categorySeries != null
00545 && categorySeries.length != 0;
00546 }
00547
00548
00549
00550
00551 public void enableCategory(boolean enabled) {
00552 if (categoryAxisEnabled == enabled) {
00553 return;
00554 }
00555
00556 if (enabled) {
00557 if (direction == Direction.Y) {
00558 throw new IllegalStateException(
00559 "Y axis cannot be category axis.");
00560 }
00561
00562 if (categorySeries != null && categorySeries.length != 0) {
00563 min = (min < 0 || min >= categorySeries.length) ? 0 : (int) min;
00564 max = (max < 0 || max >= categorySeries.length) ? max = categorySeries.length - 1
00565 : (int) max;
00566 }
00567
00568 logScaleEnabled = false;
00569 }
00570
00571 categoryAxisEnabled = enabled;
00572
00573 chart.updateLayout();
00574
00575 ((SeriesSet) chart.getSeriesSet()).updateCompressor(this);
00576 ((SeriesSet) chart.getSeriesSet()).updateStackAndRiserData();
00577 }
00578
00579
00580
00581
00582 public void setCategorySeries(String[] series) {
00583 if (series == null) {
00584 SWT.error(SWT.ERROR_NULL_ARGUMENT);
00585 return;
00586 }
00587
00588 if (direction == Direction.Y) {
00589 throw new IllegalStateException("Y axis cannot be category axis.");
00590 }
00591
00592 String[] copiedSeries = new String[series.length];
00593 System.arraycopy(series, 0, copiedSeries, 0, series.length);
00594 categorySeries = copiedSeries;
00595
00596 if (isValidCategoryAxis()) {
00597 min = (min < 0) ? 0 : (int) min;
00598 max = (max >= categorySeries.length) ? max = categorySeries.length - 1
00599 : (int) max;
00600 }
00601
00602 chart.updateLayout();
00603
00604 ((SeriesSet) chart.getSeriesSet()).updateCompressor(this);
00605 ((SeriesSet) chart.getSeriesSet()).updateStackAndRiserData();
00606 }
00607
00608
00609
00610
00611 public String[] getCategorySeries() {
00612
00613 String[] copiedCategorySeries = null;
00614
00615 if (categorySeries != null) {
00616 copiedCategorySeries = new String[categorySeries.length];
00617 System.arraycopy(categorySeries, 0, copiedCategorySeries, 0,
00618 categorySeries.length);
00619 }
00620
00621 return copiedCategorySeries;
00622 }
00623
00624
00625
00626
00627 public int getPixelCoordinate(double dataCoordinate) {
00628 return getPixelCoordinate(dataCoordinate, min, max);
00629 }
00630
00642 public int getPixelCoordinate(double dataCoordinate, double lower,
00643 double upper) {
00644 int pixelCoordinate;
00645 if (isHorizontalAxis) {
00646 if (logScaleEnabled) {
00647 pixelCoordinate = (int) ((Math.log10(dataCoordinate) - Math
00648 .log10(lower))
00649 / (Math.log10(upper) - Math.log10(lower)) * width);
00650 } else if (categoryAxisEnabled) {
00651 pixelCoordinate = (int) ((dataCoordinate + 0.5 - lower)
00652 / (upper + 1 - lower) * width);
00653 } else {
00654 pixelCoordinate = (int) ((dataCoordinate - lower)
00655 / (upper - lower) * width);
00656 }
00657 } else {
00658 if (logScaleEnabled) {
00659 pixelCoordinate = (int) ((Math.log10(upper) - Math
00660 .log10(dataCoordinate))
00661 / (Math.log10(upper) - Math.log10(lower)) * height);
00662 } else if (categoryAxisEnabled) {
00663 pixelCoordinate = (int) ((upper - dataCoordinate + 0.5)
00664 / (upper + 1 - lower) * height);
00665 } else {
00666 pixelCoordinate = (int) ((upper - dataCoordinate)
00667 / (upper - lower) * height);
00668 }
00669 }
00670 return pixelCoordinate;
00671 }
00672
00673
00674
00675
00676 public double getDataCoordinate(int pixelCoordinate) {
00677 return getDataCoordinate(pixelCoordinate, min, max);
00678 }
00679
00692 public double getDataCoordinate(int pixelCoordinate, double lower,
00693 double upper) {
00694 double dataCoordinate;
00695 if (isHorizontalAxis) {
00696 if (logScaleEnabled) {
00697 dataCoordinate = Math.pow(10, pixelCoordinate / (double) width
00698 * (Math.log10(upper) - Math.log10(lower))
00699 + Math.log10(lower));
00700 } else if (categoryAxisEnabled) {
00701 dataCoordinate = Math.floor(pixelCoordinate / (double) width
00702 * (upper + 1 - lower) + lower);
00703 } else {
00704 dataCoordinate = pixelCoordinate / (double) width
00705 * (upper - lower) + lower;
00706 }
00707 } else {
00708 if (logScaleEnabled) {
00709 dataCoordinate = Math.pow(10, Math.log10(upper)
00710 - pixelCoordinate / (double) height
00711 * (Math.log10(upper) - Math.log10(lower)));
00712 } else if (categoryAxisEnabled) {
00713 dataCoordinate = Math.floor(upper + 1 - pixelCoordinate
00714 / (double) height * (upper + 1 - lower));
00715 } else {
00716 dataCoordinate = (height - pixelCoordinate) / (double) height
00717 * (upper - lower) + lower;
00718 }
00719 }
00720 return dataCoordinate;
00721 }
00722
00729 public void setNumRisers(int numRisers) {
00730 this.numRisers = numRisers;
00731 }
00732
00738 public int getNumRisers() {
00739 return numRisers;
00740 }
00741
00748 public boolean isHorizontalAxis() {
00749 int orientation = chart.getOrientation();
00750 return (direction == Direction.X && orientation == SWT.HORIZONTAL)
00751 || (direction == Direction.Y && orientation == SWT.VERTICAL);
00752 }
00753
00757 protected void dispose() {
00758 tick.getAxisTickLabels().dispose();
00759 tick.getAxisTickMarks().dispose();
00760 title.dispose();
00761
00762 for (IDisposeListener listener : listeners) {
00763 listener.disposed(new Event());
00764 }
00765 }
00766
00767
00768
00769
00770 public void addDisposeListener(IDisposeListener listener) {
00771 listeners.add(listener);
00772 }
00773
00777 public void updateLayoutData() {
00778 title.updateLayoutData();
00779 tick.updateLayoutData();
00780 }
00781
00785 public void refresh() {
00786 int orientation = chart.getOrientation();
00787 isHorizontalAxis = (direction == Direction.X && orientation == SWT.HORIZONTAL)
00788 || (direction == Direction.Y && orientation == SWT.VERTICAL);
00789 width = chart.getPlotArea().getBounds().width;
00790 height = chart.getPlotArea().getBounds().height;
00791 }
00792
00798 public boolean isDateEnabled() {
00799 if (!isHorizontalAxis) {
00800 return false;
00801 }
00802
00803 for (ISeries series : chart.getSeriesSet().getSeries()) {
00804 if (series.getXAxisId() != id) {
00805 continue;
00806 }
00807
00808 if (((Series) series).isDateSeries() && series.isVisible()) {
00809 return true;
00810 }
00811 }
00812 return false;
00813 }
00814 }