Don't like ads? PRO users don't see any ads ;-)
Guest

Untitled

By: a guest on May 5th, 2012  |  syntax: Java  |  size: 36.45 KB  |  hits: 14  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. * Copyright (C) 2009 - 2012 SC 4ViewSoft SRL
  2.  *
  3.  * Licensed under the Apache License, Version 2.0 (the "License");
  4.  * you may not use this file except in compliance with the License.
  5.  * You may obtain a copy of the License at
  6.  *
  7.  *      http://www.apache.org/licenses/LICENSE-2.0
  8.  *
  9.  * Unless required by applicable law or agreed to in writing, software
  10.  * distributed under the License is distributed on an "AS IS" BASIS,
  11.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12.  * See the License for the specific language governing permissions and
  13.  * limitations under the License.
  14.  */
  15. package org.achartengine.chart;
  16.  
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.HashMap;
  20. import java.util.LinkedList;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Map.Entry;
  24. import java.util.SortedMap;
  25.  
  26. import org.achartengine.model.Point;
  27. import org.achartengine.model.SeriesSelection;
  28. import org.achartengine.model.XYMultipleSeriesDataset;
  29. import org.achartengine.model.XYSeries;
  30. import org.achartengine.renderer.BasicStroke;
  31. import org.achartengine.renderer.DefaultRenderer;
  32. import org.achartengine.renderer.SimpleSeriesRenderer;
  33. import org.achartengine.renderer.XYMultipleSeriesRenderer;
  34. import org.achartengine.renderer.XYMultipleSeriesRenderer.Orientation;
  35. import org.achartengine.util.MathHelper;
  36.  
  37. import android.graphics.Canvas;
  38. import android.graphics.Color;
  39. import android.graphics.DashPathEffect;
  40. import android.graphics.Paint;
  41. import android.graphics.Paint.Align;
  42. import android.graphics.Paint.Cap;
  43. import android.graphics.Paint.Join;
  44. import android.graphics.Paint.Style;
  45. import android.graphics.PathEffect;
  46. import android.graphics.Rect;
  47. import android.graphics.RectF;
  48. import android.graphics.Typeface;
  49.  
  50. /**
  51.  * The XY chart rendering class.
  52.  */
  53. public abstract class XYChart extends AbstractChart {
  54.   /** The multiple series dataset. */
  55.   protected XYMultipleSeriesDataset mDataset;
  56.   /** The multiple series renderer. */
  57.   protected XYMultipleSeriesRenderer mRenderer;
  58.   /** The current scale value. */
  59.   private float mScale;
  60.   /** The current translate value. */
  61.   private float mTranslate;
  62.   /** The canvas center point. */
  63.   private Point mCenter;
  64.   /** The visible chart area, in screen coordinates. */
  65.   private Rect mScreenR;
  66.   /** The calculated range. */
  67.   private final Map<Integer, double[]> mCalcRange = new HashMap<Integer, double[]>();
  68.  
  69.   /**
  70.    * The clickable areas for all points. The array index is the series index,
  71.    * and the RectF list index is the point index in that series.
  72.    */
  73.   private Map<Integer, List<ClickableArea>> clickableAreas = new HashMap<Integer, List<ClickableArea>>();
  74.  
  75.   protected XYChart() {
  76.   }
  77.  
  78.   /**
  79.    * Builds a new XY chart instance.
  80.    *
  81.    * @param dataset the multiple series dataset
  82.    * @param renderer the multiple series renderer
  83.    */
  84.   public XYChart(XYMultipleSeriesDataset dataset, XYMultipleSeriesRenderer renderer) {
  85.     mDataset = dataset;
  86.     mRenderer = renderer;
  87.   }
  88.  
  89.   // TODO: javadoc
  90.   protected void setDatasetRenderer(XYMultipleSeriesDataset dataset,
  91.       XYMultipleSeriesRenderer renderer) {
  92.     mDataset = dataset;
  93.     mRenderer = renderer;
  94.   }
  95.  
  96.   /**
  97.    * The graphical representation of the XY chart.
  98.    *
  99.    * @param canvas the canvas to paint to
  100.    * @param x the top left x value of the view to draw to
  101.    * @param y the top left y value of the view to draw to
  102.    * @param width the width of the view to draw to
  103.    * @param height the height of the view to draw to
  104.    * @param paint the paint
  105.    */
  106.   public void draw(Canvas canvas, int x, int y, int width, int height, Paint paint) {
  107.     paint.setAntiAlias(mRenderer.isAntialiasing());
  108.     int legendSize = getLegendSize(mRenderer, height / 5, mRenderer.getAxisTitleTextSize());
  109.     int[] margins = mRenderer.getMargins();
  110.     int left = x + margins[1];
  111.     int top = y + margins[0];
  112.     int right = x + width - margins[3];
  113.     int sLength = mDataset.getSeriesCount();
  114.     String[] titles = new String[sLength];
  115.     for (int i = 0; i < sLength; i++) {
  116.       titles[i] = mDataset.getSeriesAt(i).getTitle();
  117.     }
  118.     if (mRenderer.isFitLegend() && mRenderer.isShowLegend()) {
  119.       legendSize = drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize,
  120.           paint, true);
  121.     }
  122.     int bottom = y + height - margins[2] - legendSize;
  123.     if (mScreenR == null) {
  124.       mScreenR = new Rect();
  125.     }
  126.     mScreenR.set(left, top, right, bottom);
  127.     drawBackground(mRenderer, canvas, x, y, width, height, paint, false, DefaultRenderer.NO_COLOR);
  128.  
  129.     if (paint.getTypeface() == null
  130.         || !paint.getTypeface().toString().equals(mRenderer.getTextTypefaceName())
  131.         || paint.getTypeface().getStyle() != mRenderer.getTextTypefaceStyle()) {
  132.       paint.setTypeface(Typeface.create(mRenderer.getTextTypefaceName(),
  133.           mRenderer.getTextTypefaceStyle()));
  134.     }
  135.     Orientation or = mRenderer.getOrientation();
  136.     if (or == Orientation.VERTICAL) {
  137.       right -= legendSize;
  138.       bottom += legendSize - 20;
  139.     }
  140.     int angle = or.getAngle();
  141.     boolean rotate = angle == 90;
  142.     mScale = (float) (height) / width;
  143.     mTranslate = Math.abs(width - height) / 2;
  144.     if (mScale < 1) {
  145.       mTranslate *= -1;
  146.     }
  147.     mCenter = new Point((x + width) / 2, (y + height) / 2);
  148.     if (rotate) {
  149.       transform(canvas, angle, false);
  150.     }
  151.  
  152.     int maxScaleNumber = -Integer.MAX_VALUE;
  153.     for (int i = 0; i < sLength; i++) {
  154.       maxScaleNumber = Math.max(maxScaleNumber, mDataset.getSeriesAt(i).getScaleNumber());
  155.     }
  156.     maxScaleNumber++;
  157.     if (maxScaleNumber < 0) {
  158.       return;
  159.     }
  160.     double[] minX = new double[maxScaleNumber];
  161.     double[] maxX = new double[maxScaleNumber];
  162.     double[] minY = new double[maxScaleNumber];
  163.     double[] maxY = new double[maxScaleNumber];
  164.     boolean[] isMinXSet = new boolean[maxScaleNumber];
  165.     boolean[] isMaxXSet = new boolean[maxScaleNumber];
  166.     boolean[] isMinYSet = new boolean[maxScaleNumber];
  167.     boolean[] isMaxYSet = new boolean[maxScaleNumber];
  168.  
  169.     for (int i = 0; i < maxScaleNumber; i++) {
  170.       minX[i] = mRenderer.getXAxisMin(i);
  171.       maxX[i] = mRenderer.getXAxisMax(i);
  172.       minY[i] = mRenderer.getYAxisMin(i);
  173.       maxY[i] = mRenderer.getYAxisMax(i);
  174.       isMinXSet[i] = mRenderer.isMinXSet(i);
  175.       isMaxXSet[i] = mRenderer.isMaxXSet(i);
  176.       isMinYSet[i] = mRenderer.isMinYSet(i);
  177.       isMaxYSet[i] = mRenderer.isMaxYSet(i);
  178.       if (mCalcRange.get(i) == null) {
  179.         mCalcRange.put(i, new double[4]);
  180.       }
  181.     }
  182.     double[] xPixelsPerUnit = new double[maxScaleNumber];
  183.     double[] yPixelsPerUnit = new double[maxScaleNumber];
  184.     for (int i = 0; i < sLength; i++) {
  185.       XYSeries series = mDataset.getSeriesAt(i);
  186.       int scale = series.getScaleNumber();
  187.       if (series.getItemCount() == 0) {
  188.         continue;
  189.       }
  190.       if (!isMinXSet[scale]) {
  191.         double minimumX = series.getMinX();
  192.         minX[scale] = Math.min(minX[scale], minimumX);
  193.         mCalcRange.get(scale)[0] = minX[scale];
  194.       }
  195.       if (!isMaxXSet[scale]) {
  196.         double maximumX = series.getMaxX();
  197.         maxX[scale] = Math.max(maxX[scale], maximumX);
  198.         mCalcRange.get(scale)[1] = maxX[scale];
  199.       }
  200.       if (!isMinYSet[scale]) {
  201.         double minimumY = series.getMinY();
  202.         minY[scale] = Math.min(minY[scale], (float) minimumY);
  203.         mCalcRange.get(scale)[2] = minY[scale];
  204.       }
  205.       if (!isMaxYSet[scale]) {
  206.         double maximumY = series.getMaxY();
  207.         maxY[scale] = Math.max(maxY[scale], (float) maximumY);
  208.         mCalcRange.get(scale)[3] = maxY[scale];
  209.       }
  210.     }
  211.     for (int i = 0; i < maxScaleNumber; i++) {
  212.       if (maxX[i] - minX[i] != 0) {
  213.         xPixelsPerUnit[i] = (right - left) / (maxX[i] - minX[i]);
  214.       }
  215.       if (maxY[i] - minY[i] != 0) {
  216.         yPixelsPerUnit[i] = (float) ((bottom - top) / (maxY[i] - minY[i]));
  217.       }
  218.     }
  219.  
  220.     boolean hasValues = false;
  221.     // use a linked list for these reasons:
  222.     // 1) Avoid a large contiguous memory allocation
  223.     // 2) We don't need random seeking, only sequential reading/writing, so
  224.     // linked list makes sense
  225.     clickableAreas = new HashMap<Integer, List<ClickableArea>>();
  226.     for (int i = 0; i < sLength; i++) {
  227.       XYSeries series = mDataset.getSeriesAt(i);
  228.       int scale = series.getScaleNumber();
  229.       if (series.getItemCount() == 0) {
  230.         continue;
  231.       }
  232.  
  233.       hasValues = true;
  234.       SimpleSeriesRenderer seriesRenderer = mRenderer.getSeriesRendererAt(i);
  235.  
  236.       // int originalValuesLength = series.getItemCount();
  237.       // int valuesLength = originalValuesLength;
  238.       // int length = valuesLength * 2;
  239.  
  240.       List<Float> points = new ArrayList<Float>();
  241.       List<Double> values = new ArrayList<Double>();
  242.       float yAxisValue = Math.min(bottom, (float) (bottom + yPixelsPerUnit[scale] * minY[scale]));
  243.       LinkedList<ClickableArea> clickableArea = new LinkedList<ClickableArea>();
  244.  
  245.       clickableAreas.put(i, clickableArea);
  246.  
  247.       SortedMap<Double, Double> range = series.getRange(minX[scale], maxX[scale], 1);
  248.       int startIndex = -1;
  249.  
  250.       for (Entry<Double, Double> value : range.entrySet()) {
  251.  
  252.         double xValue = value.getKey();
  253.         double yValue = value.getValue();
  254.         if (startIndex < 0) {
  255.           startIndex = series.getIndexForKey(xValue);
  256.         }
  257.  
  258.         // points.add((float) (left + xPixelsPerUnit[scale]
  259.         // * (value.getKey().floatValue() - minX[scale])));
  260.         // points.add((float) (bottom - yPixelsPerUnit[scale]
  261.         // * (value.getValue().floatValue() - minY[scale])));
  262.         values.add(value.getKey());
  263.         values.add(value.getValue());
  264.  
  265.         if (!isNullValue(yValue)) {
  266.           points.add((float) (left + xPixelsPerUnit[scale] * (xValue - minX[scale])));
  267.           points.add((float) (bottom - yPixelsPerUnit[scale] * (yValue - minY[scale])));
  268.         } else if (isRenderNullValues()) {
  269.           points.add((float) (left + xPixelsPerUnit[scale] * (xValue - minX[scale])));
  270.           points.add((float) (bottom - yPixelsPerUnit[scale] * (-minY[scale])));
  271.         } else {
  272.           if (points.size() > 0) {
  273.             drawSeries(series, canvas, paint, points, seriesRenderer, yAxisValue, i, or, startIndex);
  274.             ClickableArea[] clickableAreasForSubSeries = clickableAreasForPoints(
  275.                 MathHelper.getFloats(points), MathHelper.getDoubles(values), yAxisValue, i,
  276.                 startIndex);
  277.             clickableArea.addAll(Arrays.asList(clickableAreasForSubSeries));
  278.             points.clear();
  279.             values.clear();
  280.           }
  281.           clickableArea.add(null);
  282.         }
  283.       }
  284.  
  285.       if (points.size() > 0) {
  286.         drawSeries(series, canvas, paint, points, seriesRenderer, yAxisValue, i, or, startIndex);
  287.         ClickableArea[] clickableAreasForSubSeries = clickableAreasForPoints(
  288.             MathHelper.getFloats(points), MathHelper.getDoubles(values), yAxisValue, i, startIndex);
  289.         clickableArea.addAll(Arrays.asList(clickableAreasForSubSeries));
  290.       }
  291.     }
  292.  
  293.     // draw stuff over the margins such as data doesn't render on these areas
  294.     drawBackground(mRenderer, canvas, x, bottom, width, height - bottom, paint, true,
  295.         mRenderer.getMarginsColor());
  296.     drawBackground(mRenderer, canvas, x, y, width, margins[0], paint, true,
  297.         mRenderer.getMarginsColor());
  298.     if (or == Orientation.HORIZONTAL) {
  299.       drawBackground(mRenderer, canvas, x, y, left - x, height - y, paint, true,
  300.           mRenderer.getMarginsColor());
  301.       drawBackground(mRenderer, canvas, right, y, margins[3], height - y, paint, true,
  302.           mRenderer.getMarginsColor());
  303.     } else if (or == Orientation.VERTICAL) {
  304.       drawBackground(mRenderer, canvas, right, y, width - right, height - y, paint, true,
  305.           mRenderer.getMarginsColor());
  306.       drawBackground(mRenderer, canvas, x, y, left - x, height - y, paint, true,
  307.           mRenderer.getMarginsColor());
  308.     }
  309.  
  310.     boolean showLabels = mRenderer.isShowLabels() && hasValues;
  311.     boolean showGridX = mRenderer.isShowGridX();
  312.     boolean showCustomTextGrid = mRenderer.isShowCustomTextGrid();
  313.     if (showLabels || showGridX) {
  314.       List<Double> xLabels = getValidLabels(getXLabels(minX[0], maxX[0], mRenderer.getXLabels()));
  315.       Map<Integer, List<Double>> allYLabels = getYLabels(minY, maxY, maxScaleNumber);
  316.  
  317.       int xLabelsLeft = left;
  318.       if (showLabels) {
  319.         paint.setColor(mRenderer.getXLabelsColor());
  320.         paint.setTextSize(mRenderer.getLabelsTextSize());
  321.         paint.setTextAlign(mRenderer.getXLabelsAlign());
  322.         if (mRenderer.getXLabelsAlign() == Align.LEFT) {
  323.           xLabelsLeft += mRenderer.getLabelsTextSize() / 4;
  324.         }
  325.       }
  326.       drawXLabels(xLabels, mRenderer.getXTextLabelLocations(), canvas, paint, xLabelsLeft, top,
  327.           bottom, xPixelsPerUnit[0], minX[0], maxX[0]);
  328.       drawYLabels(allYLabels, canvas, paint, maxScaleNumber, left, right, bottom, yPixelsPerUnit,
  329.           minY);
  330.  
  331.       if (showLabels) {
  332.         paint.setColor(mRenderer.getLabelsColor());
  333.         for (int i = 0; i < maxScaleNumber; i++) {
  334.           Align axisAlign = mRenderer.getYAxisAlign(i);
  335.           Double[] yTextLabelLocations = mRenderer.getYTextLabelLocations(i);
  336.           for (Double location : yTextLabelLocations) {
  337.             if (minY[i] <= location && location <= maxY[i]) {
  338.               float yLabel = (float) (bottom - yPixelsPerUnit[i]
  339.                   * (location.doubleValue() - minY[i]));
  340.               String label = mRenderer.getYTextLabel(location, i);
  341.               paint.setColor(mRenderer.getYLabelsColor(i));
  342.               paint.setTextAlign(mRenderer.getYLabelsAlign(i));
  343.               if (or == Orientation.HORIZONTAL) {
  344.                 if (axisAlign == Align.LEFT) {
  345.                   canvas.drawLine(left + getLabelLinePos(axisAlign), yLabel, left, yLabel, paint);
  346.                   drawText(canvas, label, left, yLabel - 2, paint, mRenderer.getYLabelsAngle());
  347.                 } else {
  348.                   canvas.drawLine(right, yLabel, right + getLabelLinePos(axisAlign), yLabel, paint);
  349.                   drawText(canvas, label, right, yLabel - 2, paint, mRenderer.getYLabelsAngle());
  350.                 }
  351.                
  352.                 if (showCustomTextGrid) {
  353.                   paint.setColor(mRenderer.getGridColor());
  354.                   canvas.drawLine(left, yLabel, right, yLabel, paint);
  355.                 }
  356.               } else {
  357.                 canvas.drawLine(right - getLabelLinePos(axisAlign), yLabel, right, yLabel, paint);
  358.                 drawText(canvas, label, right + 10, yLabel - 2, paint, mRenderer.getYLabelsAngle());
  359.                 if (showCustomTextGrid) {
  360.                   paint.setColor(mRenderer.getGridColor());
  361.                   canvas.drawLine(right, yLabel, left, yLabel, paint);
  362.                 }
  363.               }
  364.             }
  365.           }
  366.         }
  367.       }
  368.  
  369.       if (showLabels) {
  370.         paint.setColor(mRenderer.getLabelsColor());
  371.         float size = mRenderer.getAxisTitleTextSize();
  372.         paint.setTextSize(size);
  373.         paint.setTextAlign(Align.CENTER);
  374.         if (or == Orientation.HORIZONTAL) {
  375.           drawText(canvas, mRenderer.getXTitle(), x + width / 2,
  376.               bottom + mRenderer.getLabelsTextSize() * 4 / 3 + size, paint, 0);
  377.           for (int i = 0; i < maxScaleNumber; i++) {
  378.             Align axisAlign = mRenderer.getYAxisAlign(i);
  379.             if (axisAlign == Align.LEFT) {
  380.               drawText(canvas, mRenderer.getYTitle(i), x + size, y + height / 2, paint, -90);
  381.             } else {
  382.               drawText(canvas, mRenderer.getYTitle(i), x + width, y + height / 2, paint, -90);
  383.             }
  384.           }
  385.           paint.setTextSize(mRenderer.getChartTitleTextSize());
  386.           drawText(canvas, mRenderer.getChartTitle(), x + width / 2,
  387.               y + mRenderer.getChartTitleTextSize(), paint, 0);
  388.         } else if (or == Orientation.VERTICAL) {
  389.           drawText(canvas, mRenderer.getXTitle(), x + width / 2, y + height - size, paint, -90);
  390.           drawText(canvas, mRenderer.getYTitle(), right + 20, y + height / 2, paint, 0);
  391.           paint.setTextSize(mRenderer.getChartTitleTextSize());
  392.           drawText(canvas, mRenderer.getChartTitle(), x + size, top + height / 2, paint, 0);
  393.         }
  394.       }
  395.     }
  396.     if (or == Orientation.HORIZONTAL) {
  397.       drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize, paint, false);
  398.     } else if (or == Orientation.VERTICAL) {
  399.       transform(canvas, angle, true);
  400.       drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize, paint, false);
  401.       transform(canvas, angle, false);
  402.     }
  403.     if (mRenderer.isShowAxes()) {
  404.       paint.setColor(mRenderer.getAxesColor());
  405.       canvas.drawLine(left, bottom, right, bottom, paint);
  406.       boolean rightAxis = false;
  407.       for (int i = 0; i < maxScaleNumber && !rightAxis; i++) {
  408.         rightAxis = mRenderer.getYAxisAlign(i) == Align.RIGHT;
  409.       }
  410.       if (or == Orientation.HORIZONTAL) {
  411.         canvas.drawLine(left, top, left, bottom, paint);
  412.         if (rightAxis) {
  413.           canvas.drawLine(right, top, right, bottom, paint);
  414.         }
  415.       } else if (or == Orientation.VERTICAL) {
  416.         canvas.drawLine(right, top, right, bottom, paint);
  417.       }
  418.     }
  419.     if (rotate) {
  420.       transform(canvas, angle, true);
  421.     }
  422.  
  423.   }
  424.  
  425.   protected List<Double> getXLabels(double min, double max, int count) {
  426.     return MathHelper.getLabels(min, max, count);
  427.   }
  428.  
  429.   protected Map<Integer, List<Double>> getYLabels(double[] minY, double[] maxY, int maxScaleNumber) {
  430.     Map<Integer, List<Double>> allYLabels = new HashMap<Integer, List<Double>>();
  431.     for (int i = 0; i < maxScaleNumber; i++) {
  432.       allYLabels.put(i,
  433.           getValidLabels(MathHelper.getLabels(minY[i], maxY[i], mRenderer.getYLabels())));
  434.     }
  435.     return allYLabels;
  436.   }
  437.  
  438.   protected Rect getScreenR() {
  439.     return mScreenR;
  440.   }
  441.  
  442.   protected void setScreenR(Rect screenR) {
  443.     mScreenR = screenR;
  444.   }
  445.  
  446.   private List<Double> getValidLabels(List<Double> labels) {
  447.     List<Double> result = new ArrayList<Double>(labels);
  448.     for (Double label : labels) {
  449.       if (label.isNaN()) {
  450.         result.remove(label);
  451.       }
  452.     }
  453.     return result;
  454.   }
  455.  
  456.   /**
  457.    * Draws the series.
  458.    *
  459.    * @param series the series
  460.    * @param canvas the canvas
  461.    * @param paint the paint object
  462.    * @param pointsList the points to be rendered
  463.    * @param seriesRenderer the series renderer
  464.    * @param yAxisValue the y axis value in pixels
  465.    * @param seriesIndex the series index
  466.    * @param or the orientation
  467.    * @param startIndex the start index of the rendering points
  468.    */
  469.   protected void drawSeries(XYSeries series, Canvas canvas, Paint paint, List<Float> pointsList,
  470.       SimpleSeriesRenderer seriesRenderer, float yAxisValue, int seriesIndex, Orientation or,
  471.       int startIndex) {
  472.     BasicStroke stroke = seriesRenderer.getStroke();
  473.     Cap cap = paint.getStrokeCap();
  474.     Join join = paint.getStrokeJoin();
  475.     float miter = paint.getStrokeMiter();
  476.     PathEffect pathEffect = paint.getPathEffect();
  477.     Style style = paint.getStyle();
  478.     if (stroke != null) {
  479.       PathEffect effect = null;
  480.       if (stroke.getIntervals() != null) {
  481.         effect = new DashPathEffect(stroke.getIntervals(), stroke.getPhase());
  482.       }
  483.       setStroke(stroke.getCap(), stroke.getJoin(), stroke.getMiter(), Style.FILL_AND_STROKE,
  484.           effect, paint);
  485.     }
  486.     float[] points = MathHelper.getFloats(pointsList);
  487.     drawSeries(canvas, paint, points, seriesRenderer, yAxisValue, seriesIndex, startIndex);
  488.     if (isRenderPoints(seriesRenderer)) {
  489.       ScatterChart pointsChart = getPointsChart();
  490.       if (pointsChart != null) {
  491.         pointsChart.drawSeries(canvas, paint, points, seriesRenderer, yAxisValue, seriesIndex,
  492.             startIndex);
  493.       }
  494.     }
  495.     paint.setTextSize(seriesRenderer.getChartValuesTextSize());
  496.     if (or == Orientation.HORIZONTAL) {
  497.       paint.setTextAlign(Align.CENTER);
  498.     } else {
  499.       paint.setTextAlign(Align.LEFT);
  500.     }
  501.     if (seriesRenderer.isDisplayChartValues()) {
  502.       paint.setTextAlign(seriesRenderer.getChartValuesTextAlign());
  503.       drawChartValuesText(canvas, series, seriesRenderer, paint, points, seriesIndex, startIndex);
  504.     }
  505.     if (stroke != null) {
  506.       setStroke(cap, join, miter, style, pathEffect, paint);
  507.     }
  508.   }
  509.  
  510.   private void setStroke(Cap cap, Join join, float miter, Style style, PathEffect pathEffect,
  511.       Paint paint) {
  512.     paint.setStrokeCap(cap);
  513.     paint.setStrokeJoin(join);
  514.     paint.setStrokeMiter(miter);
  515.     paint.setPathEffect(pathEffect);
  516.     paint.setStyle(style);
  517.   }
  518.  
  519.   /**
  520.    * The graphical representation of the series values as text.
  521.    *
  522.    * @param canvas the canvas to paint to
  523.    * @param series the series to be painted
  524.    * @param renderer the series renderer
  525.    * @param paint the paint to be used for drawing
  526.    * @param points the array of points to be used for drawing the series
  527.    * @param seriesIndex the index of the series currently being drawn
  528.    * @param startIndex the start index of the rendering points
  529.    */
  530.   protected void drawChartValuesText(Canvas canvas, XYSeries series, SimpleSeriesRenderer renderer,
  531.       Paint paint, float[] points, int seriesIndex, int startIndex) {
  532.     if (points.length > 1) { // there are more than one point
  533.       // record the first point's position
  534.       float previousPointX = points[0];
  535.       float previousPointY = points[1];
  536.       for (int k = 0; k < points.length; k += 2) {
  537.         if (k == 2) { // decide whether to display first two points' values or not
  538.           if (Math.abs(points[2]- points[0]) > 100 || Math.abs(points[3] - points[1]) > 100) {
  539.             // first point
  540.             drawText(canvas, getLabel(series.getY(startIndex)), points[0], points[1]
  541.                 - renderer.getChartValuesSpacing(), paint, 0);
  542.             // second point
  543.             drawText(canvas, getLabel(series.getY(startIndex + 1)), points[2], points[3]
  544.                 - renderer.getChartValuesSpacing(), paint, 0);
  545.  
  546.             previousPointX = points[2];
  547.             previousPointY = points[3];
  548.           }
  549.         } else if (k > 2) {
  550.           // compare current point's position with the previous point's, if they are not too close, display
  551.           if (Math.abs(points[k]- previousPointX) > 100 || Math.abs(points[k+1] - previousPointY) > 100) {
  552.             drawText(canvas, getLabel(series.getY(startIndex + k / 2)), points[k], points[k + 1]
  553.                 - renderer.getChartValuesSpacing(), paint, 0);
  554.             previousPointX = points[k];
  555.             previousPointY = points[k+1];
  556.           }
  557.         }
  558.       }
  559.     } else { // if only one point, display it
  560.       for (int k = 0; k < points.length; k += 2) {
  561.         drawText(canvas, getLabel(series.getY(startIndex + k / 2)), points[k], points[k + 1]
  562.             - renderer.getChartValuesSpacing(), paint, 0);
  563.       }
  564.     }
  565.   }
  566.  
  567.   /**
  568.    * The graphical representation of a text, to handle both HORIZONTAL and
  569.    * VERTICAL orientations and extra rotation angles.
  570.    *
  571.    * @param canvas the canvas to paint to
  572.    * @param text the text to be rendered
  573.    * @param x the X axis location of the text
  574.    * @param y the Y axis location of the text
  575.    * @param paint the paint to be used for drawing
  576.    * @param extraAngle the text angle
  577.    */
  578.   protected void drawText(Canvas canvas, String text, float x, float y, Paint paint,
  579.       float extraAngle) {
  580.     float angle = -mRenderer.getOrientation().getAngle() + extraAngle;
  581.     if (angle != 0) {
  582.       // canvas.scale(1 / mScale, mScale);
  583.       canvas.rotate(angle, x, y);
  584.     }
  585.     drawString(canvas, text, x, y, paint);
  586.     if (angle != 0) {
  587.       canvas.rotate(-angle, x, y);
  588.       // canvas.scale(mScale, 1 / mScale);
  589.     }
  590.   }
  591.  
  592.   /**
  593.    * Transform the canvas such as it can handle both HORIZONTAL and VERTICAL
  594.    * orientations.
  595.    *
  596.    * @param canvas the canvas to paint to
  597.    * @param angle the angle of rotation
  598.    * @param inverse if the inverse transform needs to be applied
  599.    */
  600.   private void transform(Canvas canvas, float angle, boolean inverse) {
  601.     if (inverse) {
  602.       canvas.scale(1 / mScale, mScale);
  603.       canvas.translate(mTranslate, -mTranslate);
  604.       canvas.rotate(-angle, mCenter.getX(), mCenter.getY());
  605.     } else {
  606.       canvas.rotate(angle, mCenter.getX(), mCenter.getY());
  607.       canvas.translate(-mTranslate, mTranslate);
  608.       canvas.scale(mScale, 1 / mScale);
  609.     }
  610.   }
  611.  
  612.   /**
  613.    * Makes sure the fraction digit is not displayed, if not needed.
  614.    *
  615.    * @param label the input label value
  616.    * @return the label without the useless fraction digit
  617.    */
  618.   protected String getLabel(double label) {
  619.     String text = "";
  620.     if (label == Math.round(label)) {
  621.       text = Math.round(label) + "";
  622.     } else {
  623.       text = label + "";
  624.     }
  625.     return text;
  626.   }
  627.  
  628.   /**
  629.    * The graphical representation of the labels on the X axis.
  630.    *
  631.    * @param xLabels the X labels values
  632.    * @param xTextLabelLocations the X text label locations
  633.    * @param canvas the canvas to paint to
  634.    * @param paint the paint to be used for drawing
  635.    * @param left the left value of the labels area
  636.    * @param top the top value of the labels area
  637.    * @param bottom the bottom value of the labels area
  638.    * @param xPixelsPerUnit the amount of pixels per one unit in the chart labels
  639.    * @param minX the minimum value on the X axis in the chart
  640.    * @param maxX the maximum value on the X axis in the chart
  641.    */
  642.   protected void drawXLabels(List<Double> xLabels, Double[] xTextLabelLocations, Canvas canvas,
  643.       Paint paint, int left, int top, int bottom, double xPixelsPerUnit, double minX, double maxX) {
  644.     int length = xLabels.size();
  645.     boolean showLabels = mRenderer.isShowLabels();
  646.     boolean showGridY = mRenderer.isShowGridY();
  647.     for (int i = 0; i < length; i++) {
  648.       double label = xLabels.get(i);
  649.       float xLabel = (float) (left + xPixelsPerUnit * (label - minX));
  650.       if (showLabels) {
  651.         paint.setColor(mRenderer.getXLabelsColor());
  652.         canvas.drawLine(xLabel, bottom, xLabel, bottom + mRenderer.getLabelsTextSize() / 3, paint);
  653.         drawText(canvas, getLabel(label), xLabel, bottom + mRenderer.getLabelsTextSize() * 4 / 3,
  654.             paint, mRenderer.getXLabelsAngle());
  655.       }
  656.       if (showGridY) {
  657.         paint.setColor(mRenderer.getGridColor());
  658.         canvas.drawLine(xLabel, bottom, xLabel, top, paint);
  659.       }
  660.     }
  661.     drawXTextLabels(xTextLabelLocations, canvas, paint, showLabels, left, top, bottom,
  662.         xPixelsPerUnit, minX, maxX);
  663.   }
  664.  
  665.   /**
  666.    * The graphical representation of the labels on the X axis.
  667.    *
  668.    * @param xLabels the X labels values
  669.    * @param xTextLabelLocations the X text label locations
  670.    * @param canvas the canvas to paint to
  671.    * @param paint the paint to be used for drawing
  672.    * @param left the left value of the labels area
  673.    * @param top the top value of the labels area
  674.    * @param bottom the bottom value of the labels area
  675.    * @param xPixelsPerUnit the amount of pixels per one unit in the chart labels
  676.    * @param minX the minimum value on the X axis in the chart
  677.    * @param maxX the maximum value on the X axis in the chart
  678.    */
  679.   protected void drawYLabels(Map<Integer, List<Double>> allYLabels, Canvas canvas, Paint paint,
  680.       int maxScaleNumber, int left, int right, int bottom, double[] yPixelsPerUnit, double[] minY) {
  681.     Orientation or = mRenderer.getOrientation();
  682.     boolean showGridX = mRenderer.isShowGridX();
  683.     boolean showLabels = mRenderer.isShowLabels();
  684.     for (int i = 0; i < maxScaleNumber; i++) {
  685.       paint.setTextAlign(mRenderer.getYLabelsAlign(i));
  686.       List<Double> yLabels = allYLabels.get(i);
  687.       int length = yLabels.size();
  688.       for (int j = 0; j < length; j++) {
  689.         double label = yLabels.get(j);
  690.         Align axisAlign = mRenderer.getYAxisAlign(i);
  691.         boolean textLabel = mRenderer.getYTextLabel(label, i) != null;
  692.         float yLabel = (float) (bottom - yPixelsPerUnit[i] * (label - minY[i]));
  693.         if (or == Orientation.HORIZONTAL) {
  694.           if (showLabels && !textLabel) {
  695.             paint.setColor(mRenderer.getYLabelsColor(i));
  696.             if (axisAlign == Align.LEFT) {
  697.               canvas.drawLine(left + getLabelLinePos(axisAlign), yLabel, left, yLabel, paint);
  698.               drawText(canvas, getLabel(label), left, yLabel - 2, paint,
  699.                   mRenderer.getYLabelsAngle());
  700.             } else {
  701.               canvas.drawLine(right, yLabel, right + getLabelLinePos(axisAlign), yLabel, paint);
  702.               drawText(canvas, getLabel(label), right, yLabel - 2, paint,
  703.                   mRenderer.getYLabelsAngle());
  704.             }
  705.           }
  706.           if (showGridX) {
  707.             paint.setColor(mRenderer.getGridColor());
  708.             canvas.drawLine(left, yLabel, right, yLabel, paint);
  709.           }
  710.         } else if (or == Orientation.VERTICAL) {
  711.           if (showLabels && !textLabel) {
  712.             paint.setColor(mRenderer.getYLabelsColor(i));
  713.             canvas.drawLine(right - getLabelLinePos(axisAlign), yLabel, right, yLabel, paint);
  714.             drawText(canvas, getLabel(label), right + 10, yLabel - 2, paint,
  715.                 mRenderer.getYLabelsAngle());
  716.           }
  717.           if (showGridX) {
  718.             paint.setColor(mRenderer.getGridColor());
  719.             canvas.drawLine(right, yLabel, left, yLabel, paint);
  720.           }
  721.         }
  722.       }
  723.     }
  724.   }
  725.  
  726.   /**
  727.    * The graphical representation of the text labels on the X axis.
  728.    *
  729.    * @param xTextLabelLocations the X text label locations
  730.    * @param canvas the canvas to paint to
  731.    * @param paint the paint to be used for drawing
  732.    * @param left the left value of the labels area
  733.    * @param top the top value of the labels area
  734.    * @param bottom the bottom value of the labels area
  735.    * @param xPixelsPerUnit the amount of pixels per one unit in the chart labels
  736.    * @param minX the minimum value on the X axis in the chart
  737.    * @param maxX the maximum value on the X axis in the chart
  738.    */
  739.   protected void drawXTextLabels(Double[] xTextLabelLocations, Canvas canvas, Paint paint,
  740.       boolean showLabels, int left, int top, int bottom, double xPixelsPerUnit, double minX,
  741.       double maxX) {
  742.     boolean showCustomTextGrid = mRenderer.isShowCustomTextGrid();
  743.     if (showLabels) {
  744.       paint.setColor(mRenderer.getXLabelsColor());
  745.       for (Double location : xTextLabelLocations) {
  746.         if (minX <= location && location <= maxX) {
  747.           float xLabel = (float) (left + xPixelsPerUnit * (location.doubleValue() - minX));
  748.           paint.setColor(mRenderer.getXLabelsColor());
  749.           canvas
  750.           .drawLine(xLabel, bottom, xLabel, bottom + mRenderer.getLabelsTextSize() / 3, paint);
  751.           drawText(canvas, mRenderer.getXTextLabel(location), xLabel,
  752.               bottom + mRenderer.getLabelsTextSize() * 4 / 3, paint, mRenderer.getXLabelsAngle());
  753.           if (showCustomTextGrid) {
  754.             paint.setColor(mRenderer.getGridColor());
  755.             canvas.drawLine(xLabel, bottom, xLabel, top, paint);
  756.           }
  757.         }
  758.       }
  759.     }
  760.   }
  761.  
  762.   // TODO: docs
  763.   public XYMultipleSeriesRenderer getRenderer() {
  764.     return mRenderer;
  765.   }
  766.  
  767.   public XYMultipleSeriesDataset getDataset() {
  768.     return mDataset;
  769.   }
  770.  
  771.   public double[] getCalcRange(int scale) {
  772.     return mCalcRange.get(scale);
  773.   }
  774.  
  775.   public void setCalcRange(double[] range, int scale) {
  776.     mCalcRange.put(scale, range);
  777.   }
  778.  
  779.   public double[] toRealPoint(float screenX, float screenY) {
  780.     return toRealPoint(screenX, screenY, 0);
  781.   }
  782.  
  783.   public double[] toScreenPoint(double[] realPoint) {
  784.     return toScreenPoint(realPoint, 0);
  785.   }
  786.  
  787.   private int getLabelLinePos(Align align) {
  788.     int pos = 4;
  789.     if (align == Align.LEFT) {
  790.       pos = -pos;
  791.     }
  792.     return pos;
  793.   }
  794.  
  795.   /**
  796.    * Transforms a screen point to a real coordinates point.
  797.    *
  798.    * @param screenX the screen x axis value
  799.    * @param screenY the screen y axis value
  800.    * @return the real coordinates point
  801.    */
  802.   public double[] toRealPoint(float screenX, float screenY, int scale) {
  803.     double realMinX = mRenderer.getXAxisMin(scale);
  804.     double realMaxX = mRenderer.getXAxisMax(scale);
  805.     double realMinY = mRenderer.getYAxisMin(scale);
  806.     double realMaxY = mRenderer.getYAxisMax(scale);
  807.     return new double[] {
  808.         (screenX - mScreenR.left) * (realMaxX - realMinX) / mScreenR.width() + realMinX,
  809.         (mScreenR.top + mScreenR.height() - screenY) * (realMaxY - realMinY) / mScreenR.height()
  810.             + realMinY };
  811.   }
  812.  
  813.   public double[] toScreenPoint(double[] realPoint, int scale) {
  814.     double realMinX = mRenderer.getXAxisMin(scale);
  815.     double realMaxX = mRenderer.getXAxisMax(scale);
  816.     double realMinY = mRenderer.getYAxisMin(scale);
  817.     double realMaxY = mRenderer.getYAxisMax(scale);
  818.     if (!mRenderer.isMinXSet(scale) || !mRenderer.isMaxXSet(scale) || !mRenderer.isMinXSet(scale)
  819.         || !mRenderer.isMaxYSet(scale)) {
  820.       double[] calcRange = getCalcRange(scale);
  821.       realMinX = calcRange[0];
  822.       realMaxX = calcRange[1];
  823.       realMinY = calcRange[2];
  824.       realMaxY = calcRange[3];
  825.     }
  826.     return new double[] {
  827.         (realPoint[0] - realMinX) * mScreenR.width() / (realMaxX - realMinX) + mScreenR.left,
  828.         (realMaxY - realPoint[1]) * mScreenR.height() / (realMaxY - realMinY) + mScreenR.top };
  829.   }
  830.  
  831.   public SeriesSelection getSeriesAndPointForScreenCoordinate(final Point screenPoint) {
  832.     if (clickableAreas != null)
  833.       for (int seriesIndex = clickableAreas.size() - 1; seriesIndex >= 0; seriesIndex--) {
  834.         // series 0 is drawn first. Then series 1 is drawn on top, and series 2
  835.         // on top of that.
  836.         // we want to know what the user clicked on, so traverse them in the
  837.         // order they appear on the screen.
  838.         int pointIndex = 0;
  839.         if (clickableAreas.get(seriesIndex) != null) {
  840.           RectF rectangle;
  841.           for (ClickableArea area : clickableAreas.get(seriesIndex)) {
  842.             rectangle = area.getRect();
  843.             if (rectangle != null && rectangle.contains(screenPoint.getX(), screenPoint.getY())) {
  844.               return new SeriesSelection(seriesIndex, pointIndex, area.getX(), area.getY());
  845.             }
  846.             pointIndex++;
  847.           }
  848.         }
  849.       }
  850.     return super.getSeriesAndPointForScreenCoordinate(screenPoint);
  851.   }
  852.  
  853.   /**
  854.    * The graphical representation of a series.
  855.    *
  856.    * @param canvas the canvas to paint to
  857.    * @param paint the paint to be used for drawing
  858.    * @param points the array of points to be used for drawing the series
  859.    * @param seriesRenderer the series renderer
  860.    * @param yAxisValue the minimum value of the y axis
  861.    * @param seriesIndex the index of the series currently being drawn
  862.    * @param startIndex the start index of the rendering points
  863.    */
  864.   public abstract void drawSeries(Canvas canvas, Paint paint, float[] points,
  865.       SimpleSeriesRenderer seriesRenderer, float yAxisValue, int seriesIndex, int startIndex);
  866.  
  867.   /**
  868.    * Returns the clickable areas for all passed points
  869.    *
  870.    * @param points the array of points
  871.    * @param values the array of values of each point
  872.    * @param yAxisValue the minimum value of the y axis
  873.    * @param seriesIndex the index of the series to which the points belong
  874.    * @return an array of rectangles with the clickable area
  875.    * @param startIndex the start index of the rendering points
  876.    */
  877.   protected abstract ClickableArea[] clickableAreasForPoints(float[] points, double[] values,
  878.       float yAxisValue, int seriesIndex, int startIndex);
  879.  
  880.   /**
  881.    * Returns if the chart should display the null values.
  882.    *
  883.    * @return if null values should be rendered
  884.    */
  885.   protected boolean isRenderNullValues() {
  886.     return false;
  887.   }
  888.  
  889.   /**
  890.    * Returns if the chart should display the points as a certain shape.
  891.    *
  892.    * @param renderer the series renderer
  893.    */
  894.   public boolean isRenderPoints(SimpleSeriesRenderer renderer) {
  895.     return false;
  896.   }
  897.  
  898.   /**
  899.    * Returns the default axis minimum.
  900.    *
  901.    * @return the default axis minimum
  902.    */
  903.   public double getDefaultMinimum() {
  904.     return MathHelper.NULL_VALUE;
  905.   }
  906.  
  907.   /**
  908.    * Returns the scatter chart to be used for drawing the data points.
  909.    *
  910.    * @return the data points scatter chart
  911.    */
  912.   public ScatterChart getPointsChart() {
  913.     return null;
  914.   }
  915.  
  916.   /**
  917.    * Returns the chart type identifier.
  918.    *
  919.    * @return the chart type
  920.    */
  921.   public abstract String getChartType();
  922.  
  923. }