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