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.graphics.*;
20
21import com.androidplot.exception.PlotRenderException;
22import com.androidplot.ui.LayoutManager;
23import com.androidplot.ui.SizeMetrics;
24import com.androidplot.ui.widget.Widget;
25import com.androidplot.util.FontUtils;
26import com.androidplot.util.ValPixConverter;
27import com.androidplot.util.ZHash;
28import com.androidplot.util.ZIndexable;
29
30import java.text.DecimalFormat;
31import java.text.Format;
32
33/**
34 * Displays graphical data annotated with domain and range tick markers.
35 */
36public class XYGraphWidget extends Widget {
37
38    public float getRangeLabelOrientation() {
39        return rangeLabelOrientation;
40    }
41
42    public void setRangeLabelOrientation(float rangeLabelOrientation) {
43        this.rangeLabelOrientation = rangeLabelOrientation;
44    }
45
46    public float getDomainLabelOrientation() {
47        return domainLabelOrientation;
48    }
49
50    public void setDomainLabelOrientation(float domainLabelOrientation) {
51        this.domainLabelOrientation = domainLabelOrientation;
52    }
53
54    /**
55     * Will be used in a future version.
56     */
57    public enum XYPlotOrientation {
58        HORIZONTAL, VERTICAL
59    }
60
61    private static final int MARKER_LABEL_SPACING = 2;
62    private static final int CURSOR_LABEL_SPACING = 2; // space between cursor
63    private static final String TAG = "AndroidPlot";
64                                                       // lines and label in
65                                                       // pixels
66    private float domainLabelWidth = 15; // how many pixels is the area
67                                         // allocated for domain labels
68    private float rangeLabelWidth = 41; // ...
69    private float domainLabelVerticalOffset = -5;
70    private float domainLabelHorizontalOffset = 0.0f;
71    private float rangeLabelHorizontalOffset = 1.0f;   // allows tweaking of text position
72    private float rangeLabelVerticalOffset = 0.0f;  // allows tweaking of text position
73
74    private int ticksPerRangeLabel = 1;
75    private int ticksPerDomainLabel = 1;
76    private float gridPaddingTop = 0;
77    private float gridPaddingBottom = 0;
78    private float gridPaddingLeft = 0;
79    private float gridPaddingRight = 0;
80    private int domainLabelTickExtension = 5;
81    private int rangeLabelTickExtension = 5;
82    private Paint gridBackgroundPaint;
83    private Paint rangeGridLinePaint;
84    private Paint rangeSubGridLinePaint;
85    private Paint domainGridLinePaint;
86    private Paint domainSubGridLinePaint;
87    private Paint domainLabelPaint;
88    private Paint rangeLabelPaint;
89    private Paint domainCursorPaint;
90    private Paint rangeCursorPaint;
91    private Paint cursorLabelPaint;
92    private Paint cursorLabelBackgroundPaint;
93    private XYPlot plot;
94    private Format rangeValueFormat;
95    private Format domainValueFormat;
96    private Paint domainOriginLinePaint;
97    private Paint rangeOriginLinePaint;
98    private Paint domainOriginLabelPaint;
99    private Paint rangeOriginLabelPaint;
100    private RectF gridRect;
101    private RectF paddedGridRect;
102    private float domainCursorPosition;
103    private float rangeCursorPosition;
104    @SuppressWarnings("FieldCanBeLocal")
105    private boolean drawCursorLabelEnabled = true;
106    private boolean drawMarkersEnabled = true;
107
108    private boolean rangeAxisLeft = true;
109    private boolean domainAxisBottom = true;
110
111    private float rangeLabelOrientation;
112    private float domainLabelOrientation;
113
114    // TODO: consider typing this manager with a special
115    // axisLabelRegionFormatter
116    // private ZHash<LineRegion, AxisValueLabelFormatter> domainLabelRegions;
117    // private ZHash<LineRegion, AxisValueLabelFormatter> rangeLabelRegions;
118    private ZHash<RectRegion, AxisValueLabelFormatter> axisValueLabelRegions;
119
120    {
121        gridBackgroundPaint = new Paint();
122        gridBackgroundPaint.setColor(Color.rgb(140, 140, 140));
123        gridBackgroundPaint.setStyle(Paint.Style.FILL);
124        rangeGridLinePaint = new Paint();
125        rangeGridLinePaint.setColor(Color.rgb(180, 180, 180));
126        rangeGridLinePaint.setAntiAlias(true);
127        rangeGridLinePaint.setStyle(Paint.Style.STROKE);
128        domainGridLinePaint = new Paint(rangeGridLinePaint);
129        domainSubGridLinePaint = new Paint(domainGridLinePaint);
130        rangeSubGridLinePaint = new Paint(rangeGridLinePaint);
131        domainOriginLinePaint = new Paint();
132        domainOriginLinePaint.setColor(Color.WHITE);
133        domainOriginLinePaint.setAntiAlias(true);
134        rangeOriginLinePaint = new Paint();
135        rangeOriginLinePaint.setColor(Color.WHITE);
136        rangeOriginLinePaint.setAntiAlias(true);
137        domainOriginLabelPaint = new Paint();
138        domainOriginLabelPaint.setColor(Color.WHITE);
139        domainOriginLabelPaint.setAntiAlias(true);
140        domainOriginLabelPaint.setTextAlign(Paint.Align.CENTER);
141        rangeOriginLabelPaint = new Paint();
142        rangeOriginLabelPaint.setColor(Color.WHITE);
143        rangeOriginLabelPaint.setAntiAlias(true);
144        rangeOriginLabelPaint.setTextAlign(Paint.Align.RIGHT);
145        domainLabelPaint = new Paint();
146        domainLabelPaint.setColor(Color.LTGRAY);
147        domainLabelPaint.setAntiAlias(true);
148        domainLabelPaint.setTextAlign(Paint.Align.CENTER);
149        rangeLabelPaint = new Paint();
150        rangeLabelPaint.setColor(Color.LTGRAY);
151        rangeLabelPaint.setAntiAlias(true);
152        rangeLabelPaint.setTextAlign(Paint.Align.RIGHT);
153        domainCursorPaint = new Paint();
154        domainCursorPaint.setColor(Color.YELLOW);
155        rangeCursorPaint = new Paint();
156        rangeCursorPaint.setColor(Color.YELLOW);
157        cursorLabelPaint = new Paint();
158        cursorLabelPaint.setColor(Color.YELLOW);
159        cursorLabelBackgroundPaint = new Paint();
160        cursorLabelBackgroundPaint.setColor(Color.argb(100, 50, 50, 50));
161        setMarginTop(7);
162        setMarginRight(4);
163        setMarginBottom(4);
164        rangeValueFormat = new DecimalFormat("0.0");
165        domainValueFormat = new DecimalFormat("0.0");
166        // domainLabelRegions = new ZHash<LineRegion,
167        // AxisValueLabelFormatter>();
168        // rangeLabelRegions = new ZHash<LineRegion, AxisValueLabelFormatter>();
169        axisValueLabelRegions = new ZHash<RectRegion, AxisValueLabelFormatter>();
170    }
171
172    public XYGraphWidget(LayoutManager layoutManager, XYPlot plot, SizeMetrics sizeMetrics) {
173        super(layoutManager, sizeMetrics);
174        this.plot = plot;
175    }
176
177    public ZIndexable<RectRegion> getAxisValueLabelRegions() {
178        return axisValueLabelRegions;
179    }
180
181    /**
182     * Add a new Region used for rendering axis valuelabels. Note that it is
183     * possible to add multiple Region instances which overlap, in which cast
184     * the last region to be added will be used. It is up to the developer to
185     * guard against this often undesireable situation.
186     *
187     * @param region
188     * @param formatter
189     */
190    public void addAxisValueLabelRegion(RectRegion region,
191            AxisValueLabelFormatter formatter) {
192        axisValueLabelRegions.addToTop(region, formatter);
193    }
194
195    /**
196     * Convenience method - wraps addAxisValueLabelRegion, using
197     * Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY to mask off range
198     * axis value labels.
199     *
200     * @param min
201     * @param max
202     * @param formatter
203     *
204     */
205    public void addDomainAxisValueLabelRegion(double min, double max,
206            AxisValueLabelFormatter formatter) {
207        addAxisValueLabelRegion(new RectRegion(min, max,
208                Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, null),
209                formatter);
210    }
211
212    /**
213     * Convenience method - wraps addAxisValueLabelRegion, using
214     * Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY to mask off domain
215     * axis value labels.
216     *
217     * @param min
218     * @param max
219     * @param formatter
220     */
221    public void addRangeAxisValueLabelRegion(double min, double max,
222            AxisValueLabelFormatter formatter) {
223        addAxisValueLabelRegion(new RectRegion(Double.POSITIVE_INFINITY,
224                Double.NEGATIVE_INFINITY, min, max, null), formatter);
225    }
226
227    /*
228     * public void addRangeLabelRegion(LineRegion region,
229     * AxisValueLabelFormatter formatter) { rangeLabelRegions.addToTop(region,
230     * formatter); }
231     *
232     * public boolean removeRangeLabelRegion(LineRegion region) { return
233     * rangeLabelRegions.remove(region); }
234     */
235
236    /**
237     * Returns the formatter associated with the first (bottom) Region
238     * containing x and y.
239     *
240     * @param x
241     * @param y
242     * @return the formatter associated with the first (bottom) region
243     *         containing x and y. null otherwise.
244     */
245    public AxisValueLabelFormatter getAxisValueLabelFormatterForVal(double x,
246            double y) {
247        for (RectRegion r : axisValueLabelRegions.elements()) {
248            if (r.containsValue(x, y)) {
249                return axisValueLabelRegions.get(r);
250            }
251        }
252        return null;
253    }
254
255    public AxisValueLabelFormatter getAxisValueLabelFormatterForDomainVal(
256            double val) {
257        for (RectRegion r : axisValueLabelRegions.elements()) {
258            if (r.containsDomainValue(val)) {
259                return axisValueLabelRegions.get(r);
260            }
261        }
262        return null;
263    }
264
265    public AxisValueLabelFormatter getAxisValueLabelFormatterForRangeVal(
266            double val) {
267        for (RectRegion r : axisValueLabelRegions.elements()) {
268            if (r.containsRangeValue(val)) {
269                return axisValueLabelRegions.get(r);
270            }
271        }
272        return null;
273    }
274
275    /**
276     * Returns the formatter associated with the first (bottom-most) Region
277     * containing value.
278     *
279     * @param value
280     * @return
281     */
282    /*
283     * public AxisValueLabelFormatter getXYAxisFormatterForRangeVal(double
284     * value) { return getRegionContainingVal(rangeLabelRegions, value); }
285     *//**
286     * Returns the formatter associated with the first (bottom-most) Region
287     * containing value.
288     *
289     * @param value
290     * @return
291     */
292    /*
293     * public AxisValueLabelFormatter getXYAxisFormatterForDomainVal(double
294     * value) { return getRegionContainingVal(domainLabelRegions, value); }
295     */
296
297    /*
298     * private AxisValueLabelFormatter getRegionContainingVal(ZHash<LineRegion,
299     * AxisValueLabelFormatter> zhash, double val) { for (LineRegion r :
300     * zhash.elements()) { if (r.contains(val)) { return
301     * rangeLabelRegions.get(r); } } // nothing found return null; }
302     */
303
304    /**
305     * Returns a RectF representing the grid area last drawn by this plot.
306     *
307     * @return
308     */
309    public RectF getGridRect() {
310        return paddedGridRect;
311    }
312    private String getFormattedRangeValue(Number value) {
313        return rangeValueFormat.format(value);
314    }
315
316    private String getFormattedDomainValue(Number value) {
317        return domainValueFormat.format(value);
318    }
319
320    /**
321     * Convenience method. Wraps getYVal(float)
322     *
323     * @param point
324     * @return
325     */
326    public Double getYVal(PointF point) {
327        return getYVal(point.y);
328    }
329
330    /**
331     * Converts a y pixel to a y value.
332     *
333     * @param yPix
334     * @return
335     */
336    public Double getYVal(float yPix) {
337        if (plot.getCalculatedMinY() == null
338                || plot.getCalculatedMaxY() == null) {
339            return null;
340        }
341        return ValPixConverter.pixToVal(yPix - paddedGridRect.top, plot
342                .getCalculatedMinY().doubleValue(), plot.getCalculatedMaxY()
343                .doubleValue(), paddedGridRect.height(), true);
344    }
345
346    /**
347     * Convenience method. Wraps getXVal(float)
348     *
349     * @param point
350     * @return
351     */
352    public Double getXVal(PointF point) {
353        return getXVal(point.x);
354    }
355
356    /**
357     * Converts an x pixel into an x value.
358     *
359     * @param xPix
360     * @return
361     */
362    public Double getXVal(float xPix) {
363        if (plot.getCalculatedMinX() == null
364                || plot.getCalculatedMaxX() == null) {
365            return null;
366        }
367        return ValPixConverter.pixToVal(xPix - paddedGridRect.left, plot
368                .getCalculatedMinX().doubleValue(), plot.getCalculatedMaxX()
369                .doubleValue(), paddedGridRect.width(), false);
370    }
371
372    @Override
373    protected void doOnDraw(Canvas canvas, RectF widgetRect)
374            throws PlotRenderException {
375        gridRect = getGridRect(widgetRect); // used for drawing the background
376                                            // of the grid
377        paddedGridRect = getPaddedGridRect(gridRect); // used for drawing lines
378                                                      // etc.
379        //Log.v(TAG, "gridRect :" + gridRect);
380        //Log.v(TAG, "paddedGridRect :" + paddedGridRect);
381        // if (!plot.isEmpty()) {
382        // don't draw if we have no space to draw into
383        if ((paddedGridRect.height() > 0.0f) && (paddedGridRect.width() > 0.0f)) {
384            if (plot.getCalculatedMinX() != null
385                    && plot.getCalculatedMaxX() != null
386                    && plot.getCalculatedMinY() != null
387                    && plot.getCalculatedMaxY() != null) {
388                drawGrid(canvas);
389                drawData(canvas);
390                drawCursors(canvas);
391                if (isDrawMarkersEnabled()) {
392                    drawMarkers(canvas);
393                }
394            }
395        }
396        // }
397    }
398
399    private RectF getGridRect(RectF widgetRect) {
400        return new RectF(widgetRect.left + ((rangeAxisLeft)?rangeLabelWidth:1),
401                widgetRect.top + ((domainAxisBottom)?1:domainLabelWidth),
402                widgetRect.right - ((rangeAxisLeft)?1:rangeLabelWidth),
403                widgetRect.bottom - ((domainAxisBottom)?domainLabelWidth:1));
404    }
405
406    private RectF getPaddedGridRect(RectF gridRect) {
407        return new RectF(gridRect.left + gridPaddingLeft, gridRect.top
408                + gridPaddingTop, gridRect.right - gridPaddingRight,
409                gridRect.bottom - gridPaddingBottom);
410    }
411
412    private void drawTickText(Canvas canvas, XYAxisType axis, Number value,
413            float xPix, float yPix, Paint labelPaint) {
414        AxisValueLabelFormatter rf = null;
415        String txt = null;
416        double v = value.doubleValue();
417
418        int canvasState = canvas.save();
419        try {
420            switch (axis) {
421                case DOMAIN:
422                    rf = getAxisValueLabelFormatterForDomainVal(v);
423                    txt = getFormattedDomainValue(value);
424                    canvas.rotate(getDomainLabelOrientation(), xPix, yPix);
425                    break;
426                case RANGE:
427                    rf = getAxisValueLabelFormatterForRangeVal(v);
428                    txt = getFormattedRangeValue(value);
429                    canvas.rotate(getRangeLabelOrientation(), xPix, yPix);
430                    break;
431            }
432
433            // if a matching region formatter was found, create a clone
434            // of labelPaint and use the formatter's color. Otherwise
435            // just use labelPaint:
436            Paint p;
437            if (rf != null) {
438                // p = rf.getPaint();
439                p = new Paint(labelPaint);
440                p.setColor(rf.getColor());
441                // p.setColor(Color.RED);
442            } else {
443                p = labelPaint;
444            }
445            canvas.drawText(txt, xPix, yPix, p);
446        } finally {
447            canvas.restoreToCount(canvasState);
448        }
449    }
450
451    private void drawDomainTick(Canvas canvas, float xPix, Number xVal,
452            Paint labelPaint, Paint linePaint, boolean drawLineOnly) {
453        if (!drawLineOnly) {
454            if (linePaint != null) {
455                if (domainAxisBottom){
456                canvas.drawLine(xPix, gridRect.top, xPix, gridRect.bottom
457                        + domainLabelTickExtension, linePaint);
458                } else {
459                    canvas.drawLine(xPix, gridRect.top - domainLabelTickExtension, xPix,
460                            gridRect.bottom , linePaint);
461                }
462            }
463            if (labelPaint != null) {
464                float fontHeight = FontUtils.getFontHeight(labelPaint);
465                float yPix;
466                if (domainAxisBottom){
467                    yPix = gridRect.bottom + domainLabelTickExtension
468                            + domainLabelVerticalOffset + fontHeight;
469                } else {
470                    yPix = gridRect.top - domainLabelTickExtension
471                            - domainLabelVerticalOffset;
472                }
473                drawTickText(canvas, XYAxisType.DOMAIN, xVal, xPix + domainLabelHorizontalOffset, yPix,
474                        labelPaint);
475            }
476        } else if (linePaint != null) {
477
478            canvas.drawLine(xPix, gridRect.top, xPix, gridRect.bottom,
479                    linePaint);
480
481        }
482    }
483
484    public void drawRangeTick(Canvas canvas, float yPix, Number yVal,
485            Paint labelPaint, Paint linePaint, boolean drawLineOnly) {
486        if (!drawLineOnly) {
487            if (linePaint != null) {
488                if (rangeAxisLeft){
489                canvas.drawLine(gridRect.left - rangeLabelTickExtension, yPix,
490                        gridRect.right, yPix, linePaint);
491                } else {
492                    canvas.drawLine(gridRect.left, yPix,
493                            gridRect.right + rangeLabelTickExtension, yPix, linePaint);
494                }
495            }
496            if (labelPaint != null) {
497                float xPix;
498                if (rangeAxisLeft){
499                    xPix = gridRect.left
500                            - (rangeLabelTickExtension + rangeLabelHorizontalOffset);
501                } else {
502                    xPix = gridRect.right
503                            + (rangeLabelTickExtension + rangeLabelHorizontalOffset);
504                }
505                drawTickText(canvas, XYAxisType.RANGE, yVal, xPix, yPix - rangeLabelVerticalOffset,
506                        labelPaint);
507            }
508        } else if (linePaint != null) {
509            canvas.drawLine(gridRect.left, yPix, gridRect.right, yPix,
510                    linePaint);
511        }
512    }
513
514    /**
515     * Draws the drid and domain/range labels for the plot.
516     *
517     * @param canvas
518     */
519    protected void drawGrid(Canvas canvas) {
520
521        if (gridBackgroundPaint != null) {
522            canvas.drawRect(gridRect, gridBackgroundPaint);
523        }
524
525        float domainOriginF;
526        if (plot.getDomainOrigin() != null) {
527            double domainOriginVal = plot.getDomainOrigin().doubleValue();
528            domainOriginF = ValPixConverter.valToPix(domainOriginVal, plot
529                    .getCalculatedMinX().doubleValue(), plot
530                    .getCalculatedMaxX().doubleValue(), paddedGridRect.width(),
531                    false);
532            domainOriginF += paddedGridRect.left;
533            // if no origin is set, use the leftmost value visible on the grid:
534        } else {
535            domainOriginF = paddedGridRect.left;
536        }
537
538        XYStep domainStep = XYStepCalculator.getStep(plot, XYAxisType.DOMAIN,
539                paddedGridRect, plot.getCalculatedMinX().doubleValue(), plot
540                        .getCalculatedMaxX().doubleValue());
541
542        // draw domain origin:
543        if (domainOriginF >= paddedGridRect.left
544                && domainOriginF <= paddedGridRect.right) {
545            if (domainOriginLinePaint != null){
546                domainOriginLinePaint.setTextAlign(Paint.Align.CENTER);
547            }
548            drawDomainTick(canvas, domainOriginF, plot.getDomainOrigin()
549                    .doubleValue(), domainOriginLabelPaint,
550                    domainOriginLinePaint, false);
551        }
552
553        // draw ticks LEFT of origin:
554        {
555            int i = 1;
556            double xVal;
557            float xPix = domainOriginF - domainStep.getStepPix();
558            for (; xPix >= paddedGridRect.left; xPix = domainOriginF
559                    - (i * domainStep.getStepPix())) {
560                xVal = plot.getDomainOrigin().doubleValue() - i
561                        * domainStep.getStepVal();
562                if (xPix >= paddedGridRect.left && xPix <= paddedGridRect.right) {
563                    if (i % getTicksPerDomainLabel() == 0) {
564                        drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
565                                domainGridLinePaint, false);
566                    } else {
567                        drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
568                                domainSubGridLinePaint, true);
569                    }
570                }
571                i++;
572            }
573        }
574
575        // draw ticks RIGHT of origin:
576        {
577            int i = 1;
578            double xVal;
579            float xPix = domainOriginF + domainStep.getStepPix();
580            for (; xPix <= paddedGridRect.right; xPix = domainOriginF
581                    + (i * domainStep.getStepPix())) {
582                xVal = plot.getDomainOrigin().doubleValue() + i
583                        * domainStep.getStepVal();
584                if (xPix >= paddedGridRect.left && xPix <= paddedGridRect.right) {
585
586                    if (i % getTicksPerDomainLabel() == 0) {
587                        drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
588                                domainGridLinePaint, false);
589                    } else {
590                        drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
591                                domainSubGridLinePaint, true);
592                    }
593                }
594                i++;
595            }
596        }
597
598        // draw range origin:
599
600        float rangeOriginF;
601        if (plot.getRangeOrigin() != null) {
602            // --------- NEW WAY ------
603            double rangeOriginD = plot.getRangeOrigin().doubleValue();
604            rangeOriginF = ValPixConverter.valToPix(rangeOriginD, plot
605                    .getCalculatedMinY().doubleValue(), plot
606                    .getCalculatedMaxY().doubleValue(),
607                    paddedGridRect.height(), true);
608            rangeOriginF += paddedGridRect.top;
609            // if no origin is set, use the leftmost value visible on the grid
610        } else {
611            rangeOriginF = paddedGridRect.bottom;
612        }
613
614        XYStep rangeStep = XYStepCalculator.getStep(plot, XYAxisType.RANGE,
615                paddedGridRect, plot.getCalculatedMinY().doubleValue(), plot
616                        .getCalculatedMaxY().doubleValue());
617
618        // draw range origin:
619        if (rangeOriginF >= paddedGridRect.top
620                && rangeOriginF <= paddedGridRect.bottom) {
621            if (rangeOriginLinePaint != null){
622                rangeOriginLinePaint.setTextAlign(Paint.Align.RIGHT);
623            }
624            drawRangeTick(canvas, rangeOriginF, plot.getRangeOrigin()
625                    .doubleValue(), rangeOriginLabelPaint,
626                    rangeOriginLinePaint, false);
627        }
628        // draw ticks ABOVE origin:
629        {
630            int i = 1;
631            double yVal;
632            float yPix = rangeOriginF - rangeStep.getStepPix();
633            for (; yPix >= paddedGridRect.top; yPix = rangeOriginF
634                    - (i * rangeStep.getStepPix())) {
635                yVal = plot.getRangeOrigin().doubleValue() + i
636                        * rangeStep.getStepVal();
637                if (yPix >= paddedGridRect.top && yPix <= paddedGridRect.bottom) {
638                    if (i % getTicksPerRangeLabel() == 0) {
639                        drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
640                                rangeGridLinePaint, false);
641                    } else {
642                        drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
643                                rangeSubGridLinePaint, true);
644                    }
645                }
646                i++;
647            }
648        }
649
650        // draw ticks BENEATH origin:
651        {
652            int i = 1;
653            double yVal;
654            float yPix = rangeOriginF + rangeStep.getStepPix();
655            for (; yPix <= paddedGridRect.bottom; yPix = rangeOriginF
656                    + (i * rangeStep.getStepPix())) {
657                yVal = plot.getRangeOrigin().doubleValue() - i
658                        * rangeStep.getStepVal();
659                if (yPix >= paddedGridRect.top && yPix <= paddedGridRect.bottom) {
660                    if (i % getTicksPerRangeLabel() == 0) {
661                        drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
662                                rangeGridLinePaint, false);
663                    } else {
664                        drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
665                                rangeSubGridLinePaint, true);
666                    }
667                }
668                i++;
669            }
670        }
671    }
672
673    /**
674     * Renders the text associated with user defined markers
675     *
676     * @param canvas
677     * @param text
678     * @param marker
679     * @param x
680     * @param y
681     */
682    private void drawMarkerText(Canvas canvas, String text, ValueMarker marker,
683            float x, float y) {
684        x += MARKER_LABEL_SPACING;
685        y -= MARKER_LABEL_SPACING;
686        RectF textRect = new RectF(FontUtils.getStringDimensions(text,
687                marker.getTextPaint()));
688        textRect.offsetTo(x, y - textRect.height());
689
690        if (textRect.right > paddedGridRect.right) {
691            textRect.offset(-(textRect.right - paddedGridRect.right), 0);
692        }
693
694        if (textRect.top < paddedGridRect.top) {
695            textRect.offset(0, paddedGridRect.top - textRect.top);
696        }
697
698        canvas.drawText(text, textRect.left, textRect.bottom,
699                marker.getTextPaint());
700
701    }
702
703    protected void drawMarkers(Canvas canvas) {
704        for (YValueMarker marker : plot.getYValueMarkers()) {
705
706            if (marker.getValue() != null) {
707                double yVal = marker.getValue().doubleValue();
708                float yPix = ValPixConverter.valToPix(yVal, plot
709                        .getCalculatedMinY().doubleValue(), plot
710                        .getCalculatedMaxY().doubleValue(), paddedGridRect
711                        .height(), true);
712                yPix += paddedGridRect.top;
713                canvas.drawLine(paddedGridRect.left, yPix,
714                        paddedGridRect.right, yPix, marker.getLinePaint());
715
716                // String text = getFormattedRangeValue(yVal);
717                float xPix = marker.getTextPosition().getPixelValue(
718                        paddedGridRect.width());
719                xPix += paddedGridRect.left;
720
721                if (marker.getText() != null) {
722                    drawMarkerText(canvas, marker.getText(), marker, xPix, yPix);
723                } else {
724                    drawMarkerText(canvas,
725                            getFormattedRangeValue(marker.getValue()), marker,
726                            xPix, yPix);
727                }
728            }
729        }
730
731        for (XValueMarker marker : plot.getXValueMarkers()) {
732            if (marker.getValue() != null) {
733                double xVal = marker.getValue().doubleValue();
734                float xPix = ValPixConverter.valToPix(xVal, plot
735                        .getCalculatedMinX().doubleValue(), plot
736                        .getCalculatedMaxX().doubleValue(), paddedGridRect
737                        .width(), false);
738                xPix += paddedGridRect.left;
739                canvas.drawLine(xPix, paddedGridRect.top, xPix,
740                        paddedGridRect.bottom, marker.getLinePaint());
741
742                // String text = getFormattedDomainValue(xVal);
743                float yPix = marker.getTextPosition().getPixelValue(
744                        paddedGridRect.height());
745                yPix += paddedGridRect.top;
746                if (marker.getText() != null) {
747                    drawMarkerText(canvas, marker.getText(), marker, xPix, yPix);
748                } else {
749                    drawMarkerText(canvas,
750                            getFormattedDomainValue(marker.getValue()), marker,
751                            xPix, yPix);
752                }
753            }
754        }
755    }
756
757    protected void drawCursors(Canvas canvas) {
758        boolean hasDomainCursor = false;
759        // draw the domain cursor:
760        if (domainCursorPaint != null
761                && domainCursorPosition <= paddedGridRect.right
762                && domainCursorPosition >= paddedGridRect.left) {
763            hasDomainCursor = true;
764            canvas.drawLine(domainCursorPosition, paddedGridRect.top,
765                    domainCursorPosition, paddedGridRect.bottom,
766                    domainCursorPaint);
767        }
768
769        boolean hasRangeCursor = false;
770        // draw the range cursor:
771        if (rangeCursorPaint != null
772                && rangeCursorPosition >= paddedGridRect.top
773                && rangeCursorPosition <= paddedGridRect.bottom) {
774            hasRangeCursor = true;
775            canvas.drawLine(paddedGridRect.left, rangeCursorPosition,
776                    paddedGridRect.right, rangeCursorPosition, rangeCursorPaint);
777        }
778
779        if (drawCursorLabelEnabled && cursorLabelPaint != null
780                && hasRangeCursor && hasDomainCursor) {
781
782            String label = "X="
783                    + getDomainValueFormat().format(getDomainCursorVal());
784            label += " Y=" + getRangeValueFormat().format(getRangeCursorVal());
785
786            // convert the label dimensions rect into floating-point:
787            RectF cursorRect = new RectF(FontUtils.getPackedStringDimensions(
788                    label, cursorLabelPaint));
789            cursorRect.offsetTo(domainCursorPosition, rangeCursorPosition
790                    - cursorRect.height());
791
792            // if we are too close to the right edge of the plot, we will move
793            // the
794            // label to the left side of our cursor:
795            if (cursorRect.right >= paddedGridRect.right) {
796                cursorRect.offsetTo(domainCursorPosition - cursorRect.width(),
797                        cursorRect.top);
798            }
799
800            // same thing for the top edge of the plot:
801            // dunno why but these rects can have negative values for top and
802            // bottom.
803            if (cursorRect.top <= paddedGridRect.top) {
804                cursorRect.offsetTo(cursorRect.left, rangeCursorPosition);
805            }
806
807            if (cursorLabelBackgroundPaint != null) {
808                canvas.drawRect(cursorRect, cursorLabelBackgroundPaint);
809            }
810
811            canvas.drawText(label, cursorRect.left, cursorRect.bottom,
812                    cursorLabelPaint);
813        }
814    }
815
816    /**
817     * Draws lines and points for each element in the series.
818     *
819     * @param canvas
820     * @throws PlotRenderException
821     */
822    protected void drawData(Canvas canvas) throws PlotRenderException {
823        // TODO: iterate through a XYSeriesRenderer list
824
825        // int canvasState = canvas.save();
826        try {
827            canvas.save(Canvas.ALL_SAVE_FLAG);
828            canvas.clipRect(gridRect, android.graphics.Region.Op.INTERSECT);
829            for (XYSeriesRenderer renderer : plot.getRendererList()) {
830                renderer.render(canvas, paddedGridRect);
831            }
832            // canvas.restoreToCount(canvasState);
833        } finally {
834            canvas.restore();
835        }
836    }
837
838    protected void drawPoint(Canvas canvas, PointF point, Paint paint) {
839        canvas.drawPoint(point.x, point.y, paint);
840    }
841
842    public float getDomainLabelWidth() {
843        return domainLabelWidth;
844    }
845
846    public void setDomainLabelWidth(float domainLabelWidth) {
847        this.domainLabelWidth = domainLabelWidth;
848    }
849
850    public float getRangeLabelWidth() {
851        return rangeLabelWidth;
852    }
853
854    public void setRangeLabelWidth(float rangeLabelWidth) {
855        this.rangeLabelWidth = rangeLabelWidth;
856    }
857
858    public float getDomainLabelVerticalOffset() {
859        return domainLabelVerticalOffset;
860    }
861
862    public void setDomainLabelVerticalOffset(float domainLabelVerticalOffset) {
863        this.domainLabelVerticalOffset = domainLabelVerticalOffset;
864    }
865
866    public float getDomainLabelHorizontalOffset() {
867        return domainLabelHorizontalOffset;
868    }
869
870    public void setDomainLabelHorizontalOffset(float domainLabelHorizontalOffset) {
871        this.domainLabelHorizontalOffset = domainLabelHorizontalOffset;
872    }
873
874    public float getRangeLabelHorizontalOffset() {
875        return rangeLabelHorizontalOffset;
876    }
877
878    public void setRangeLabelHorizontalOffset(float rangeLabelHorizontalOffset) {
879        this.rangeLabelHorizontalOffset = rangeLabelHorizontalOffset;
880    }
881
882    public float getRangeLabelVerticalOffset() {
883        return rangeLabelVerticalOffset;
884    }
885
886    public void setRangeLabelVerticalOffset(float rangeLabelVerticalOffset) {
887        this.rangeLabelVerticalOffset = rangeLabelVerticalOffset;
888    }
889
890    public Paint getGridBackgroundPaint() {
891        return gridBackgroundPaint;
892    }
893
894    public void setGridBackgroundPaint(Paint gridBackgroundPaint) {
895        this.gridBackgroundPaint = gridBackgroundPaint;
896    }
897
898    public Paint getDomainLabelPaint() {
899        return domainLabelPaint;
900    }
901
902    public void setDomainLabelPaint(Paint domainLabelPaint) {
903        this.domainLabelPaint = domainLabelPaint;
904    }
905
906    public Paint getRangeLabelPaint() {
907        return rangeLabelPaint;
908    }
909
910    public void setRangeLabelPaint(Paint rangeLabelPaint) {
911        this.rangeLabelPaint = rangeLabelPaint;
912    }
913
914    /**
915     * Get the paint used to draw the domain grid line.
916     */
917    public Paint getDomainGridLinePaint() {
918        return domainGridLinePaint;
919    }
920
921    /**
922     * Set the paint used to draw the domain grid line.
923     * @param gridLinePaint
924     */
925    public void setDomainGridLinePaint(Paint gridLinePaint) {
926        this.domainGridLinePaint = gridLinePaint;
927    }
928
929    /**
930     * Get the paint used to draw the range grid line.
931     */
932    public Paint getRangeGridLinePaint() {
933        return rangeGridLinePaint;
934    }
935
936    /**
937     * Get the paint used to draw the domain grid line.
938     */
939    public Paint getDomainSubGridLinePaint() {
940        return domainSubGridLinePaint;
941    }
942
943    /**
944     * Set the paint used to draw the domain grid line.
945     * @param gridLinePaint
946     */
947    public void setDomainSubGridLinePaint(Paint gridLinePaint) {
948        this.domainSubGridLinePaint = gridLinePaint;
949    }
950
951    /**
952     * Set the Paint used to draw the range grid line.
953     * @param gridLinePaint
954     */
955    public void setRangeGridLinePaint(Paint gridLinePaint) {
956        this.rangeGridLinePaint = gridLinePaint;
957    }
958
959    /**
960     * Get the paint used to draw the range grid line.
961     */
962    public Paint getRangeSubGridLinePaint() {
963        return rangeSubGridLinePaint;
964    }
965
966    /**
967     * Set the Paint used to draw the range grid line.
968     * @param gridLinePaint
969     */
970    public void setRangeSubGridLinePaint(Paint gridLinePaint) {
971        this.rangeSubGridLinePaint = gridLinePaint;
972    }
973
974    // TODO: make a generic renderer queue.
975
976    public Format getRangeValueFormat() {
977        return rangeValueFormat;
978    }
979
980    public void setRangeValueFormat(Format rangeValueFormat) {
981        this.rangeValueFormat = rangeValueFormat;
982    }
983
984    public Format getDomainValueFormat() {
985        return domainValueFormat;
986    }
987
988    public void setDomainValueFormat(Format domainValueFormat) {
989        this.domainValueFormat = domainValueFormat;
990    }
991
992    public int getDomainLabelTickExtension() {
993        return domainLabelTickExtension;
994    }
995
996    public void setDomainLabelTickExtension(int domainLabelTickExtension) {
997        this.domainLabelTickExtension = domainLabelTickExtension;
998    }
999
1000    public int getRangeLabelTickExtension() {
1001        return rangeLabelTickExtension;
1002    }
1003
1004    public void setRangeLabelTickExtension(int rangeLabelTickExtension) {
1005        this.rangeLabelTickExtension = rangeLabelTickExtension;
1006    }
1007
1008    public int getTicksPerRangeLabel() {
1009        return ticksPerRangeLabel;
1010    }
1011
1012    public void setTicksPerRangeLabel(int ticksPerRangeLabel) {
1013        this.ticksPerRangeLabel = ticksPerRangeLabel;
1014    }
1015
1016    public int getTicksPerDomainLabel() {
1017        return ticksPerDomainLabel;
1018    }
1019
1020    public void setTicksPerDomainLabel(int ticksPerDomainLabel) {
1021        this.ticksPerDomainLabel = ticksPerDomainLabel;
1022    }
1023
1024    public void setGridPaddingTop(float gridPaddingTop) {
1025        this.gridPaddingTop = gridPaddingTop;
1026    }
1027
1028    public float getGridPaddingBottom() {
1029        return gridPaddingBottom;
1030    }
1031
1032    public void setGridPaddingBottom(float gridPaddingBottom) {
1033        this.gridPaddingBottom = gridPaddingBottom;
1034    }
1035
1036    public float getGridPaddingLeft() {
1037        return gridPaddingLeft;
1038    }
1039
1040    public void setGridPaddingLeft(float gridPaddingLeft) {
1041        this.gridPaddingLeft = gridPaddingLeft;
1042    }
1043
1044    public float getGridPaddingRight() {
1045        return gridPaddingRight;
1046    }
1047
1048    public void setGridPaddingRight(float gridPaddingRight) {
1049        this.gridPaddingRight = gridPaddingRight;
1050    }
1051
1052    public float getGridPaddingTop() {
1053        return gridPaddingTop;
1054    }
1055
1056    public void setGridPadding(float left, float top, float right, float bottom) {
1057        setGridPaddingLeft(left);
1058        setGridPaddingTop(top);
1059        setGridPaddingRight(right);
1060        setGridPaddingBottom(bottom);
1061    }
1062
1063    public Paint getDomainOriginLinePaint() {
1064        return domainOriginLinePaint;
1065    }
1066
1067    public void setDomainOriginLinePaint(Paint domainOriginLinePaint) {
1068        this.domainOriginLinePaint = domainOriginLinePaint;
1069    }
1070
1071    public Paint getRangeOriginLinePaint() {
1072        return rangeOriginLinePaint;
1073    }
1074
1075    public void setRangeOriginLinePaint(Paint rangeOriginLinePaint) {
1076        this.rangeOriginLinePaint = rangeOriginLinePaint;
1077    }
1078
1079    public Paint getDomainOriginLabelPaint() {
1080        return domainOriginLabelPaint;
1081    }
1082
1083    public void setDomainOriginLabelPaint(Paint domainOriginLabelPaint) {
1084        this.domainOriginLabelPaint = domainOriginLabelPaint;
1085    }
1086
1087    public Paint getRangeOriginLabelPaint() {
1088        return rangeOriginLabelPaint;
1089    }
1090
1091    public void setRangeOriginLabelPaint(Paint rangeOriginLabelPaint) {
1092        this.rangeOriginLabelPaint = rangeOriginLabelPaint;
1093    }
1094
1095    public void setCursorPosition(float x, float y) {
1096        setDomainCursorPosition(x);
1097        setRangeCursorPosition(y);
1098    }
1099
1100    public void setCursorPosition(PointF point) {
1101        setCursorPosition(point.x, point.y);
1102    }
1103
1104    public float getDomainCursorPosition() {
1105        return domainCursorPosition;
1106    }
1107
1108    public Double getDomainCursorVal() {
1109        return getXVal(getDomainCursorPosition());
1110    }
1111
1112    public void setDomainCursorPosition(float domainCursorPosition) {
1113        this.domainCursorPosition = domainCursorPosition;
1114    }
1115
1116    public float getRangeCursorPosition() {
1117        return rangeCursorPosition;
1118    }
1119
1120    public Double getRangeCursorVal() {
1121        return getYVal(getRangeCursorPosition());
1122    }
1123
1124    public void setRangeCursorPosition(float rangeCursorPosition) {
1125        this.rangeCursorPosition = rangeCursorPosition;
1126    }
1127
1128    public Paint getCursorLabelPaint() {
1129        return cursorLabelPaint;
1130    }
1131
1132    public void setCursorLabelPaint(Paint cursorLabelPaint) {
1133        this.cursorLabelPaint = cursorLabelPaint;
1134    }
1135
1136    public Paint getCursorLabelBackgroundPaint() {
1137        return cursorLabelBackgroundPaint;
1138    }
1139
1140    public void setCursorLabelBackgroundPaint(Paint cursorLabelBackgroundPaint) {
1141        this.cursorLabelBackgroundPaint = cursorLabelBackgroundPaint;
1142    }
1143
1144    public boolean isDrawMarkersEnabled() {
1145        return drawMarkersEnabled;
1146    }
1147
1148    public void setDrawMarkersEnabled(boolean drawMarkersEnabled) {
1149        this.drawMarkersEnabled = drawMarkersEnabled;
1150    }
1151
1152    public boolean isRangeAxisLeft() {
1153        return rangeAxisLeft;
1154    }
1155
1156    public void setRangeAxisLeft(boolean rangeAxisLeft) {
1157        this.rangeAxisLeft = rangeAxisLeft;
1158    }
1159
1160    public boolean isDomainAxisBottom() {
1161        return domainAxisBottom;
1162    }
1163
1164    public void setDomainAxisBottom(boolean domainAxisBottom) {
1165        this.domainAxisBottom = domainAxisBottom;
1166    }
1167
1168    /*
1169     * set the position of the range axis labels.  Set the labelPaint textSizes before setting this.
1170     * This call sets the various vertical and horizontal offsets and widths to good defaults.
1171     *
1172     * @param rangeAxisLeft axis labels are on the left hand side not the right hand side.
1173     * @param rangeAxisOverlay axis labels are overlaid on the plot, not external to it.
1174     * @param tickSize the size of the tick extensions for none overlaid axis.
1175     * @param maxLableString Sample label representing the biggest size space needs to be allocated for.
1176     */
1177    public void setRangeAxisPosition(boolean rangeAxisLeft, boolean rangeAxisOverlay, int tickSize, String maxLableString){
1178        setRangeAxisLeft(rangeAxisLeft);
1179
1180        if (rangeAxisOverlay) {
1181            setRangeLabelWidth(1);    // needs to be at least 1 to display grid line.
1182            setRangeLabelHorizontalOffset(-2.0f);
1183            setRangeLabelVerticalOffset(2.0f);    // get above the line
1184            Paint p = getRangeLabelPaint();
1185            if (p != null) {
1186                p.setTextAlign(((rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT));
1187            }
1188            Paint po = getRangeOriginLabelPaint();
1189            if (po != null) {
1190                po.setTextAlign(((rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT));
1191            }
1192            setRangeLabelTickExtension(0);
1193        } else {
1194            setRangeLabelWidth(1);    // needs to be at least 1 to display grid line.
1195                                      // if we have a paint this gets bigger.
1196            setRangeLabelHorizontalOffset(1.0f);
1197            setRangeLabelTickExtension(tickSize);
1198            Paint p = getRangeLabelPaint();
1199            if (p != null) {
1200                p.setTextAlign(((!rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT));
1201                Rect r = FontUtils.getPackedStringDimensions(maxLableString,p);
1202                setRangeLabelVerticalOffset(r.top/2);
1203                setRangeLabelWidth(r.right + getRangeLabelTickExtension());
1204            }
1205            Paint po = getRangeOriginLabelPaint();
1206            if (po != null) {
1207                po.setTextAlign(((!rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT));
1208            }
1209        }
1210    }
1211
1212    /*
1213     * set the position of the domain axis labels.  Set the labelPaint textSizes before setting this.
1214     * This call sets the various vertical and horizontal offsets and widths to good defaults.
1215     *
1216     * @param domainAxisBottom axis labels are on the bottom not the top of the plot.
1217     * @param domainAxisOverlay axis labels are overlaid on the plot, not external to it.
1218     * @param tickSize the size of the tick extensions for non overlaid axis.
1219     * @param maxLableString Sample label representing the biggest size space needs to be allocated for.
1220     */
1221    public void setDomainAxisPosition(boolean domainAxisBottom, boolean domainAxisOverlay, int tickSize, String maxLabelString){
1222        setDomainAxisBottom(domainAxisBottom);
1223        if (domainAxisOverlay) {
1224            setDomainLabelWidth(1);    // needs to be at least 1 to display grid line.
1225            setDomainLabelVerticalOffset(2.0f);    // get above the line
1226            setDomainLabelTickExtension(0);
1227            Paint p = getDomainLabelPaint();
1228            if (p != null) {
1229                Rect r = FontUtils.getPackedStringDimensions(maxLabelString,p);
1230                if (domainAxisBottom){
1231                    setDomainLabelVerticalOffset(2 * r.top);
1232                } else {
1233                    setDomainLabelVerticalOffset(r.top - 1.0f);
1234                }
1235            }
1236        } else {
1237            setDomainLabelWidth(1);    // needs to be at least 1 to display grid line.
1238                                       // if we have a paint this gets bigger.
1239            setDomainLabelTickExtension(tickSize);
1240            Paint p = getDomainLabelPaint();
1241            if (p != null) {
1242                float fontHeight = FontUtils.getFontHeight(p);
1243                if (domainAxisBottom){
1244                    setDomainLabelVerticalOffset(-4.0f);
1245                } else {
1246                    setDomainLabelVerticalOffset(+1.0f);
1247                }
1248                setDomainLabelWidth(fontHeight + getDomainLabelTickExtension());
1249            }
1250        }
1251    }
1252}
1253