1/*
2 * Copyright 2012 AndroidPlot.com
3 *
4 *    Licensed under the Apache License, Version 2.0 (the "License");
5 *    you may not use this file except in compliance with the License.
6 *    You may obtain a copy of the License at
7 *
8 *        http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *    Unless required by applicable law or agreed to in writing, software
11 *    distributed under the License is distributed on an "AS IS" BASIS,
12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *    See the License for the specific language governing permissions and
14 *    limitations under the License.
15 */
16
17package com.androidplot.xy;
18
19import android.content.Context;
20import android.graphics.Canvas;
21import android.graphics.Color;
22import android.graphics.Paint;
23import android.graphics.PointF;
24import android.util.AttributeSet;
25import com.androidplot.Plot;
26import com.androidplot.ui.*;
27import com.androidplot.ui.TextOrientationType;
28import com.androidplot.ui.widget.TextLabelWidget;
29import com.androidplot.util.PixelUtils;
30
31import java.text.Format;
32import java.util.ArrayList;
33import java.util.List;
34
35/**
36 * A View to graphically display x/y coordinates.
37 */
38public class XYPlot extends Plot<XYSeries, XYSeriesFormatter, XYSeriesRenderer> {
39
40    private BoundaryMode domainOriginBoundaryMode;
41    private BoundaryMode rangeOriginBoundaryMode;
42
43    // widgets
44    private XYLegendWidget legendWidget;
45    private XYGraphWidget graphWidget;
46    private TextLabelWidget domainLabelWidget;
47    private TextLabelWidget rangeLabelWidget;
48
49    private XYStepMode domainStepMode = XYStepMode.SUBDIVIDE;
50    private double domainStepValue = 10;
51
52    private XYStepMode rangeStepMode = XYStepMode.SUBDIVIDE;
53    private double rangeStepValue = 10;
54
55    // user settable min/max values
56    private Number userMinX;
57    private Number userMaxX;
58    private Number userMinY;
59    private Number userMaxY;
60
61    // these are the final min/max used for dispplaying data
62    private Number calculatedMinX;
63    private Number calculatedMaxX;
64    private Number calculatedMinY;
65    private Number calculatedMaxY;
66
67    // previous calculated min/max vals.
68    // primarily used for GROW/SHRINK operations.
69    private Number prevMinX;
70    private Number prevMaxX;
71    private Number prevMinY;
72    private Number prevMaxY;
73
74    // uses set boundary min and max values
75    // should be null if not used.
76    private Number rangeTopMin = null;
77    private Number rangeTopMax = null;
78    private Number rangeBottomMin = null;
79    private Number rangeBottomMax = null;
80    private Number domainLeftMin = null;
81    private Number domainLeftMax = null;
82    private Number domainRightMin = null;
83    private Number domainRightMax = null;
84
85    // used for  calculating the domain/range extents that will be displayed on the plot.
86    // using boundaries and origins are mutually exclusive.  because of this,
87    // setting one will disable the other.  when only setting the FramingModel,
88    // the origin or boundary is set to the current value of the plot.
89    private XYFramingModel domainFramingModel = XYFramingModel.EDGE;
90    private XYFramingModel rangeFramingModel = XYFramingModel.EDGE;
91
92    private Number userDomainOrigin;
93    private Number userRangeOrigin;
94
95    private Number calculatedDomainOrigin;
96    private Number calculatedRangeOrigin;
97
98    @SuppressWarnings("FieldCanBeLocal")
99    private Number domainOriginExtent = null;
100    @SuppressWarnings("FieldCanBeLocal")
101    private Number rangeOriginExtent = null;
102
103    private BoundaryMode domainUpperBoundaryMode = BoundaryMode.AUTO;
104    private BoundaryMode domainLowerBoundaryMode = BoundaryMode.AUTO;
105    private BoundaryMode rangeUpperBoundaryMode = BoundaryMode.AUTO;
106    private BoundaryMode rangeLowerBoundaryMode = BoundaryMode.AUTO;
107
108    private boolean drawDomainOriginEnabled = true;
109    private boolean drawRangeOriginEnabled = true;
110
111    private ArrayList<YValueMarker> yValueMarkers;
112    private ArrayList<XValueMarker> xValueMarkers;
113
114    private RectRegion defaultBounds;
115
116
117    private static final int DEFAULT_LEGEND_WIDGET_H_DP = 10;
118    private static final int DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP = 7;
119    private static final int DEFAULT_GRAPH_WIDGET_H_DP = 18;
120    private static final int DEFAULT_GRAPH_WIDGET_W_DP = 10;
121    private static final int DEFAULT_DOMAIN_LABEL_WIDGET_H_DP = 10;
122    private static final int DEFAULT_DOMAIN_LABEL_WIDGET_W_DP = 80;
123    private static final int DEFAULT_RANGE_LABEL_WIDGET_H_DP = 50;
124    private static final int DEFAULT_RANGE_LABEL_WIDGET_W_DP = 10;
125
126    private static final int DEFAULT_LEGEND_WIDGET_Y_OFFSET_DP = 0;
127    private static final int DEFAULT_LEGEND_WIDGET_X_OFFSET_DP = 40;
128    private static final int DEFAULT_GRAPH_WIDGET_Y_OFFSET_DP = 0;
129    private static final int DEFAULT_GRAPH_WIDGET_X_OFFSET_DP = 0;
130    private static final int DEFAULT_DOMAIN_LABEL_WIDGET_Y_OFFSET_DP = 0;
131    private static final int DEFAULT_DOMAIN_LABEL_WIDGET_X_OFFSET_DP = 20;
132    private static final int DEFAULT_RANGE_LABEL_WIDGET_Y_OFFSET_DP = 0;
133    private static final int DEFAULT_RANGE_LABEL_WIDGET_X_OFFSET_DP = 0;
134
135    private static final int DEFAULT_GRAPH_WIDGET_TOP_MARGIN_DP = 3;
136    private static final int DEFAULT_GRAPH_WIDGET_RIGHT_MARGIN_DP = 3;
137    private static final int DEFAULT_PLOT_LEFT_MARGIN_DP = 2;
138    private static final int DEFAULT_PLOT_RIGHT_MARGIN_DP = 2;
139    private static final int DEFAULT_PLOT_BOTTOM_MARGIN_DP = 2;
140
141    public XYPlot(Context context, String title) {
142        super(context, title);
143    }
144
145    public XYPlot(Context context, String title, RenderMode mode) {
146        super(context, title, mode);
147    }
148
149    public XYPlot(Context context, AttributeSet attributes) {
150        super(context, attributes);
151    }
152
153    public XYPlot(Context context, AttributeSet attrs, int defStyle) {
154        super(context, attrs, defStyle);
155
156    }
157
158    @Override
159    protected void onPreInit() {
160        legendWidget = new XYLegendWidget(
161                getLayoutManager(),
162                this,
163                new SizeMetrics(
164                        PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_H_DP),
165                        SizeLayoutType.ABSOLUTE, 0.5f, SizeLayoutType.RELATIVE),
166                new DynamicTableModel(0, 1),
167                new SizeMetrics(
168                        PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP),
169                        SizeLayoutType.ABSOLUTE,
170                        PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP),
171                        SizeLayoutType.ABSOLUTE));
172
173        graphWidget = new XYGraphWidget(
174                getLayoutManager(),
175                this,
176                new SizeMetrics(
177                        PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_H_DP),
178                        SizeLayoutType.FILL,
179                        PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_W_DP),
180                        SizeLayoutType.FILL));
181
182        Paint backgroundPaint = new Paint();
183        backgroundPaint.setColor(Color.DKGRAY);
184        backgroundPaint.setStyle(Paint.Style.FILL);
185        graphWidget.setBackgroundPaint(backgroundPaint);
186
187
188        domainLabelWidget = new TextLabelWidget(
189                getLayoutManager(),
190                new SizeMetrics(
191                        PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_H_DP),
192                        SizeLayoutType.ABSOLUTE,
193                        PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_W_DP),
194                        SizeLayoutType.ABSOLUTE),
195                TextOrientationType.HORIZONTAL);
196        rangeLabelWidget = new TextLabelWidget(
197                getLayoutManager(),
198                new SizeMetrics(
199                        PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_H_DP),
200                        SizeLayoutType.ABSOLUTE,
201                        PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_W_DP),
202                        SizeLayoutType.ABSOLUTE),
203                TextOrientationType.VERTICAL_ASCENDING);
204
205        legendWidget.position(
206                PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_X_OFFSET_DP),
207                XLayoutStyle.ABSOLUTE_FROM_RIGHT,
208                PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_Y_OFFSET_DP),
209                YLayoutStyle.ABSOLUTE_FROM_BOTTOM,
210                AnchorPosition.RIGHT_BOTTOM);
211
212        graphWidget.position(
213                PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_X_OFFSET_DP),
214                XLayoutStyle.ABSOLUTE_FROM_RIGHT,
215                PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_Y_OFFSET_DP),
216                YLayoutStyle.ABSOLUTE_FROM_CENTER,
217                AnchorPosition.RIGHT_MIDDLE);
218
219        domainLabelWidget.position(
220                PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_X_OFFSET_DP),
221                XLayoutStyle.ABSOLUTE_FROM_LEFT,
222                PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_Y_OFFSET_DP),
223                YLayoutStyle.ABSOLUTE_FROM_BOTTOM,
224                AnchorPosition.LEFT_BOTTOM);
225
226        rangeLabelWidget.position(
227                PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_X_OFFSET_DP),
228                XLayoutStyle.ABSOLUTE_FROM_LEFT,
229                PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_Y_OFFSET_DP),
230                YLayoutStyle.ABSOLUTE_FROM_CENTER,
231                AnchorPosition.LEFT_MIDDLE);
232
233        getLayoutManager().moveToTop(getTitleWidget());
234        getLayoutManager().moveToTop(getLegendWidget());
235        graphWidget.setMarginTop(PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_TOP_MARGIN_DP));
236        graphWidget.setMarginRight(PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_RIGHT_MARGIN_DP));
237
238        getDomainLabelWidget().pack();
239        getRangeLabelWidget().pack();
240        setPlotMarginLeft(PixelUtils.dpToPix(DEFAULT_PLOT_LEFT_MARGIN_DP));
241        setPlotMarginRight(PixelUtils.dpToPix(DEFAULT_PLOT_RIGHT_MARGIN_DP));
242        setPlotMarginBottom(PixelUtils.dpToPix(DEFAULT_PLOT_BOTTOM_MARGIN_DP));
243
244        xValueMarkers = new ArrayList<XValueMarker>();
245        yValueMarkers = new ArrayList<YValueMarker>();
246
247        setDefaultBounds(new RectRegion(-1, 1, -1, 1));
248    }
249
250
251    public void setGridPadding(float left, float top, float right, float bottom) {
252        getGraphWidget().setGridPaddingTop(top);
253        getGraphWidget().setGridPaddingBottom(bottom);
254        getGraphWidget().setGridPaddingLeft(left);
255        getGraphWidget().setGridPaddingRight(right);
256    }
257
258    @Override
259    protected void notifyListenersBeforeDraw(Canvas canvas) {
260        super.notifyListenersBeforeDraw(canvas);
261
262        // this call must be AFTER the notify so that if the listener
263        // is a synchronized series, it has the opportunity to
264        // place a read lock on it's data.
265        calculateMinMaxVals();
266    }
267
268    /**
269     * Checks whether the point is within the plot's graph area.
270     *
271     * @param x
272     * @param y
273     * @return
274     */
275    public boolean containsPoint(float x, float y) {
276        if (getGraphWidget().getGridRect() != null) {
277            return getGraphWidget().getGridRect().contains(x, y);
278        }
279        return false;
280    }
281
282
283    /**
284     * Convenience method - wraps containsPoint(PointF).
285     *
286     * @param point
287     * @return
288     */
289    public boolean containsPoint(PointF point) {
290        return containsPoint(point.x, point.y);
291    }
292
293    public void setCursorPosition(PointF point) {
294        getGraphWidget().setCursorPosition(point);
295    }
296
297    public void setCursorPosition(float x, float y) {
298        getGraphWidget().setCursorPosition(x, y);
299    }
300
301    public Number getYVal(PointF point) {
302        return getGraphWidget().getYVal(point);
303    }
304
305    public Number getXVal(PointF point) {
306        return getGraphWidget().getXVal(point);
307    }
308
309    private boolean isXValWithinView(double xVal) {
310        return (userMinY == null || xVal >= userMinY.doubleValue()) &&
311                userMaxY == null || xVal <= userMaxY.doubleValue();
312    }
313
314    private boolean isPointVisible(Number x, Number y) {
315        // values without both an x and y val arent visible
316        if (x == null || y == null) {
317            return false;
318        }
319        return isValWithinRange(y.doubleValue(), userMinY, userMaxY) &&
320                isValWithinRange(x.doubleValue(), userMinX, userMaxX);
321    }
322
323    private boolean isValWithinRange(double val, Number min, Number max) {
324        boolean isAboveMinThreshold = min == null || val >= min.doubleValue();
325        boolean isBelowMaxThreshold = max == null || val <= max.doubleValue();
326        return isAboveMinThreshold &&
327                isBelowMaxThreshold;
328    }
329
330    public void calculateMinMaxVals() {
331        prevMinX = calculatedMinX;
332        prevMaxX = calculatedMaxX;
333        prevMinY = calculatedMinY;
334        prevMaxY = calculatedMaxY;
335
336        calculatedMinX = userMinX;
337        calculatedMaxX = userMaxX;
338        calculatedMinY = userMinY;
339        calculatedMaxY = userMaxY;
340
341        // next we go through each series to update our min/max values:
342        for (final XYSeries series : getSeriesSet()) {
343            // step through each point in each series:
344            for (int i = 0; i < series.size(); i++) {
345                Number thisX = series.getX(i);
346                Number thisY = series.getY(i);
347                if (isPointVisible(thisX, thisY)) {
348                    // only calculate if a static value has not been set:
349                    if (userMinX == null) {
350                        if (thisX != null && (calculatedMinX == null ||
351                                thisX.doubleValue() < calculatedMinX.doubleValue())) {
352                            calculatedMinX = thisX;
353                        }
354                    }
355
356                    if (userMaxX == null) {
357                        if (thisX != null && (calculatedMaxX == null ||
358                                thisX.doubleValue() > calculatedMaxX.doubleValue())) {
359                            calculatedMaxX = thisX;
360                        }
361                    }
362
363                    if (userMinY == null) {
364                        if (thisY != null && (calculatedMinY == null ||
365                                thisY.doubleValue() < calculatedMinY.doubleValue())) {
366                            calculatedMinY = thisY;
367                        }
368                    }
369
370                    if (userMaxY == null) {
371                        if (thisY != null && (calculatedMaxY == null || thisY.doubleValue() > calculatedMaxY.doubleValue())) {
372                            calculatedMaxY = thisY;
373                        }
374                    }
375                }
376            }
377        }
378
379        // at this point we now know what points are going to be visible on our
380        // plot, but we still need to make corrections based on modes being used:
381        // (grow, shrink etc.)
382        switch (domainFramingModel) {
383            case ORIGIN:
384                updateDomainMinMaxForOriginModel();
385                break;
386            case EDGE:
387                updateDomainMinMaxForEdgeModel();
388                calculatedMinX = ApplyUserMinMax(calculatedMinX, domainLeftMin,
389                        domainLeftMax);
390                calculatedMaxX = ApplyUserMinMax(calculatedMaxX,
391                        domainRightMin, domainRightMax);
392                break;
393            default:
394                throw new UnsupportedOperationException(
395                        "Domain Framing Model not yet supported: " + domainFramingModel);
396        }
397
398        switch (rangeFramingModel) {
399            case ORIGIN:
400                updateRangeMinMaxForOriginModel();
401                break;
402            case EDGE:
403            	if (getSeriesSet().size() > 0) {
404	                updateRangeMinMaxForEdgeModel();
405	                calculatedMinY = ApplyUserMinMax(calculatedMinY,
406	                        rangeBottomMin, rangeBottomMax);
407	                calculatedMaxY = ApplyUserMinMax(calculatedMaxY, rangeTopMin,
408	                        rangeTopMax);
409            	}
410                break;
411            default:
412                throw new UnsupportedOperationException(
413                        "Range Framing Model not yet supported: " + domainFramingModel);
414        }
415
416        calculatedDomainOrigin = userDomainOrigin != null ? userDomainOrigin : getCalculatedMinX();
417        calculatedRangeOrigin = this.userRangeOrigin != null ? userRangeOrigin : getCalculatedMinY();
418    }
419
420    /**
421     * Should ONLY be called from updateMinMax.
422     * Results are undefined otherwise.
423     */
424    private void updateDomainMinMaxForEdgeModel() {
425        switch (domainUpperBoundaryMode) {
426            case FIXED:
427                break;
428            case AUTO:
429                break;
430            case GROW:
431                if (!(prevMaxX == null || (calculatedMaxX.doubleValue() > prevMaxX.doubleValue()))) {
432                    calculatedMaxX = prevMaxX;
433                }
434                break;
435            case SHRINNK:
436                if (!(prevMaxX == null || calculatedMaxX.doubleValue() < prevMaxX.doubleValue())) {
437                    calculatedMaxX = prevMaxX;
438                }
439                break;
440            default:
441                throw new UnsupportedOperationException(
442                        "DomainUpperBoundaryMode not yet implemented: " + domainUpperBoundaryMode);
443        }
444
445        switch (domainLowerBoundaryMode) {
446            case FIXED:
447                break;
448            case AUTO:
449                break;
450            case GROW:
451                if (!(prevMinX == null || calculatedMinX.doubleValue() < prevMinX.doubleValue())) {
452                    calculatedMinX = prevMinX;
453                }
454                break;
455            case SHRINNK:
456                if (!(prevMinX == null || calculatedMinX.doubleValue() > prevMinX.doubleValue())) {
457                    calculatedMinX = prevMinX;
458                }
459                break;
460            default:
461                throw new UnsupportedOperationException(
462                        "DomainLowerBoundaryMode not supported: " + domainLowerBoundaryMode);
463        }
464    }
465
466    public void updateRangeMinMaxForEdgeModel() {
467        switch (rangeUpperBoundaryMode) {
468            case FIXED:
469                break;
470            case AUTO:
471                break;
472            case GROW:
473                if (!(prevMaxY == null || calculatedMaxY.doubleValue() > prevMaxY.doubleValue())) {
474                    calculatedMaxY = prevMaxY;
475                }
476                break;
477            case SHRINNK:
478                if (!(prevMaxY == null || calculatedMaxY.doubleValue() < prevMaxY.doubleValue())) {
479                    calculatedMaxY = prevMaxY;
480                }
481                break;
482            default:
483                throw new UnsupportedOperationException(
484                        "RangeUpperBoundaryMode not supported: " + rangeUpperBoundaryMode);
485        }
486
487        switch (rangeLowerBoundaryMode) {
488            case FIXED:
489                break;
490            case AUTO:
491                break;
492            case GROW:
493                if (!(prevMinY == null || calculatedMinY.doubleValue() < prevMinY.doubleValue())) {
494                    calculatedMinY = prevMinY;
495                }
496                break;
497            case SHRINNK:
498                if (!(prevMinY == null || calculatedMinY.doubleValue() > prevMinY.doubleValue())) {
499                    calculatedMinY = prevMinY;
500                }
501                break;
502            default:
503                throw new UnsupportedOperationException(
504                        "RangeLowerBoundaryMode not supported: " + rangeLowerBoundaryMode);
505        }
506    }
507
508    /**
509     * Apply user supplied min and max to the calculated boundary value.
510     *
511     * @param value
512     * @param min
513     * @param max
514     */
515    private Number ApplyUserMinMax(Number value, Number min, Number max) {
516        value = (((min == null) || (value.doubleValue() > min.doubleValue()))
517                ? value
518                : min);
519        value = (((max == null) || (value.doubleValue() < max.doubleValue()))
520                ? value
521                : max);
522        return value;
523    }
524
525    /**
526     * Centers the domain axis on origin.
527     *
528     * @param origin
529     */
530    public void centerOnDomainOrigin(Number origin) {
531        centerOnDomainOrigin(origin, null, BoundaryMode.AUTO);
532    }
533
534    /**
535     * Centers the domain on origin, calculating the upper and lower boundaries of the axis
536     * using mode and extent.
537     *
538     * @param origin
539     * @param extent
540     * @param mode
541     */
542    public void centerOnDomainOrigin(Number origin, Number extent, BoundaryMode mode) {
543        if (origin == null) {
544            throw new NullPointerException("Origin param cannot be null.");
545        }
546        domainFramingModel = XYFramingModel.ORIGIN;
547        setUserDomainOrigin(origin);
548        domainOriginExtent = extent;
549        domainOriginBoundaryMode = mode;
550
551        if (domainOriginBoundaryMode == BoundaryMode.FIXED) {
552            double domO = userDomainOrigin.doubleValue();
553            double domE = domainOriginExtent.doubleValue();
554            userMaxX = domO + domE;
555            userMinX = domO - domE;
556        } else {
557            userMaxX = null;
558            userMinX = null;
559        }
560    }
561
562    /**
563     * Centers the range axis on origin.
564     *
565     * @param origin
566     */
567    public void centerOnRangeOrigin(Number origin) {
568        centerOnRangeOrigin(origin, null, BoundaryMode.AUTO);
569    }
570
571    /**
572     * Centers the domain on origin, calculating the upper and lower boundaries of the axis
573     * using mode and extent.
574     *
575     * @param origin
576     * @param extent
577     * @param mode
578     */
579    @SuppressWarnings("SameParameterValue")
580    public void centerOnRangeOrigin(Number origin, Number extent, BoundaryMode mode) {
581        if (origin == null) {
582            throw new NullPointerException("Origin param cannot be null.");
583        }
584        rangeFramingModel = XYFramingModel.ORIGIN;
585        setUserRangeOrigin(origin);
586        rangeOriginExtent = extent;
587        rangeOriginBoundaryMode = mode;
588
589        if (rangeOriginBoundaryMode == BoundaryMode.FIXED) {
590            double raO = userRangeOrigin.doubleValue();
591            double raE = rangeOriginExtent.doubleValue();
592            userMaxY = raO + raE;
593            userMinY = raO - raE;
594        } else {
595            userMaxY = null;
596            userMinY = null;
597        }
598    }
599
600    /**
601     * Returns the distance between x and y.
602     * Result is never a negative number.
603     *
604     * @param x
605     * @param y
606     * @return
607     */
608    private double distance(double x, double y) {
609        if (x > y) {
610            return x - y;
611        } else {
612            return y - x;
613        }
614    }
615
616    public void updateDomainMinMaxForOriginModel() {
617        double origin = userDomainOrigin.doubleValue();
618        double maxXDelta = distance(calculatedMaxX.doubleValue(), origin);
619        double minXDelta = distance(calculatedMinX.doubleValue(), origin);
620        double delta = maxXDelta > minXDelta ? maxXDelta : minXDelta;
621        double dlb = origin - delta;
622        double dub = origin + delta;
623        switch (domainOriginBoundaryMode) {
624            case AUTO:
625                calculatedMinX = dlb;
626                calculatedMaxX = dub;
627
628                break;
629            // if fixed, then the value already exists within "user" vals.
630            case FIXED:
631                break;
632            case GROW: {
633
634                if (prevMinX == null || dlb < prevMinX.doubleValue()) {
635                    calculatedMinX = dlb;
636                } else {
637                    calculatedMinX = prevMinX;
638                }
639
640                if (prevMaxX == null || dub > prevMaxX.doubleValue()) {
641                    calculatedMaxX = dub;
642                } else {
643                    calculatedMaxX = prevMaxX;
644                }
645            }
646            break;
647            case SHRINNK:
648                if (prevMinX == null || dlb > prevMinX.doubleValue()) {
649                    calculatedMinX = dlb;
650                } else {
651                    calculatedMinX = prevMinX;
652                }
653
654                if (prevMaxX == null || dub < prevMaxX.doubleValue()) {
655                    calculatedMaxX = dub;
656                } else {
657                    calculatedMaxX = prevMaxX;
658                }
659                break;
660            default:
661                throw new UnsupportedOperationException("Domain Origin Boundary Mode not yet supported: " + domainOriginBoundaryMode);
662        }
663    }
664
665    public void updateRangeMinMaxForOriginModel() {
666        switch (rangeOriginBoundaryMode) {
667            case AUTO:
668                double origin = userRangeOrigin.doubleValue();
669                double maxYDelta = distance(calculatedMaxY.doubleValue(), origin);
670                double minYDelta = distance(calculatedMinY.doubleValue(), origin);
671                if (maxYDelta > minYDelta) {
672                    calculatedMinY = origin - maxYDelta;
673                    calculatedMaxY = origin + maxYDelta;
674                } else {
675                    calculatedMinY = origin - minYDelta;
676                    calculatedMaxY = origin + minYDelta;
677                }
678                break;
679            case FIXED:
680            case GROW:
681            case SHRINNK:
682            default:
683                throw new UnsupportedOperationException(
684                        "Range Origin Boundary Mode not yet supported: " + rangeOriginBoundaryMode);
685        }
686    }
687
688    /**
689     * Convenience method - wraps XYGraphWidget.getTicksPerRangeLabel().
690     * Equivalent to getGraphWidget().getTicksPerRangeLabel().
691     *
692     * @return
693     */
694    public int getTicksPerRangeLabel() {
695        return graphWidget.getTicksPerRangeLabel();
696    }
697
698    /**
699     * Convenience method - wraps XYGraphWidget.setTicksPerRangeLabel().
700     * Equivalent to getGraphWidget().setTicksPerRangeLabel().
701     *
702     * @param ticksPerRangeLabel
703     */
704    public void setTicksPerRangeLabel(int ticksPerRangeLabel) {
705        graphWidget.setTicksPerRangeLabel(ticksPerRangeLabel);
706    }
707
708    /**
709     * Convenience method - wraps XYGraphWidget.getTicksPerDomainLabel().
710     * Equivalent to getGraphWidget().getTicksPerDomainLabel().
711     *
712     * @return
713     */
714    public int getTicksPerDomainLabel() {
715        return graphWidget.getTicksPerDomainLabel();
716    }
717
718    /**
719     * Convenience method - wraps XYGraphWidget.setTicksPerDomainLabel().
720     * Equivalent to getGraphWidget().setTicksPerDomainLabel().
721     *
722     * @param ticksPerDomainLabel
723     */
724    public void setTicksPerDomainLabel(int ticksPerDomainLabel) {
725        graphWidget.setTicksPerDomainLabel(ticksPerDomainLabel);
726    }
727
728    public XYStepMode getDomainStepMode() {
729        return domainStepMode;
730    }
731
732    public void setDomainStepMode(XYStepMode domainStepMode) {
733        this.domainStepMode = domainStepMode;
734    }
735
736    public double getDomainStepValue() {
737        return domainStepValue;
738    }
739
740    public void setDomainStepValue(double domainStepValue) {
741        this.domainStepValue = domainStepValue;
742    }
743
744    public void setDomainStep(XYStepMode mode, double value) {
745        setDomainStepMode(mode);
746        setDomainStepValue(value);
747    }
748
749    public XYStepMode getRangeStepMode() {
750        return rangeStepMode;
751    }
752
753    public void setRangeStepMode(XYStepMode rangeStepMode) {
754        this.rangeStepMode = rangeStepMode;
755    }
756
757    public double getRangeStepValue() {
758        return rangeStepValue;
759    }
760
761    public void setRangeStepValue(double rangeStepValue) {
762        this.rangeStepValue = rangeStepValue;
763    }
764
765    public void setRangeStep(XYStepMode mode, double value) {
766        setRangeStepMode(mode);
767        setRangeStepValue(value);
768    }
769
770    public String getDomainLabel() {
771        return getDomainLabelWidget().getText();
772    }
773
774    public void setDomainLabel(String domainLabel) {
775        getDomainLabelWidget().setText(domainLabel);
776    }
777
778    public String getRangeLabel() {
779        return getRangeLabelWidget().getText();
780    }
781
782    public void setRangeLabel(String rangeLabel) {
783        getRangeLabelWidget().setText(rangeLabel);
784    }
785
786    public XYLegendWidget getLegendWidget() {
787        return legendWidget;
788    }
789
790    public void setLegendWidget(XYLegendWidget legendWidget) {
791        this.legendWidget = legendWidget;
792    }
793
794    public XYGraphWidget getGraphWidget() {
795        return graphWidget;
796    }
797
798    public void setGraphWidget(XYGraphWidget graphWidget) {
799        this.graphWidget = graphWidget;
800    }
801
802    public TextLabelWidget getDomainLabelWidget() {
803        return domainLabelWidget;
804    }
805
806    public void setDomainLabelWidget(TextLabelWidget domainLabelWidget) {
807        this.domainLabelWidget = domainLabelWidget;
808    }
809
810    public TextLabelWidget getRangeLabelWidget() {
811        return rangeLabelWidget;
812    }
813
814    public void setRangeLabelWidget(TextLabelWidget rangeLabelWidget) {
815        this.rangeLabelWidget = rangeLabelWidget;
816    }
817
818    /**
819     * Convenience method - wraps XYGraphWidget.getRangeValueFormat().
820     *
821     * @return
822     */
823    public Format getRangeValueFormat() {
824        return graphWidget.getRangeValueFormat();
825    }
826
827    /**
828     * Convenience method - wraps XYGraphWidget.setRangeValueFormat().
829     *
830     * @param rangeValueFormat
831     */
832    public void setRangeValueFormat(Format rangeValueFormat) {
833        graphWidget.setRangeValueFormat(rangeValueFormat);
834    }
835
836    /**
837     * Convenience method - wraps XYGraphWidget.getDomainValueFormat().
838     *
839     * @return
840     */
841    public Format getDomainValueFormat() {
842        return graphWidget.getDomainValueFormat();
843    }
844
845    /**
846     * Convenience method - wraps XYGraphWidget.setDomainValueFormat().
847     *
848     * @param domainValueFormat
849     */
850    public void setDomainValueFormat(Format domainValueFormat) {
851        graphWidget.setDomainValueFormat(domainValueFormat);
852    }
853
854    /**
855     * Setup the boundary mode, boundary values only applicable in FIXED mode.
856     *
857     * @param lowerBoundary
858     * @param upperBoundary
859     * @param mode
860     */
861    public synchronized void setDomainBoundaries(Number lowerBoundary, Number upperBoundary, BoundaryMode mode) {
862        setDomainBoundaries(lowerBoundary, mode, upperBoundary, mode);
863    }
864
865    /**
866     * Setup the boundary mode, boundary values only applicable in FIXED mode.
867     *
868     * @param lowerBoundary
869     * @param lowerBoundaryMode
870     * @param upperBoundary
871     * @param upperBoundaryMode
872     */
873    public synchronized void setDomainBoundaries(Number lowerBoundary, BoundaryMode lowerBoundaryMode,
874                                                 Number upperBoundary, BoundaryMode upperBoundaryMode) {
875        setDomainLowerBoundary(lowerBoundary, lowerBoundaryMode);
876        setDomainUpperBoundary(upperBoundary, upperBoundaryMode);
877    }
878
879    /**
880     * Setup the boundary mode, boundary values only applicable in FIXED mode.
881     *
882     * @param lowerBoundary
883     * @param upperBoundary
884     * @param mode
885     */
886    public synchronized void setRangeBoundaries(Number lowerBoundary, Number upperBoundary, BoundaryMode mode) {
887        setRangeBoundaries(lowerBoundary, mode, upperBoundary, mode);
888    }
889
890    /**
891     * Setup the boundary mode, boundary values only applicable in FIXED mode.
892     *
893     * @param lowerBoundary
894     * @param lowerBoundaryMode
895     * @param upperBoundary
896     * @param upperBoundaryMode
897     */
898    public synchronized void setRangeBoundaries(Number lowerBoundary, BoundaryMode lowerBoundaryMode,
899                                                Number upperBoundary, BoundaryMode upperBoundaryMode) {
900        setRangeLowerBoundary(lowerBoundary, lowerBoundaryMode);
901        setRangeUpperBoundary(upperBoundary, upperBoundaryMode);
902    }
903
904    protected synchronized void setDomainUpperBoundaryMode(BoundaryMode mode) {
905        this.domainUpperBoundaryMode = mode;
906    }
907
908    protected synchronized void setUserMaxX(Number boundary) {
909        // Ifor 12/10/2011
910        // you want null for auto grow and shrink
911        //if(boundary == null) {
912        //    throw new NullPointerException("Boundary value cannot be null.");
913        //}
914        this.userMaxX = boundary;
915    }
916
917    /**
918     * Setup the boundary mode, boundary values only applicable in FIXED mode.
919     *
920     * @param boundary
921     * @param mode
922     */
923    public synchronized void setDomainUpperBoundary(Number boundary, BoundaryMode mode) {
924        setUserMaxX((mode == BoundaryMode.FIXED) ? boundary : null);
925        setDomainUpperBoundaryMode(mode);
926        setDomainFramingModel(XYFramingModel.EDGE);
927    }
928
929    protected synchronized void setDomainLowerBoundaryMode(BoundaryMode mode) {
930        this.domainLowerBoundaryMode = mode;
931    }
932
933    protected synchronized void setUserMinX(Number boundary) {
934        // Ifor 12/10/2011
935        // you want null for auto grow and shrink
936        //if(boundary == null) {
937        //    throw new NullPointerException("Boundary value cannot be null.");
938        //}
939        this.userMinX = boundary;
940    }
941
942    /**
943     * Setup the boundary mode, boundary values only applicable in FIXED mode.
944     *
945     * @param boundary
946     * @param mode
947     */
948    public synchronized void setDomainLowerBoundary(Number boundary, BoundaryMode mode) {
949        setUserMinX((mode == BoundaryMode.FIXED) ? boundary : null);
950        setDomainLowerBoundaryMode(mode);
951        setDomainFramingModel(XYFramingModel.EDGE);
952        //updateMinMaxVals();
953    }
954
955    protected synchronized void setRangeUpperBoundaryMode(BoundaryMode mode) {
956        this.rangeUpperBoundaryMode = mode;
957    }
958
959    protected synchronized void setUserMaxY(Number boundary) {
960        // Ifor 12/10/2011
961        // you want null for auto grow and shrink
962        //if(boundary == null) {
963        //    throw new NullPointerException("Boundary value cannot be null.");
964        //}
965        this.userMaxY = boundary;
966    }
967
968    /**
969     * Setup the boundary mode, boundary values only applicable in FIXED mode.
970     *
971     * @param boundary
972     * @param mode
973     */
974    public synchronized void setRangeUpperBoundary(Number boundary, BoundaryMode mode) {
975        setUserMaxY((mode == BoundaryMode.FIXED) ? boundary : null);
976        setRangeUpperBoundaryMode(mode);
977        setRangeFramingModel(XYFramingModel.EDGE);
978    }
979
980    protected synchronized void setRangeLowerBoundaryMode(BoundaryMode mode) {
981        this.rangeLowerBoundaryMode = mode;
982    }
983
984    protected synchronized void setUserMinY(Number boundary) {
985        // Ifor 12/10/2011
986        // you want null for auto grow and shrink
987        //if(boundary == null) {
988        //    throw new NullPointerException("Boundary value cannot be null.");
989        //}
990        this.userMinY = boundary;
991    }
992
993    /**
994     * Setup the boundary mode, boundary values only applicable in FIXED mode.
995     *
996     * @param boundary
997     * @param mode
998     */
999    public synchronized void setRangeLowerBoundary(Number boundary, BoundaryMode mode) {
1000        setUserMinY((mode == BoundaryMode.FIXED) ? boundary : null);
1001        setRangeLowerBoundaryMode(mode);
1002        setRangeFramingModel(XYFramingModel.EDGE);
1003    }
1004
1005    private Number getUserMinX() {
1006        return userMinX;
1007    }
1008
1009    private Number getUserMaxX() {
1010        return userMaxX;
1011    }
1012
1013    private Number getUserMinY() {
1014        return userMinY;
1015    }
1016
1017    private Number getUserMaxY() {
1018        return userMaxY;
1019    }
1020
1021    public Number getDomainOrigin() {
1022        return calculatedDomainOrigin;
1023    }
1024
1025    public Number getRangeOrigin() {
1026        return calculatedRangeOrigin;
1027    }
1028
1029    protected BoundaryMode getDomainUpperBoundaryMode() {
1030        return domainUpperBoundaryMode;
1031    }
1032
1033    protected BoundaryMode getDomainLowerBoundaryMode() {
1034        return domainLowerBoundaryMode;
1035    }
1036
1037    protected BoundaryMode getRangeUpperBoundaryMode() {
1038        return rangeUpperBoundaryMode;
1039    }
1040
1041    protected BoundaryMode getRangeLowerBoundaryMode() {
1042        return rangeLowerBoundaryMode;
1043    }
1044
1045    public synchronized void setUserDomainOrigin(Number origin) {
1046        if (origin == null) {
1047            throw new NullPointerException("Origin value cannot be null.");
1048        }
1049        this.userDomainOrigin = origin;
1050    }
1051
1052    public synchronized void setUserRangeOrigin(Number origin) {
1053        if (origin == null) {
1054            throw new NullPointerException("Origin value cannot be null.");
1055        }
1056        this.userRangeOrigin = origin;
1057    }
1058
1059    public XYFramingModel getDomainFramingModel() {
1060        return domainFramingModel;
1061    }
1062
1063    @SuppressWarnings("SameParameterValue")
1064    protected void setDomainFramingModel(XYFramingModel domainFramingModel) {
1065        this.domainFramingModel = domainFramingModel;
1066    }
1067
1068    public XYFramingModel getRangeFramingModel() {
1069
1070        return rangeFramingModel;
1071    }
1072
1073    @SuppressWarnings("SameParameterValue")
1074    protected void setRangeFramingModel(XYFramingModel rangeFramingModel) {
1075        this.rangeFramingModel = rangeFramingModel;
1076    }
1077
1078    /**
1079     * CalculatedMinX value after the the framing model has been applied.
1080     *
1081     * @return
1082     */
1083    public Number getCalculatedMinX() {
1084        return calculatedMinX != null ? calculatedMinX : getDefaultBounds().getMinX();
1085    }
1086
1087    /**
1088     * CalculatedMaxX value after the the framing model has been applied.
1089     *
1090     * @return
1091     */
1092    public Number getCalculatedMaxX() {
1093        return calculatedMaxX != null ? calculatedMaxX : getDefaultBounds().getMaxX();
1094    }
1095
1096    /**
1097     * CalculatedMinY value after the the framing model has been applied.
1098     *
1099     * @return
1100     */
1101    public Number getCalculatedMinY() {
1102        return calculatedMinY != null ? calculatedMinY : getDefaultBounds().getMinY();
1103    }
1104
1105    /**
1106     * CalculatedMaxY value after the the framing model has been applied.
1107     *
1108     * @return
1109     */
1110    public Number getCalculatedMaxY() {
1111        return calculatedMaxY != null ? calculatedMaxY : getDefaultBounds().getMaxY();
1112    }
1113
1114    public boolean isDrawDomainOriginEnabled() {
1115        return drawDomainOriginEnabled;
1116    }
1117
1118    public void setDrawDomainOriginEnabled(boolean drawDomainOriginEnabled) {
1119        this.drawDomainOriginEnabled = drawDomainOriginEnabled;
1120    }
1121
1122    public boolean isDrawRangeOriginEnabled() {
1123        return drawRangeOriginEnabled;
1124    }
1125
1126    public void setDrawRangeOriginEnabled(boolean drawRangeOriginEnabled) {
1127        this.drawRangeOriginEnabled = drawRangeOriginEnabled;
1128    }
1129
1130    /**
1131     * Appends the specified marker to the end of plot's yValueMarkers list.
1132     *
1133     * @param marker The YValueMarker to be added.
1134     * @return true if the object was successfully added, false otherwise.
1135     */
1136    public boolean addMarker(YValueMarker marker) {
1137        if (yValueMarkers.contains(marker)) {
1138            return false;
1139        } else {
1140            return yValueMarkers.add(marker);
1141        }
1142    }
1143
1144    /**
1145     * Removes the specified marker from the plot.
1146     *
1147     * @param marker
1148     * @return The YValueMarker removed if successfull,  null otherwise.
1149     */
1150    public YValueMarker removeMarker(YValueMarker marker) {
1151        int markerIndex = yValueMarkers.indexOf(marker);
1152        if (markerIndex == -1) {
1153            return null;
1154        } else {
1155            return yValueMarkers.remove(markerIndex);
1156        }
1157    }
1158
1159    /**
1160     * Convenience method - combines removeYMarkers() and removeXMarkers().
1161     *
1162     * @return
1163     */
1164    public int removeMarkers() {
1165        int removed = removeXMarkers();
1166        removed += removeYMarkers();
1167        return removed;
1168    }
1169
1170    /**
1171     * Removes all YValueMarker instances from the plot.
1172     *
1173     * @return
1174     */
1175    public int removeYMarkers() {
1176        int numMarkersRemoved = yValueMarkers.size();
1177        yValueMarkers.clear();
1178        return numMarkersRemoved;
1179    }
1180
1181    /**
1182     * Appends the specified marker to the end of plot's xValueMarkers list.
1183     *
1184     * @param marker The XValueMarker to be added.
1185     * @return true if the object was successfully added, false otherwise.
1186     */
1187    public boolean addMarker(XValueMarker marker) {
1188        return !xValueMarkers.contains(marker) && xValueMarkers.add(marker);
1189    }
1190
1191    /**
1192     * Removes the specified marker from the plot.
1193     *
1194     * @param marker
1195     * @return The XValueMarker removed if successfull,  null otherwise.
1196     */
1197    public XValueMarker removeMarker(XValueMarker marker) {
1198        int markerIndex = xValueMarkers.indexOf(marker);
1199        if (markerIndex == -1) {
1200            return null;
1201        } else {
1202            return xValueMarkers.remove(markerIndex);
1203        }
1204    }
1205
1206    /**
1207     * Removes all XValueMarker instances from the plot.
1208     *
1209     * @return
1210     */
1211    public int removeXMarkers() {
1212        int numMarkersRemoved = xValueMarkers.size();
1213        xValueMarkers.clear();
1214        return numMarkersRemoved;
1215    }
1216
1217    protected List<YValueMarker> getYValueMarkers() {
1218        return yValueMarkers;
1219    }
1220
1221    protected List<XValueMarker> getXValueMarkers() {
1222        return xValueMarkers;
1223    }
1224
1225    public RectRegion getDefaultBounds() {
1226        return defaultBounds;
1227    }
1228
1229    public void setDefaultBounds(RectRegion defaultBounds) {
1230        this.defaultBounds = defaultBounds;
1231    }
1232
1233    /**
1234     * @return the rangeTopMin
1235     */
1236    public Number getRangeTopMin() {
1237        return rangeTopMin;
1238    }
1239
1240    /**
1241     * @param rangeTopMin the rangeTopMin to set
1242     */
1243    public synchronized void setRangeTopMin(Number rangeTopMin) {
1244        this.rangeTopMin = rangeTopMin;
1245    }
1246
1247    /**
1248     * @return the rangeTopMax
1249     */
1250    public Number getRangeTopMax() {
1251        return rangeTopMax;
1252    }
1253
1254    /**
1255     * @param rangeTopMax the rangeTopMax to set
1256     */
1257    public synchronized void setRangeTopMax(Number rangeTopMax) {
1258        this.rangeTopMax = rangeTopMax;
1259    }
1260
1261    /**
1262     * @return the rangeBottomMin
1263     */
1264    public Number getRangeBottomMin() {
1265        return rangeBottomMin;
1266    }
1267
1268    /**
1269     * @param rangeBottomMin the rangeBottomMin to set
1270     */
1271    public synchronized void setRangeBottomMin(Number rangeBottomMin) {
1272        this.rangeBottomMin = rangeBottomMin;
1273    }
1274
1275    /**
1276     * @return the rangeBottomMax
1277     */
1278    public Number getRangeBottomMax() {
1279        return rangeBottomMax;
1280    }
1281
1282    /**
1283     * @param rangeBottomMax the rangeBottomMax to set
1284     */
1285    public synchronized void setRangeBottomMax(Number rangeBottomMax) {
1286        this.rangeBottomMax = rangeBottomMax;
1287    }
1288
1289    /**
1290     * @return the domainLeftMin
1291     */
1292    public Number getDomainLeftMin() {
1293        return domainLeftMin;
1294    }
1295
1296    /**
1297     * @param domainLeftMin the domainLeftMin to set
1298     */
1299    public synchronized void setDomainLeftMin(Number domainLeftMin) {
1300        this.domainLeftMin = domainLeftMin;
1301    }
1302
1303    /**
1304     * @return the domainLeftMax
1305     */
1306    public Number getDomainLeftMax() {
1307        return domainLeftMax;
1308    }
1309
1310    /**
1311     * @param domainLeftMax the domainLeftMax to set
1312     */
1313    public synchronized void setDomainLeftMax(Number domainLeftMax) {
1314        this.domainLeftMax = domainLeftMax;
1315    }
1316
1317    /**
1318     * @return the domainRightMin
1319     */
1320    public Number getDomainRightMin() {
1321        return domainRightMin;
1322    }
1323
1324    /**
1325     * @param domainRightMin the domainRightMin to set
1326     */
1327    public synchronized void setDomainRightMin(Number domainRightMin) {
1328        this.domainRightMin = domainRightMin;
1329    }
1330
1331    /**
1332     * @return the domainRightMax
1333     */
1334    public Number getDomainRightMax() {
1335        return domainRightMax;
1336    }
1337
1338    /**
1339     * @param domainRightMax the domainRightMax to set
1340     */
1341    public synchronized void setDomainRightMax(Number domainRightMax) {
1342        this.domainRightMax = domainRightMax;
1343    }
1344}