• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.androidplot;
18 
19 import android.content.Context;
20 import android.graphics.*;
21 import android.os.Build;
22 import android.os.Looper;
23 import android.util.AttributeSet;
24 import android.util.Log;
25 import android.view.View;
26 import com.androidplot.exception.PlotRenderException;
27 import com.androidplot.ui.*;
28 import com.androidplot.ui.Formatter;
29 import com.androidplot.ui.TextOrientationType;
30 import com.androidplot.ui.widget.TextLabelWidget;
31 import com.androidplot.ui.widget.Widget;
32 import com.androidplot.ui.SeriesRenderer;
33 import com.androidplot.util.Configurator;
34 import com.androidplot.util.DisplayDimensions;
35 import com.androidplot.util.PixelUtils;
36 import com.androidplot.ui.XLayoutStyle;
37 import com.androidplot.ui.YLayoutStyle;
38 
39 import java.util.*;
40 
41 /**
42  * Base class for all other Plot implementations..
43  */
44 public abstract class Plot<SeriesType extends Series, FormatterType extends Formatter, RendererType extends SeriesRenderer>
45         extends View implements Resizable{
46     private static final String TAG = Plot.class.getName();
47     private static final String XML_ATTR_PREFIX      = "androidplot";
48 
49     private static final String ATTR_TITLE           = "title";
50     private static final String ATTR_RENDER_MODE     = "renderMode";
51 
getDisplayDimensions()52     public DisplayDimensions getDisplayDimensions() {
53         return displayDims;
54     }
55 
56     public enum BorderStyle {
57         ROUNDED,
58         SQUARE,
59         NONE
60     }
61 
62     /**
63      * The RenderMode used by a Plot to draw it's self onto the screen.  The RenderMode can be set
64      * in two ways.
65      *
66      * In an xml layout:
67      *
68      * <code>
69      * <com.androidplot.xy.XYPlot
70      * android:id="@+id/mySimpleXYPlot"
71      * android:layout_width="fill_parent"
72      * android:layout_height="fill_parent"
73      * title="@string/sxy_title"
74      * renderMode="useBackgroundThread"/>
75      * </code>
76      *
77      * Programatically:
78      *
79      * <code>
80      * XYPlot myPlot = new XYPlot(context "MyPlot", Plot.RenderMode.USE_MAIN_THREAD);
81      * </code>
82      *
83      * A Plot's  RenderMode cannot be changed after the plot has been initialized.
84      * @since 0.5.1
85      */
86     public enum RenderMode {
87         /**
88          * Use a second thread and an off-screen buffer to do drawing.  This is the preferred method
89          * of drawing dynamic data and static data that consists of a large number of points.  This mode
90          * provides more efficient CPU utilization at the cost of increased memory usage.  As of
91          * version 0.5.1 this is the default RenderMode.
92          *
93          * XML value: use_background_thread
94          * @since 0.5.1
95          */
96         USE_BACKGROUND_THREAD,
97 
98         /**
99          * Do everything in the primary thread.  This is the preferred method of drawing static charts
100          * and dynamic data that consists of a small number of points. This mode uses less memory at
101          * the cost of poor CPU utilization.
102          *
103          * XML value: use_main_thread
104          * @since 0.5.1
105          */
106         USE_MAIN_THREAD
107     }
108     private BoxModel boxModel = new BoxModel(3, 3, 3, 3, 3, 3, 3, 3);
109     private BorderStyle borderStyle = Plot.BorderStyle.SQUARE;
110     private float borderRadiusX = 15;
111     private float borderRadiusY = 15;
112     private boolean drawBorderEnabled = true;
113     private Paint borderPaint;
114     private Paint backgroundPaint;
115     private LayoutManager layoutManager;
116     private TextLabelWidget titleWidget;
117     private DisplayDimensions displayDims = new DisplayDimensions();
118     private RenderMode renderMode = RenderMode.USE_MAIN_THREAD;
119     private final BufferedCanvas pingPong = new BufferedCanvas();
120 
121     // used to get rid of flickering when drawing offScreenBitmap to the visible Canvas.
122     private final Object renderSynch = new Object();
123 
124     /**
125      * Used for caching renderer instances.  Note that once a renderer is initialized it remains initialized
126      * for the life of the application; does not and should not be destroyed until the application exits.
127      */
128     private LinkedList<RendererType> renderers;
129 
130     /**
131      * Associates lists series and formatter pairs with the class of the Renderer used to render them.
132      */
133     private LinkedHashMap<Class, SeriesAndFormatterList<SeriesType,FormatterType>> seriesRegistry;
134 
135     private final ArrayList<PlotListener> listeners;
136 
137     private Thread renderThread;
138     private boolean keepRunning = false;
139     private boolean isIdle = true;
140 
141     {
142         listeners = new ArrayList<PlotListener>();
143         seriesRegistry = new LinkedHashMap<Class, SeriesAndFormatterList<SeriesType,FormatterType>>();
144         renderers = new LinkedList<RendererType>();
145         borderPaint = new Paint();
146         borderPaint.setColor(Color.rgb(150, 150, 150));
147         borderPaint.setStyle(Paint.Style.STROKE);
148         borderPaint.setStrokeWidth(1.0f);
149         borderPaint.setAntiAlias(true);
150         backgroundPaint = new Paint();
151         backgroundPaint.setColor(Color.DKGRAY);
152         backgroundPaint.setStyle(Paint.Style.FILL);
153     }
154 
155 
156     /**
157      *  Any rendering that utilizes a buffer from this class should synchronize rendering on the instance of this class
158      *  that is being used.
159      */
160     private class BufferedCanvas {
161         private volatile Bitmap bgBuffer;  // all drawing is done on this buffer.
162         private volatile Bitmap fgBuffer;
163         private Canvas canvas = new Canvas();
164 
165         /**
166          * Call this method once drawing on a Canvas retrieved by {@link #getCanvas()} to mark
167          * the buffer as fully rendered.  Failure to call this method will result in nothing being drawn.
168          */
swap()169         public synchronized void swap() {
170             Bitmap tmp = bgBuffer;
171             bgBuffer = fgBuffer;
172             fgBuffer = tmp;
173         }
174 
resize(int h, int w)175         public synchronized void resize(int h, int w) {
176             if (w <= 0 || h <= 0) {
177                 bgBuffer = null;
178                 fgBuffer = null;
179             } else {
180                 bgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
181                 fgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
182             }
183         }
184 
185 
186         /**
187          * Get a Canvas for drawing.  Actual drawing should be synchronized on the instance
188          * of BufferedCanvas being used.
189          * @return The Canvas instance to draw onto.  Returns null if drawing buffers have not
190          *         been initialized a la {@link #resize(int, int)}.
191          */
getCanvas()192         public synchronized Canvas getCanvas() {
193             if(bgBuffer != null) {
194                 canvas.setBitmap(bgBuffer);
195                 return canvas;
196             } else {
197                 return null;
198             }
199         }
200 
201         /**
202          * @return The most recent fully rendered Bitmsp
203          */
getBitmap()204         public Bitmap getBitmap() {
205             return fgBuffer;
206         }
207     }
208 
209     /**
210      * Convenience constructor - wraps {@link #Plot(android.content.Context, String, com.androidplot.Plot.RenderMode)}.
211      * RenderMode is set to {@link RenderMode#USE_BACKGROUND_THREAD}.
212      * @param context
213      * @param title The display title of this Plot.
214      */
Plot(Context context, String title)215     public Plot(Context context, String title) {
216         this(context, title, RenderMode.USE_MAIN_THREAD);
217     }
218 
219     /**
220      * Used for programmatic instantiation.
221      * @param context
222      * @param title The display title of this Plot.
223      */
Plot(Context context, String title, RenderMode mode)224     public Plot(Context context, String title, RenderMode mode) {
225         super(context);
226         this.renderMode = mode;
227         init(null, null);
228         setTitle(title);
229     }
230 
231 
232     /**
233      * Required by super-class. Extending class' implementations should add
234      * the following code immediately before exiting to ensure that loadAttrs
235      * is called only once by the derived class:
236      * <code>
237      * if(getClass().equals(DerivedPlot.class) {
238      *     loadAttrs(context, attrs);
239      * }
240      * </code>
241      *
242      * See {@link com.androidplot.xy.XYPlot#XYPlot(android.content.Context, android.util.AttributeSet)}
243      * for an example.
244      * @param context
245      * @param attrs
246      */
Plot(Context context, AttributeSet attrs)247     public Plot(Context context, AttributeSet attrs) {
248         super(context, attrs);
249         init(context, attrs);
250     }
251 
252     /**
253      * Required by super-class. Extending class' implementations should add
254      * the following code immediately before exiting to ensure that loadAttrs
255      * is called only once by the derived class:
256      * <code>
257      * if(getClass().equals(DerivedPlot.class) {
258      *     loadAttrs(context, attrs);
259      * }
260      * </code>
261      *
262      * See {@link com.androidplot.xy.XYPlot#XYPlot(android.content.Context, android.util.AttributeSet, int)}
263      * for an example.
264      * @param context
265      * @param attrs
266      * @param defStyle
267      */
Plot(Context context, AttributeSet attrs, int defStyle)268     public Plot(Context context, AttributeSet attrs, int defStyle) {
269         super(context, attrs, defStyle);
270         init(context, attrs);
271     }
272 
273     /**
274      * Can be overridden by derived classes to control hardware acceleration state.
275      * Note that this setting is only used on Honeycomb and later environments.
276      * @return True if hardware acceleration is allowed, false otherwise.
277      * @since 0.5.1
278      */
279     @SuppressWarnings("BooleanMethodIsAlwaysInverted")
isHwAccelerationSupported()280     protected boolean isHwAccelerationSupported() {
281         return false;
282     }
283 
284     /**
285      * Sets the render mode used by the Plot.
286      * WARNING: This method is not currently designed for general use outside of Configurator.
287      * Attempting to reassign the render mode at runtime will result in unexpected behavior.
288      * @param mode
289      */
setRenderMode(RenderMode mode)290     public void setRenderMode(RenderMode mode) {
291         this.renderMode = mode;
292     }
293 
294     /**
295      * Concrete implementations should do any final setup / initialization
296      * here.  Immediately following this method's invocation, AndroidPlot assumes
297      * that the Plot instance is ready for final configuration via the Configurator.
298      */
onPreInit()299     protected abstract void onPreInit();
300 
301 
init(Context context, AttributeSet attrs)302     private void init(Context context, AttributeSet attrs) {
303         PixelUtils.init(getContext());
304         layoutManager = new LayoutManager();
305         titleWidget = new TextLabelWidget(layoutManager, new SizeMetrics(25,
306                 SizeLayoutType.ABSOLUTE, 100,
307                 SizeLayoutType.ABSOLUTE),
308                 TextOrientationType.HORIZONTAL);
309         titleWidget.position(0, XLayoutStyle.RELATIVE_TO_CENTER, 0,
310                 YLayoutStyle.ABSOLUTE_FROM_TOP, AnchorPosition.TOP_MIDDLE);
311 
312         onPreInit();
313         // make sure the title widget is always the topmost widget:
314         layoutManager.moveToTop(titleWidget);
315         if(context != null && attrs != null) {
316             loadAttrs(attrs);
317         }
318 
319         layoutManager.onPostInit();
320         Log.d(TAG, "AndroidPlot RenderMode: " + renderMode);
321         if (renderMode == RenderMode.USE_BACKGROUND_THREAD) {
322             renderThread = new Thread(new Runnable() {
323                 @Override
324                 public void run() {
325 
326                     keepRunning = true;
327                     while (keepRunning) {
328                         isIdle = false;
329                         synchronized (pingPong) {
330                             Canvas c = pingPong.getCanvas();
331                             renderOnCanvas(c);
332                             pingPong.swap();
333                         }
334                         synchronized (renderSynch) {
335                             postInvalidate();
336                             // prevent this thread from becoming an orphan
337                             // after the view is destroyed
338                             if (keepRunning) {
339                                 try {
340                                     renderSynch.wait();
341                                 } catch (InterruptedException e) {
342                                     keepRunning = false;
343                                 }
344                             }
345                         }
346                     }
347                     System.out.println("AndroidPlot render thread finished.");
348                 }
349             });
350         }
351     }
352 
353     /**
354      * Parse XML Attributes.  Should only be called once and at the end of the base class constructor.
355      *
356      * @param attrs
357      */
loadAttrs(AttributeSet attrs)358     private void loadAttrs(AttributeSet attrs) {
359 
360         if (attrs != null) {
361             // filter out androidplot prefixed attrs:
362             HashMap<String, String> attrHash = new HashMap<String, String>();
363             for (int i = 0; i < attrs.getAttributeCount(); i++) {
364                 String attrName = attrs.getAttributeName(i);
365 
366                 // case insensitive check to see if this attr begins with our prefix:
367                 if (attrName.toUpperCase().startsWith(XML_ATTR_PREFIX.toUpperCase())) {
368                     attrHash.put(attrName.substring(XML_ATTR_PREFIX.length() + 1), attrs.getAttributeValue(i));
369                 }
370             }
371             Configurator.configure(getContext(), this, attrHash);
372         }
373     }
374 
getRenderMode()375     public RenderMode getRenderMode() {
376         return renderMode;
377     }
378 
addListener(PlotListener listener)379     public synchronized boolean addListener(PlotListener listener) {
380         return !listeners.contains(listener) && listeners.add(listener);
381     }
382 
removeListener(PlotListener listener)383     public synchronized boolean removeListener(PlotListener listener) {
384         return listeners.remove(listener);
385     }
386 
notifyListenersBeforeDraw(Canvas canvas)387     protected void notifyListenersBeforeDraw(Canvas canvas) {
388         for (PlotListener listener : listeners) {
389             listener.onBeforeDraw(this, canvas);
390         }
391     }
392 
notifyListenersAfterDraw(Canvas canvas)393     protected void notifyListenersAfterDraw(Canvas canvas) {
394         for (PlotListener listener : listeners) {
395             listener.onAfterDraw(this, canvas);
396         }
397     }
398 
399     /**
400      * @param series
401      */
addSeries(SeriesType series, FormatterType formatter)402     public synchronized boolean addSeries(SeriesType series, FormatterType formatter) {
403         Class rendererClass = formatter.getRendererClass();
404         SeriesAndFormatterList<SeriesType, FormatterType> sfList = seriesRegistry.get(rendererClass);
405 
406         // if there is no list for this renderer type, we need to getInstance one:
407         if(sfList == null) {
408             // make sure there is not already an instance of this renderer available:
409             if(getRenderer(rendererClass) == null) {
410                 renderers.add((RendererType) formatter.getRendererInstance(this));
411             }
412             sfList = new SeriesAndFormatterList<SeriesType,FormatterType>();
413             seriesRegistry.put(rendererClass, sfList);
414         }
415 
416         // if this series implements PlotListener, add it as a listener:
417         if(series instanceof PlotListener) {
418             addListener((PlotListener)series);
419         }
420 
421         // do nothing if this series already associated with the renderer:
422         if(sfList.contains(series)) {
423             return false;
424         } else {
425             sfList.add(series, formatter);
426             return true;
427         }
428     }
429 
removeSeries(SeriesType series, Class rendererClass)430     public synchronized boolean removeSeries(SeriesType series, Class rendererClass) {
431         boolean result = seriesRegistry.get(rendererClass).remove(series);
432         if(seriesRegistry.get(rendererClass).size() <= 0) {
433             seriesRegistry.remove(rendererClass);
434         }
435 
436         // if series implements PlotListener, remove it from listeners:
437         if(series instanceof PlotListener) {
438             removeListener((PlotListener) series);
439         }
440         return result;
441     }
442 
443     /**
444      * Remove all occorrences of series from all renderers
445      * @param series
446      */
removeSeries(SeriesType series)447     public synchronized void removeSeries(SeriesType series) {
448 
449         // remove all occurrences of series from all renderers:
450         for(Class rendererClass : seriesRegistry.keySet()) {
451             seriesRegistry.get(rendererClass).remove(series);
452         }
453 
454         // remove empty SeriesAndFormatterList instances from the registry:
455         for(Iterator<SeriesAndFormatterList<SeriesType,FormatterType>> it = seriesRegistry.values().iterator(); it.hasNext();) {
456             if(it.next().size() <= 0) {
457                 it.remove();
458             }
459         }
460 
461         // if series implements PlotListener, remove it from listeners:
462         if (series instanceof PlotListener) {
463             removeListener((PlotListener) series);
464         }
465     }
466 
467     /**
468      * Remove all series from all renderers
469      */
clear()470     public void clear() {
471         for(Iterator<SeriesAndFormatterList<SeriesType,FormatterType>> it = seriesRegistry.values().iterator(); it.hasNext();) {
472             it.next();
473             it.remove();
474         }
475     }
476 
isEmpty()477     public boolean isEmpty() {
478         return seriesRegistry.isEmpty();
479     }
480 
getFormatter(SeriesType series, Class rendererClass)481     public FormatterType getFormatter(SeriesType series, Class rendererClass) {
482         return seriesRegistry.get(rendererClass).getFormatter(series);
483     }
484 
getSeriesAndFormatterListForRenderer(Class rendererClass)485     public SeriesAndFormatterList<SeriesType,FormatterType> getSeriesAndFormatterListForRenderer(Class rendererClass) {
486         return seriesRegistry.get(rendererClass);
487     }
488 
489     /**
490      * Returns a list of all series assigned to the various renderers within the Plot.
491      * The returned List will contain no duplicates.
492      * @return
493      */
getSeriesSet()494     public Set<SeriesType> getSeriesSet() {
495         Set<SeriesType> seriesSet = new LinkedHashSet<SeriesType>();
496         for (SeriesRenderer renderer : getRendererList()) {
497             List<SeriesType> seriesList = getSeriesListForRenderer(renderer.getClass());
498             if (seriesList != null) {
499                 for (SeriesType series : seriesList) {
500                     seriesSet.add(series);
501                 }
502             }
503         }
504         return seriesSet;
505     }
506 
getSeriesListForRenderer(Class rendererClass)507     public List<SeriesType> getSeriesListForRenderer(Class rendererClass) {
508         SeriesAndFormatterList<SeriesType,FormatterType> lst = seriesRegistry.get(rendererClass);
509         if(lst == null) {
510             return null;
511         } else {
512             return lst.getSeriesList();
513         }
514     }
515 
getRenderer(Class rendererClass)516     public RendererType getRenderer(Class rendererClass) {
517         for(RendererType renderer : renderers) {
518             if(renderer.getClass() == rendererClass) {
519                 return renderer;
520             }
521         }
522         return null;
523     }
524 
getRendererList()525     public List<RendererType> getRendererList() {
526         return renderers;
527     }
528 
setMarkupEnabled(boolean enabled)529     public void setMarkupEnabled(boolean enabled) {
530         this.layoutManager.setMarkupEnabled(enabled);
531     }
532 
533     /**
534      * Causes the plot to be redrawn.
535      * @since 0.5.1
536      */
redraw()537     public void redraw() {
538 
539         if (renderMode == RenderMode.USE_BACKGROUND_THREAD) {
540 
541             // only enter synchronized block if the call is expected to block OR
542             // if the render thread is idle, so we know that we won't have to wait to
543             // obtain a lock.
544             if (isIdle) {
545                 synchronized (renderSynch) {
546                     renderSynch.notify();
547                 }
548             }
549         } else if(renderMode == RenderMode.USE_MAIN_THREAD) {
550 
551             // are we on the UI thread?
552             if (Looper.myLooper() == Looper.getMainLooper()) {
553                 invalidate();
554             } else {
555                 postInvalidate();
556             }
557         } else {
558             throw new IllegalArgumentException("Unsupported Render Mode: " + renderMode);
559         }
560     }
561 
562     @Override
layout(final DisplayDimensions dims)563     public synchronized void layout(final DisplayDimensions dims) {
564         displayDims = dims;
565         layoutManager.layout(displayDims);
566     }
567 
568     @Override
onDetachedFromWindow()569     protected void onDetachedFromWindow() {
570         synchronized(renderSynch) {
571             keepRunning = false;
572             renderSynch.notify();
573         }
574     }
575 
576 
577     @Override
onSizeChanged(int w, int h, int oldw, int oldh)578     protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) {
579 
580         // update pixel conversion values
581         PixelUtils.init(getContext());
582 
583         // disable hardware acceleration if it's not explicitly supported
584         // by the current Plot implementation. this check only applies to
585         // honeycomb and later environments.
586         if (Build.VERSION.SDK_INT >= 11) {
587             if (!isHwAccelerationSupported() && isHardwareAccelerated()) {
588                 setLayerType(View.LAYER_TYPE_SOFTWARE, null);
589             }
590         }
591 
592         // pingPong is only used in background rendering mode.
593         if(renderMode == RenderMode.USE_BACKGROUND_THREAD) {
594             pingPong.resize(h, w);
595         }
596 
597         RectF cRect = new RectF(0, 0, w, h);
598         RectF mRect = boxModel.getMarginatedRect(cRect);
599         RectF pRect = boxModel.getPaddedRect(mRect);
600 
601         layout(new DisplayDimensions(cRect, mRect, pRect));
602         super.onSizeChanged(w, h, oldw, oldh);
603         if(renderThread != null && !renderThread.isAlive()) {
604             renderThread.start();
605         }
606     }
607 
608     /**
609      * Called whenever the plot needs to be drawn via the Handler, which invokes invalidate().
610      * Should never be called directly; use {@link #redraw()} instead.
611      * @param canvas
612      */
613     @Override
onDraw(Canvas canvas)614     protected void onDraw(Canvas canvas) {
615         if (renderMode == RenderMode.USE_BACKGROUND_THREAD) {
616             synchronized(pingPong) {
617                 Bitmap bmp = pingPong.getBitmap();
618                 if(bmp != null) {
619                     canvas.drawBitmap(bmp, 0, 0, null);
620                 }
621             }
622         } else if (renderMode == RenderMode.USE_MAIN_THREAD) {
623             renderOnCanvas(canvas);
624         } else {
625             throw new IllegalArgumentException("Unsupported Render Mode: " + renderMode);
626         }
627     }
628 
629     /**
630      * Renders the plot onto a canvas.  Used by both main thread to draw directly
631      * onto the View's canvas as well as by background draw to render onto a
632      * Bitmap buffer.  At the end of the day this is the main entry for a plot's
633      * "heavy lifting".
634      * @param canvas
635      */
renderOnCanvas(Canvas canvas)636     protected synchronized void renderOnCanvas(Canvas canvas) {
637         try {
638             // any series interested in synchronizing with plot should
639             // implement PlotListener.onBeforeDraw(...) and do a read lock from within its
640             // invocation.  This is the entry point into that call:
641             notifyListenersBeforeDraw(canvas);
642             try {
643                 // need to completely erase what was on the canvas before redrawing, otherwise
644                 // some odd aliasing artifacts begin to build up around the edges of aa'd entities
645                 // over time.
646                 canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
647                 if (backgroundPaint != null) {
648                     drawBackground(canvas, displayDims.marginatedRect);
649                 }
650 
651                 layoutManager.draw(canvas);
652 
653                 if (getBorderPaint() != null) {
654                     drawBorder(canvas, displayDims.marginatedRect);
655                 }
656             } catch (PlotRenderException e) {
657                 Log.e(TAG, "Exception while rendering Plot.", e);
658                 e.printStackTrace();
659             } catch (Exception e) {
660                 Log.e(TAG, "Exception while rendering Plot.", e);
661             }
662         } finally {
663             isIdle = true;
664             // any series interested in synchronizing with plot should
665             // implement PlotListener.onAfterDraw(...) and do a read unlock from within that
666             // invocation. This is the entry point for that invocation.
667             notifyListenersAfterDraw(canvas);
668         }
669     }
670 
671 
672     /**
673      * Sets the visual style of the plot's border.
674      * @param style
675      * @param radiusX Sets the X radius for BorderStyle.ROUNDED.  Use null for all other styles.
676      * @param radiusY Sets the Y radius for BorderStyle.ROUNDED.  Use null for all other styles.
677      */
setBorderStyle(BorderStyle style, Float radiusX, Float radiusY)678     public void setBorderStyle(BorderStyle style, Float radiusX, Float radiusY) {
679         if (style == Plot.BorderStyle.ROUNDED) {
680             if (radiusX == null || radiusY == null){
681                 throw new IllegalArgumentException("radiusX and radiusY cannot be null when using BorderStyle.ROUNDED");
682             }
683             this.borderRadiusX = radiusX;
684             this.borderRadiusY = radiusY;
685         }
686         this.borderStyle = style;
687     }
688 
689     /**
690      * Draws the plot's outer border.
691      * @param canvas
692      * @throws PlotRenderException
693      */
drawBorder(Canvas canvas, RectF dims)694     protected void drawBorder(Canvas canvas, RectF dims) {
695         switch (borderStyle) {
696             case ROUNDED:
697                 canvas.drawRoundRect(dims, borderRadiusX, borderRadiusY, borderPaint);
698                 break;
699             case SQUARE:
700                 canvas.drawRect(dims, borderPaint);
701                 break;
702             default:
703         }
704     }
705 
drawBackground(Canvas canvas, RectF dims)706     protected void drawBackground(Canvas canvas, RectF dims) {
707         switch (borderStyle) {
708             case ROUNDED:
709                 canvas.drawRoundRect(dims, borderRadiusX, borderRadiusY, backgroundPaint);
710                 break;
711             case SQUARE:
712                 canvas.drawRect(dims, backgroundPaint);
713                 break;
714             default:
715         }
716     }
717 
718     /**
719      *
720      * @return The displayed title of this Plot.
721      */
getTitle()722     public String getTitle() {
723         return getTitleWidget().getText();
724     }
725 
726     /**
727      *
728      * @param title  The title to display on this Plot.
729      */
setTitle(String title)730     public void setTitle(String title) {
731         titleWidget.setText(title);
732     }
733 
getLayoutManager()734     public LayoutManager getLayoutManager() {
735         return layoutManager;
736     }
737 
setLayoutManager(LayoutManager layoutManager)738     public void setLayoutManager(LayoutManager layoutManager) {
739         this.layoutManager = layoutManager;
740     }
741 
getTitleWidget()742     public TextLabelWidget getTitleWidget() {
743         return titleWidget;
744     }
745 
setTitleWidget(TextLabelWidget titleWidget)746     public void setTitleWidget(TextLabelWidget titleWidget) {
747         this.titleWidget = titleWidget;
748     }
749 
getBackgroundPaint()750     public Paint getBackgroundPaint() {
751         return backgroundPaint;
752     }
753 
setBackgroundPaint(Paint backgroundPaint)754     public void setBackgroundPaint(Paint backgroundPaint) {
755         this.backgroundPaint = backgroundPaint;
756     }
757 
758     /**
759      * Convenience method - wraps the individual setMarginXXX methods into a single method.
760      * @param left
761      * @param top
762      * @param right
763      * @param bottom
764      */
setPlotMargins(float left, float top, float right, float bottom)765     public void setPlotMargins(float left, float top, float right, float bottom) {
766         setPlotMarginLeft(left);
767         setPlotMarginTop(top);
768         setPlotMarginRight(right);
769         setPlotMarginBottom(bottom);
770     }
771 
772     /**
773      * Convenience method - wraps the individual setPaddingXXX methods into a single method.
774      * @param left
775      * @param top
776      * @param right
777      * @param bottom
778      */
setPlotPadding(float left, float top, float right, float bottom)779     public void setPlotPadding(float left, float top, float right, float bottom) {
780         setPlotPaddingLeft(left);
781         setPlotPaddingTop(top);
782         setPlotPaddingRight(right);
783         setPlotPaddingBottom(bottom);
784     }
785 
getPlotMarginTop()786     public float getPlotMarginTop() {
787         return boxModel.getMarginTop();
788     }
789 
setPlotMarginTop(float plotMarginTop)790     public void setPlotMarginTop(float plotMarginTop) {
791         boxModel.setMarginTop(plotMarginTop);
792     }
793 
getPlotMarginBottom()794     public float getPlotMarginBottom() {
795         return boxModel.getMarginBottom();
796     }
797 
setPlotMarginBottom(float plotMarginBottom)798     public void setPlotMarginBottom(float plotMarginBottom) {
799         boxModel.setMarginBottom(plotMarginBottom);
800     }
801 
getPlotMarginLeft()802     public float getPlotMarginLeft() {
803         return boxModel.getMarginLeft();
804     }
805 
setPlotMarginLeft(float plotMarginLeft)806     public void setPlotMarginLeft(float plotMarginLeft) {
807         boxModel.setMarginLeft(plotMarginLeft);
808     }
809 
getPlotMarginRight()810     public float getPlotMarginRight() {
811         return boxModel.getMarginRight();
812     }
813 
setPlotMarginRight(float plotMarginRight)814     public void setPlotMarginRight(float plotMarginRight) {
815         boxModel.setMarginRight(plotMarginRight);
816     }
817 
getPlotPaddingTop()818     public float getPlotPaddingTop() {
819         return boxModel.getPaddingTop();
820     }
821 
setPlotPaddingTop(float plotPaddingTop)822     public void setPlotPaddingTop(float plotPaddingTop) {
823         boxModel.setPaddingTop(plotPaddingTop);
824     }
825 
getPlotPaddingBottom()826     public float getPlotPaddingBottom() {
827         return boxModel.getPaddingBottom();
828     }
829 
setPlotPaddingBottom(float plotPaddingBottom)830     public void setPlotPaddingBottom(float plotPaddingBottom) {
831         boxModel.setPaddingBottom(plotPaddingBottom);
832     }
833 
getPlotPaddingLeft()834     public float getPlotPaddingLeft() {
835         return boxModel.getPaddingLeft();
836     }
837 
setPlotPaddingLeft(float plotPaddingLeft)838     public void setPlotPaddingLeft(float plotPaddingLeft) {
839         boxModel.setPaddingLeft(plotPaddingLeft);
840     }
841 
getPlotPaddingRight()842     public float getPlotPaddingRight() {
843         return boxModel.getPaddingRight();
844     }
845 
setPlotPaddingRight(float plotPaddingRight)846     public void setPlotPaddingRight(float plotPaddingRight) {
847         boxModel.setPaddingRight(plotPaddingRight);
848     }
849 
getBorderPaint()850     public Paint getBorderPaint() {
851         return borderPaint;
852     }
853 
854     /**
855      * Set's the paint used to draw the border.  Note that this method
856      * copies borderPaint and set's the copy's Paint.Style attribute to
857      * Paint.Style.STROKE.
858      * @param borderPaint
859      */
setBorderPaint(Paint borderPaint)860     public void setBorderPaint(Paint borderPaint) {
861         if(borderPaint == null) {
862             this.borderPaint = null;
863         } else {
864             this.borderPaint = new Paint(borderPaint);
865             this.borderPaint.setStyle(Paint.Style.STROKE);
866         }
867     }
868 }
869