Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / android-libraries / achartengine / src / org / achartengine / chart / XYChart.java
diff --git a/android-libraries/achartengine/src/org/achartengine/chart/XYChart.java b/android-libraries/achartengine/src/org/achartengine/chart/XYChart.java
new file mode 100644 (file)
index 0000000..6b531d6
--- /dev/null
@@ -0,0 +1,905 @@
+/**\r
+ * Copyright (C) 2009 - 2012 SC 4ViewSoft SRL\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package org.achartengine.chart;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.HashMap;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.SortedMap;\r
+\r
+import org.achartengine.model.Point;\r
+import org.achartengine.model.SeriesSelection;\r
+import org.achartengine.model.XYMultipleSeriesDataset;\r
+import org.achartengine.model.XYSeries;\r
+import org.achartengine.renderer.BasicStroke;\r
+import org.achartengine.renderer.DefaultRenderer;\r
+import org.achartengine.renderer.SimpleSeriesRenderer;\r
+import org.achartengine.renderer.XYMultipleSeriesRenderer;\r
+import org.achartengine.renderer.XYMultipleSeriesRenderer.Orientation;\r
+import org.achartengine.util.MathHelper;\r
+\r
+import android.graphics.Canvas;\r
+import android.graphics.DashPathEffect;\r
+import android.graphics.Paint;\r
+import android.graphics.Paint.Align;\r
+import android.graphics.Paint.Cap;\r
+import android.graphics.Paint.Join;\r
+import android.graphics.Paint.Style;\r
+import android.graphics.PathEffect;\r
+import android.graphics.Rect;\r
+import android.graphics.RectF;\r
+import android.graphics.Typeface;\r
+\r
+/**\r
+ * The XY chart rendering class.\r
+ */\r
+public abstract class XYChart extends AbstractChart {\r
+  /** The multiple series dataset. */\r
+  protected XYMultipleSeriesDataset mDataset;\r
+  /** The multiple series renderer. */\r
+  protected XYMultipleSeriesRenderer mRenderer;\r
+  /** The current scale value. */\r
+  private float mScale;\r
+  /** The current translate value. */\r
+  private float mTranslate;\r
+  /** The canvas center point. */\r
+  private Point mCenter;\r
+  /** The visible chart area, in screen coordinates. */\r
+  private Rect mScreenR;\r
+  /** The calculated range. */\r
+  private final Map<Integer, double[]> mCalcRange = new HashMap<Integer, double[]>();\r
+\r
+  /**\r
+   * The clickable areas for all points. The array index is the series index,\r
+   * and the RectF list index is the point index in that series.\r
+   */\r
+  private Map<Integer, List<ClickableArea>> clickableAreas = new HashMap<Integer, List<ClickableArea>>();\r
+\r
+  protected XYChart() {\r
+  }\r
+\r
+  /**\r
+   * Builds a new XY chart instance.\r
+   * \r
+   * @param dataset the multiple series dataset\r
+   * @param renderer the multiple series renderer\r
+   */\r
+  public XYChart(XYMultipleSeriesDataset dataset, XYMultipleSeriesRenderer renderer) {\r
+    mDataset = dataset;\r
+    mRenderer = renderer;\r
+  }\r
+\r
+  // TODO: javadoc\r
+  protected void setDatasetRenderer(XYMultipleSeriesDataset dataset,\r
+      XYMultipleSeriesRenderer renderer) {\r
+    mDataset = dataset;\r
+    mRenderer = renderer;\r
+  }\r
+\r
+  /**\r
+   * The graphical representation of the XY chart.\r
+   * \r
+   * @param canvas the canvas to paint to\r
+   * @param x the top left x value of the view to draw to\r
+   * @param y the top left y value of the view to draw to\r
+   * @param width the width of the view to draw to\r
+   * @param height the height of the view to draw to\r
+   * @param paint the paint\r
+   */\r
+  public void draw(Canvas canvas, int x, int y, int width, int height, Paint paint) {\r
+    paint.setAntiAlias(mRenderer.isAntialiasing());\r
+    int legendSize = getLegendSize(mRenderer, height / 5, mRenderer.getAxisTitleTextSize());\r
+    int[] margins = mRenderer.getMargins();\r
+    int left = x + margins[1];\r
+    int top = y + margins[0];\r
+    int right = x + width - margins[3];\r
+    int sLength = mDataset.getSeriesCount();\r
+    String[] titles = new String[sLength];\r
+    for (int i = 0; i < sLength; i++) {\r
+      titles[i] = mDataset.getSeriesAt(i).getTitle();\r
+    }\r
+    if (mRenderer.isFitLegend() && mRenderer.isShowLegend()) {\r
+      legendSize = drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize,\r
+          paint, true);\r
+    }\r
+    int bottom = y + height - margins[2] - legendSize;\r
+    if (mScreenR == null) {\r
+      mScreenR = new Rect();\r
+    }\r
+    mScreenR.set(left, top, right, bottom);\r
+    drawBackground(mRenderer, canvas, x, y, width, height, paint, false, DefaultRenderer.NO_COLOR);\r
+\r
+    if (paint.getTypeface() == null\r
+        || !paint.getTypeface().toString().equals(mRenderer.getTextTypefaceName())\r
+        || paint.getTypeface().getStyle() != mRenderer.getTextTypefaceStyle()) {\r
+      paint.setTypeface(Typeface.create(mRenderer.getTextTypefaceName(),\r
+          mRenderer.getTextTypefaceStyle()));\r
+    }\r
+    Orientation or = mRenderer.getOrientation();\r
+    if (or == Orientation.VERTICAL) {\r
+      right -= legendSize;\r
+      bottom += legendSize - 20;\r
+    }\r
+    int angle = or.getAngle();\r
+    boolean rotate = angle == 90;\r
+    mScale = (float) (height) / width;\r
+    mTranslate = Math.abs(width - height) / 2;\r
+    if (mScale < 1) {\r
+      mTranslate *= -1;\r
+    }\r
+    mCenter = new Point((x + width) / 2, (y + height) / 2);\r
+    if (rotate) {\r
+      transform(canvas, angle, false);\r
+    }\r
+\r
+    int maxScaleNumber = -Integer.MAX_VALUE;\r
+    for (int i = 0; i < sLength; i++) {\r
+      maxScaleNumber = Math.max(maxScaleNumber, mDataset.getSeriesAt(i).getScaleNumber());\r
+    }\r
+    maxScaleNumber++;\r
+    if (maxScaleNumber < 0) {\r
+      return;\r
+    }\r
+    double[] minX = new double[maxScaleNumber];\r
+    double[] maxX = new double[maxScaleNumber];\r
+    double[] minY = new double[maxScaleNumber];\r
+    double[] maxY = new double[maxScaleNumber];\r
+    boolean[] isMinXSet = new boolean[maxScaleNumber];\r
+    boolean[] isMaxXSet = new boolean[maxScaleNumber];\r
+    boolean[] isMinYSet = new boolean[maxScaleNumber];\r
+    boolean[] isMaxYSet = new boolean[maxScaleNumber];\r
+\r
+    for (int i = 0; i < maxScaleNumber; i++) {\r
+      minX[i] = mRenderer.getXAxisMin(i);\r
+      maxX[i] = mRenderer.getXAxisMax(i);\r
+      minY[i] = mRenderer.getYAxisMin(i);\r
+      maxY[i] = mRenderer.getYAxisMax(i);\r
+      isMinXSet[i] = mRenderer.isMinXSet(i);\r
+      isMaxXSet[i] = mRenderer.isMaxXSet(i);\r
+      isMinYSet[i] = mRenderer.isMinYSet(i);\r
+      isMaxYSet[i] = mRenderer.isMaxYSet(i);\r
+      if (mCalcRange.get(i) == null) {\r
+        mCalcRange.put(i, new double[4]);\r
+      }\r
+    }\r
+    double[] xPixelsPerUnit = new double[maxScaleNumber];\r
+    double[] yPixelsPerUnit = new double[maxScaleNumber];\r
+    for (int i = 0; i < sLength; i++) {\r
+      XYSeries series = mDataset.getSeriesAt(i);\r
+      int scale = series.getScaleNumber();\r
+      if (series.getItemCount() == 0) {\r
+        continue;\r
+      }\r
+      if (!isMinXSet[scale]) {\r
+        double minimumX = series.getMinX();\r
+        minX[scale] = Math.min(minX[scale], minimumX);\r
+        mCalcRange.get(scale)[0] = minX[scale];\r
+      }\r
+      if (!isMaxXSet[scale]) {\r
+        double maximumX = series.getMaxX();\r
+        maxX[scale] = Math.max(maxX[scale], maximumX);\r
+        mCalcRange.get(scale)[1] = maxX[scale];\r
+      }\r
+      if (!isMinYSet[scale]) {\r
+        double minimumY = series.getMinY();\r
+        minY[scale] = Math.min(minY[scale], (float) minimumY);\r
+        mCalcRange.get(scale)[2] = minY[scale];\r
+      }\r
+      if (!isMaxYSet[scale]) {\r
+        double maximumY = series.getMaxY();\r
+        maxY[scale] = Math.max(maxY[scale], (float) maximumY);\r
+        mCalcRange.get(scale)[3] = maxY[scale];\r
+      }\r
+    }\r
+    for (int i = 0; i < maxScaleNumber; i++) {\r
+      if (maxX[i] - minX[i] != 0) {\r
+        xPixelsPerUnit[i] = (right - left) / (maxX[i] - minX[i]);\r
+      }\r
+      if (maxY[i] - minY[i] != 0) {\r
+        yPixelsPerUnit[i] = (float) ((bottom - top) / (maxY[i] - minY[i]));\r
+      }\r
+    }\r
+\r
+    boolean hasValues = false;\r
+    // use a linked list for these reasons:\r
+    // 1) Avoid a large contiguous memory allocation\r
+    // 2) We don't need random seeking, only sequential reading/writing, so\r
+    // linked list makes sense\r
+    clickableAreas = new HashMap<Integer, List<ClickableArea>>();\r
+    for (int i = 0; i < sLength; i++) {\r
+      XYSeries series = mDataset.getSeriesAt(i);\r
+      int scale = series.getScaleNumber();\r
+      if (series.getItemCount() == 0) {\r
+        continue;\r
+      }\r
+\r
+      hasValues = true;\r
+      SimpleSeriesRenderer seriesRenderer = mRenderer.getSeriesRendererAt(i);\r
+\r
+      // int originalValuesLength = series.getItemCount();\r
+      // int valuesLength = originalValuesLength;\r
+      // int length = valuesLength * 2;\r
+\r
+      List<Float> points = new ArrayList<Float>();\r
+      List<Double> values = new ArrayList<Double>();\r
+      float yAxisValue = Math.min(bottom, (float) (bottom + yPixelsPerUnit[scale] * minY[scale]));\r
+      LinkedList<ClickableArea> clickableArea = new LinkedList<ClickableArea>();\r
+\r
+      clickableAreas.put(i, clickableArea);\r
+\r
+      SortedMap<Double, Double> range = series.getRange(minX[scale], maxX[scale], 1);\r
+      int startIndex = -1;\r
+\r
+      for (Entry<Double, Double> value : range.entrySet()) {\r
+\r
+        double xValue = value.getKey();\r
+        double yValue = value.getValue();\r
+        if (startIndex < 0) {\r
+          startIndex = series.getIndexForKey(xValue);\r
+        }\r
+\r
+        // points.add((float) (left + xPixelsPerUnit[scale]\r
+        // * (value.getKey().floatValue() - minX[scale])));\r
+        // points.add((float) (bottom - yPixelsPerUnit[scale]\r
+        // * (value.getValue().floatValue() - minY[scale])));\r
+        values.add(value.getKey());\r
+        values.add(value.getValue());\r
+\r
+        if (!isNullValue(yValue)) {\r
+          points.add((float) (left + xPixelsPerUnit[scale] * (xValue - minX[scale])));\r
+          points.add((float) (bottom - yPixelsPerUnit[scale] * (yValue - minY[scale])));\r
+        } else if (isRenderNullValues()) {\r
+          points.add((float) (left + xPixelsPerUnit[scale] * (xValue - minX[scale])));\r
+          points.add((float) (bottom - yPixelsPerUnit[scale] * (-minY[scale])));\r
+        } else {\r
+          if (points.size() > 0) {\r
+            drawSeries(series, canvas, paint, points, seriesRenderer, yAxisValue, i, or, startIndex);\r
+            ClickableArea[] clickableAreasForSubSeries = clickableAreasForPoints(\r
+                MathHelper.getFloats(points), MathHelper.getDoubles(values), yAxisValue, i,\r
+                startIndex);\r
+            clickableArea.addAll(Arrays.asList(clickableAreasForSubSeries));\r
+            points.clear();\r
+            values.clear();\r
+          }\r
+          clickableArea.add(null);\r
+        }\r
+      }\r
+\r
+      if (points.size() > 0) {\r
+        drawSeries(series, canvas, paint, points, seriesRenderer, yAxisValue, i, or, startIndex);\r
+        ClickableArea[] clickableAreasForSubSeries = clickableAreasForPoints(\r
+            MathHelper.getFloats(points), MathHelper.getDoubles(values), yAxisValue, i, startIndex);\r
+        clickableArea.addAll(Arrays.asList(clickableAreasForSubSeries));\r
+      }\r
+    }\r
+\r
+    // draw stuff over the margins such as data doesn't render on these areas\r
+    drawBackground(mRenderer, canvas, x, bottom, width, height - bottom, paint, true,\r
+        mRenderer.getMarginsColor());\r
+    drawBackground(mRenderer, canvas, x, y, width, margins[0], paint, true,\r
+        mRenderer.getMarginsColor());\r
+    if (or == Orientation.HORIZONTAL) {\r
+      drawBackground(mRenderer, canvas, x, y, left - x, height - y, paint, true,\r
+          mRenderer.getMarginsColor());\r
+      drawBackground(mRenderer, canvas, right, y, margins[3], height - y, paint, true,\r
+          mRenderer.getMarginsColor());\r
+    } else if (or == Orientation.VERTICAL) {\r
+      drawBackground(mRenderer, canvas, right, y, width - right, height - y, paint, true,\r
+          mRenderer.getMarginsColor());\r
+      drawBackground(mRenderer, canvas, x, y, left - x, height - y, paint, true,\r
+          mRenderer.getMarginsColor());\r
+    }\r
+\r
+    boolean showLabels = mRenderer.isShowLabels() && hasValues;\r
+    boolean showGridX = mRenderer.isShowGridX();\r
+    boolean showCustomTextGrid = mRenderer.isShowCustomTextGrid();\r
+    if (showLabels || showGridX) {\r
+      List<Double> xLabels = getValidLabels(getXLabels(minX[0], maxX[0], mRenderer.getXLabels()));\r
+      Map<Integer, List<Double>> allYLabels = getYLabels(minY, maxY, maxScaleNumber);\r
+\r
+      int xLabelsLeft = left;\r
+      if (showLabels) {\r
+        paint.setColor(mRenderer.getXLabelsColor());\r
+        paint.setTextSize(mRenderer.getLabelsTextSize());\r
+        paint.setTextAlign(mRenderer.getXLabelsAlign());\r
+        if (mRenderer.getXLabelsAlign() == Align.LEFT) {\r
+          xLabelsLeft += mRenderer.getLabelsTextSize() / 4;\r
+        }\r
+      }\r
+      drawXLabels(xLabels, mRenderer.getXTextLabelLocations(), canvas, paint, xLabelsLeft, top,\r
+          bottom, xPixelsPerUnit[0], minX[0], maxX[0]);\r
+      drawYLabels(allYLabels, canvas, paint, maxScaleNumber, left, right, bottom, yPixelsPerUnit,\r
+          minY);\r
+\r
+      if (showLabels) {\r
+        paint.setColor(mRenderer.getLabelsColor());\r
+        for (int i = 0; i < maxScaleNumber; i++) {\r
+          Align axisAlign = mRenderer.getYAxisAlign(i);\r
+          Double[] yTextLabelLocations = mRenderer.getYTextLabelLocations(i);\r
+          for (Double location : yTextLabelLocations) {\r
+            if (minY[i] <= location && location <= maxY[i]) {\r
+              float yLabel = (float) (bottom - yPixelsPerUnit[i]\r
+                  * (location.doubleValue() - minY[i]));\r
+              String label = mRenderer.getYTextLabel(location, i);\r
+              paint.setColor(mRenderer.getYLabelsColor(i));\r
+              paint.setTextAlign(mRenderer.getYLabelsAlign(i));\r
+              if (or == Orientation.HORIZONTAL) {\r
+                if (axisAlign == Align.LEFT) {\r
+                  canvas.drawLine(left + getLabelLinePos(axisAlign), yLabel, left, yLabel, paint);\r
+                  drawText(canvas, label, left, yLabel - 2, paint, mRenderer.getYLabelsAngle());\r
+                } else {\r
+                  canvas.drawLine(right, yLabel, right + getLabelLinePos(axisAlign), yLabel, paint);\r
+                  drawText(canvas, label, right, yLabel - 2, paint, mRenderer.getYLabelsAngle());\r
+                }\r
+                \r
+                if (showCustomTextGrid) {\r
+                  paint.setColor(mRenderer.getGridColor());\r
+                  canvas.drawLine(left, yLabel, right, yLabel, paint);\r
+                }\r
+              } else {\r
+                canvas.drawLine(right - getLabelLinePos(axisAlign), yLabel, right, yLabel, paint);\r
+                drawText(canvas, label, right + 10, yLabel - 2, paint, mRenderer.getYLabelsAngle());\r
+                if (showCustomTextGrid) {\r
+                  paint.setColor(mRenderer.getGridColor());\r
+                  canvas.drawLine(right, yLabel, left, yLabel, paint);\r
+                }\r
+              }\r
+            }\r
+          }\r
+        }\r
+      }\r
+\r
+      if (showLabels) {\r
+        paint.setColor(mRenderer.getLabelsColor());\r
+        float size = mRenderer.getAxisTitleTextSize();\r
+        paint.setTextSize(size);\r
+        paint.setTextAlign(Align.CENTER);\r
+        if (or == Orientation.HORIZONTAL) {\r
+          drawText(canvas, mRenderer.getXTitle(), x + width / 2,\r
+              bottom + mRenderer.getLabelsTextSize() * 4 / 3 + size, paint, 0);\r
+          for (int i = 0; i < maxScaleNumber; i++) {\r
+            Align axisAlign = mRenderer.getYAxisAlign(i);\r
+            if (axisAlign == Align.LEFT) {\r
+              drawText(canvas, mRenderer.getYTitle(i), x + size, y + height / 2, paint, -90);\r
+            } else {\r
+              drawText(canvas, mRenderer.getYTitle(i), x + width, y + height / 2, paint, -90);\r
+            }\r
+          }\r
+          paint.setTextSize(mRenderer.getChartTitleTextSize());\r
+          drawText(canvas, mRenderer.getChartTitle(), x + width / 2,\r
+              y + mRenderer.getChartTitleTextSize(), paint, 0);\r
+        } else if (or == Orientation.VERTICAL) {\r
+          drawText(canvas, mRenderer.getXTitle(), x + width / 2, y + height - size, paint, -90);\r
+          drawText(canvas, mRenderer.getYTitle(), right + 20, y + height / 2, paint, 0);\r
+          paint.setTextSize(mRenderer.getChartTitleTextSize());\r
+          drawText(canvas, mRenderer.getChartTitle(), x + size, top + height / 2, paint, 0);\r
+        }\r
+      }\r
+    }\r
+    if (or == Orientation.HORIZONTAL) {\r
+      drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize, paint, false);\r
+    } else if (or == Orientation.VERTICAL) {\r
+      transform(canvas, angle, true);\r
+      drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize, paint, false);\r
+      transform(canvas, angle, false);\r
+    }\r
+    if (mRenderer.isShowAxes()) {\r
+      paint.setColor(mRenderer.getAxesColor());\r
+      canvas.drawLine(left, bottom, right, bottom, paint);\r
+      boolean rightAxis = false;\r
+      for (int i = 0; i < maxScaleNumber && !rightAxis; i++) {\r
+        rightAxis = mRenderer.getYAxisAlign(i) == Align.RIGHT;\r
+      }\r
+      if (or == Orientation.HORIZONTAL) {\r
+        canvas.drawLine(left, top, left, bottom, paint);\r
+        if (rightAxis) {\r
+          canvas.drawLine(right, top, right, bottom, paint);\r
+        }\r
+      } else if (or == Orientation.VERTICAL) {\r
+        canvas.drawLine(right, top, right, bottom, paint);\r
+      }\r
+    }\r
+    if (rotate) {\r
+      transform(canvas, angle, true);\r
+    }\r
+  }\r
+\r
+  protected List<Double> getXLabels(double min, double max, int count) {\r
+    return MathHelper.getLabels(min, max, count);\r
+  }\r
+\r
+  protected Map<Integer, List<Double>> getYLabels(double[] minY, double[] maxY, int maxScaleNumber) {\r
+    Map<Integer, List<Double>> allYLabels = new HashMap<Integer, List<Double>>();\r
+    for (int i = 0; i < maxScaleNumber; i++) {\r
+      allYLabels.put(i,\r
+          getValidLabels(MathHelper.getLabels(minY[i], maxY[i], mRenderer.getYLabels())));\r
+    }\r
+    return allYLabels;\r
+  }\r
+\r
+  protected Rect getScreenR() {\r
+    return mScreenR;\r
+  }\r
+\r
+  protected void setScreenR(Rect screenR) {\r
+    mScreenR = screenR;\r
+  }\r
+\r
+  private List<Double> getValidLabels(List<Double> labels) {\r
+    List<Double> result = new ArrayList<Double>(labels);\r
+    for (Double label : labels) {\r
+      if (label.isNaN()) {\r
+        result.remove(label);\r
+      }\r
+    }\r
+    return result;\r
+  }\r
+\r
+  /**\r
+   * Draws the series.\r
+   * \r
+   * @param series the series\r
+   * @param canvas the canvas\r
+   * @param paint the paint object\r
+   * @param pointsList the points to be rendered\r
+   * @param seriesRenderer the series renderer\r
+   * @param yAxisValue the y axis value in pixels\r
+   * @param seriesIndex the series index\r
+   * @param or the orientation\r
+   * @param startIndex the start index of the rendering points\r
+   */\r
+  protected void drawSeries(XYSeries series, Canvas canvas, Paint paint, List<Float> pointsList,\r
+      SimpleSeriesRenderer seriesRenderer, float yAxisValue, int seriesIndex, Orientation or,\r
+      int startIndex) {\r
+    BasicStroke stroke = seriesRenderer.getStroke();\r
+    Cap cap = paint.getStrokeCap();\r
+    Join join = paint.getStrokeJoin();\r
+    float miter = paint.getStrokeMiter();\r
+    PathEffect pathEffect = paint.getPathEffect();\r
+    Style style = paint.getStyle();\r
+    if (stroke != null) {\r
+      PathEffect effect = null;\r
+      if (stroke.getIntervals() != null) {\r
+        effect = new DashPathEffect(stroke.getIntervals(), stroke.getPhase());\r
+      }\r
+      setStroke(stroke.getCap(), stroke.getJoin(), stroke.getMiter(), Style.FILL_AND_STROKE,\r
+          effect, paint);\r
+    }\r
+    float[] points = MathHelper.getFloats(pointsList);\r
+    drawSeries(canvas, paint, points, seriesRenderer, yAxisValue, seriesIndex, startIndex);\r
+    if (isRenderPoints(seriesRenderer)) {\r
+      ScatterChart pointsChart = getPointsChart();\r
+      if (pointsChart != null) {\r
+        pointsChart.drawSeries(canvas, paint, points, seriesRenderer, yAxisValue, seriesIndex,\r
+            startIndex);\r
+      }\r
+    }\r
+    paint.setTextSize(seriesRenderer.getChartValuesTextSize());\r
+    if (or == Orientation.HORIZONTAL) {\r
+      paint.setTextAlign(Align.CENTER);\r
+    } else {\r
+      paint.setTextAlign(Align.LEFT);\r
+    }\r
+    if (seriesRenderer.isDisplayChartValues()) {\r
+      paint.setTextAlign(seriesRenderer.getChartValuesTextAlign());\r
+      drawChartValuesText(canvas, series, seriesRenderer, paint, points, seriesIndex, startIndex);\r
+    }\r
+    if (stroke != null) {\r
+      setStroke(cap, join, miter, style, pathEffect, paint);\r
+    }\r
+  }\r
+\r
+  private void setStroke(Cap cap, Join join, float miter, Style style, PathEffect pathEffect,\r
+      Paint paint) {\r
+    paint.setStrokeCap(cap);\r
+    paint.setStrokeJoin(join);\r
+    paint.setStrokeMiter(miter);\r
+    paint.setPathEffect(pathEffect);\r
+    paint.setStyle(style);\r
+  }\r
+\r
+  /**\r
+   * The graphical representation of the series values as text.\r
+   * \r
+   * @param canvas the canvas to paint to\r
+   * @param series the series to be painted\r
+   * @param renderer the series renderer\r
+   * @param paint the paint to be used for drawing\r
+   * @param points the array of points to be used for drawing the series\r
+   * @param seriesIndex the index of the series currently being drawn\r
+   * @param startIndex the start index of the rendering points\r
+   */\r
+  protected void drawChartValuesText(Canvas canvas, XYSeries series, SimpleSeriesRenderer renderer,\r
+      Paint paint, float[] points, int seriesIndex, int startIndex) {\r
+    if (points.length > 1) { // there are more than one point\r
+      // record the first point's position\r
+      float previousPointX = points[0];\r
+      float previousPointY = points[1];\r
+      for (int k = 0; k < points.length; k += 2) {\r
+        if (k == 2) { // decide whether to display first two points' values or not\r
+          if (Math.abs(points[2]- points[0]) > 100 || Math.abs(points[3] - points[1]) > 100) {\r
+            // first point\r
+            drawText(canvas, getLabel(series.getY(startIndex)), points[0], points[1]\r
+                - renderer.getChartValuesSpacing(), paint, 0);\r
+            // second point\r
+            drawText(canvas, getLabel(series.getY(startIndex + 1)), points[2], points[3]\r
+                - renderer.getChartValuesSpacing(), paint, 0);\r
+\r
+            previousPointX = points[2];\r
+            previousPointY = points[3];\r
+          }\r
+        } else if (k > 2) {\r
+          // compare current point's position with the previous point's, if they are not too close, display\r
+          if (Math.abs(points[k]- previousPointX) > 100 || Math.abs(points[k+1] - previousPointY) > 100) {\r
+            drawText(canvas, getLabel(series.getY(startIndex + k / 2)), points[k], points[k + 1]\r
+                - renderer.getChartValuesSpacing(), paint, 0);\r
+            previousPointX = points[k];\r
+            previousPointY = points[k+1];\r
+          }\r
+        }\r
+      }\r
+    } else { // if only one point, display it\r
+      for (int k = 0; k < points.length; k += 2) {\r
+        drawText(canvas, getLabel(series.getY(startIndex + k / 2)), points[k], points[k + 1]\r
+            - renderer.getChartValuesSpacing(), paint, 0);\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * The graphical representation of a text, to handle both HORIZONTAL and\r
+   * VERTICAL orientations and extra rotation angles.\r
+   * \r
+   * @param canvas the canvas to paint to\r
+   * @param text the text to be rendered\r
+   * @param x the X axis location of the text\r
+   * @param y the Y axis location of the text\r
+   * @param paint the paint to be used for drawing\r
+   * @param extraAngle the text angle\r
+   */\r
+  protected void drawText(Canvas canvas, String text, float x, float y, Paint paint,\r
+      float extraAngle) {\r
+    float angle = -mRenderer.getOrientation().getAngle() + extraAngle;\r
+    if (angle != 0) {\r
+      // canvas.scale(1 / mScale, mScale);\r
+      canvas.rotate(angle, x, y);\r
+    }\r
+    drawString(canvas, text, x, y, paint);\r
+    if (angle != 0) {\r
+      canvas.rotate(-angle, x, y);\r
+      // canvas.scale(mScale, 1 / mScale);\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Transform the canvas such as it can handle both HORIZONTAL and VERTICAL\r
+   * orientations.\r
+   * \r
+   * @param canvas the canvas to paint to\r
+   * @param angle the angle of rotation\r
+   * @param inverse if the inverse transform needs to be applied\r
+   */\r
+  private void transform(Canvas canvas, float angle, boolean inverse) {\r
+    if (inverse) {\r
+      canvas.scale(1 / mScale, mScale);\r
+      canvas.translate(mTranslate, -mTranslate);\r
+      canvas.rotate(-angle, mCenter.getX(), mCenter.getY());\r
+    } else {\r
+      canvas.rotate(angle, mCenter.getX(), mCenter.getY());\r
+      canvas.translate(-mTranslate, mTranslate);\r
+      canvas.scale(mScale, 1 / mScale);\r
+    }\r
+  }\r
+\r
+  /**\r
+   * The graphical representation of the labels on the X axis.\r
+   * \r
+   * @param xLabels the X labels values\r
+   * @param xTextLabelLocations the X text label locations\r
+   * @param canvas the canvas to paint to\r
+   * @param paint the paint to be used for drawing\r
+   * @param left the left value of the labels area\r
+   * @param top the top value of the labels area\r
+   * @param bottom the bottom value of the labels area\r
+   * @param xPixelsPerUnit the amount of pixels per one unit in the chart labels\r
+   * @param minX the minimum value on the X axis in the chart\r
+   * @param maxX the maximum value on the X axis in the chart\r
+   */\r
+  protected void drawXLabels(List<Double> xLabels, Double[] xTextLabelLocations, Canvas canvas,\r
+      Paint paint, int left, int top, int bottom, double xPixelsPerUnit, double minX, double maxX) {\r
+    int length = xLabels.size();\r
+    boolean showLabels = mRenderer.isShowLabels();\r
+    boolean showGridY = mRenderer.isShowGridY();\r
+    for (int i = 0; i < length; i++) {\r
+      double label = xLabels.get(i);\r
+      float xLabel = (float) (left + xPixelsPerUnit * (label - minX));\r
+      if (showLabels) {\r
+        paint.setColor(mRenderer.getXLabelsColor());\r
+        canvas.drawLine(xLabel, bottom, xLabel, bottom + mRenderer.getLabelsTextSize() / 3, paint);\r
+        drawText(canvas, getLabel(label), xLabel, bottom + mRenderer.getLabelsTextSize() * 4 / 3,\r
+            paint, mRenderer.getXLabelsAngle());\r
+      }\r
+      if (showGridY) {\r
+        paint.setColor(mRenderer.getGridColor());\r
+        canvas.drawLine(xLabel, bottom, xLabel, top, paint);\r
+      }\r
+    }\r
+    drawXTextLabels(xTextLabelLocations, canvas, paint, showLabels, left, top, bottom,\r
+        xPixelsPerUnit, minX, maxX);\r
+  }\r
+\r
+  /**\r
+   * The graphical representation of the labels on the X axis.\r
+   * \r
+   * @param allYLabels the Y labels values\r
+   * @param canvas the canvas to paint to\r
+   * @param paint the paint to be used for drawing\r
+   * @param maxScaleNumber the maximum scale number\r
+   * @param left the left value of the labels area\r
+   * @param right the right value of the labels area\r
+   * @param bottom the bottom value of the labels area\r
+   * @param yPixelsPerUnit the amount of pixels per one unit in the chart labels\r
+   * @param minY the minimum value on the Y axis in the chart\r
+   */\r
+  protected void drawYLabels(Map<Integer, List<Double>> allYLabels, Canvas canvas, Paint paint,\r
+      int maxScaleNumber, int left, int right, int bottom, double[] yPixelsPerUnit, double[] minY) {\r
+    Orientation or = mRenderer.getOrientation();\r
+    boolean showGridX = mRenderer.isShowGridX();\r
+    boolean showLabels = mRenderer.isShowLabels();\r
+    for (int i = 0; i < maxScaleNumber; i++) {\r
+      paint.setTextAlign(mRenderer.getYLabelsAlign(i));\r
+      List<Double> yLabels = allYLabels.get(i);\r
+      int length = yLabels.size();\r
+      for (int j = 0; j < length; j++) {\r
+        double label = yLabels.get(j);\r
+        Align axisAlign = mRenderer.getYAxisAlign(i);\r
+        boolean textLabel = mRenderer.getYTextLabel(label, i) != null;\r
+        float yLabel = (float) (bottom - yPixelsPerUnit[i] * (label - minY[i]));\r
+        if (or == Orientation.HORIZONTAL) {\r
+          if (showLabels && !textLabel) {\r
+            paint.setColor(mRenderer.getYLabelsColor(i));\r
+            if (axisAlign == Align.LEFT) {\r
+              canvas.drawLine(left + getLabelLinePos(axisAlign), yLabel, left, yLabel, paint);\r
+              drawText(canvas, getLabel(label), left, yLabel - 2, paint,\r
+                  mRenderer.getYLabelsAngle());\r
+            } else {\r
+              canvas.drawLine(right, yLabel, right + getLabelLinePos(axisAlign), yLabel, paint);\r
+              drawText(canvas, getLabel(label), right, yLabel - 2, paint,\r
+                  mRenderer.getYLabelsAngle());\r
+            }\r
+          }\r
+          if (showGridX) {\r
+            paint.setColor(mRenderer.getGridColor());\r
+            canvas.drawLine(left, yLabel, right, yLabel, paint);\r
+          }\r
+        } else if (or == Orientation.VERTICAL) {\r
+          if (showLabels && !textLabel) {\r
+            paint.setColor(mRenderer.getYLabelsColor(i));\r
+            canvas.drawLine(right - getLabelLinePos(axisAlign), yLabel, right, yLabel, paint);\r
+            drawText(canvas, getLabel(label), right + 10, yLabel - 2, paint,\r
+                mRenderer.getYLabelsAngle());\r
+          }\r
+          if (showGridX) {\r
+            paint.setColor(mRenderer.getGridColor());\r
+            canvas.drawLine(right, yLabel, left, yLabel, paint);\r
+          }\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * The graphical representation of the text labels on the X axis.\r
+   * \r
+   * @param xTextLabelLocations the X text label locations\r
+   * @param canvas the canvas to paint to\r
+   * @param paint the paint to be used for drawing\r
+   * @param left the left value of the labels area\r
+   * @param top the top value of the labels area\r
+   * @param bottom the bottom value of the labels area\r
+   * @param xPixelsPerUnit the amount of pixels per one unit in the chart labels\r
+   * @param minX the minimum value on the X axis in the chart\r
+   * @param maxX the maximum value on the X axis in the chart\r
+   */\r
+  protected void drawXTextLabels(Double[] xTextLabelLocations, Canvas canvas, Paint paint,\r
+      boolean showLabels, int left, int top, int bottom, double xPixelsPerUnit, double minX,\r
+      double maxX) {\r
+    boolean showCustomTextGrid = mRenderer.isShowCustomTextGrid();\r
+    if (showLabels) {\r
+      paint.setColor(mRenderer.getXLabelsColor());\r
+      for (Double location : xTextLabelLocations) {\r
+        if (minX <= location && location <= maxX) {\r
+          float xLabel = (float) (left + xPixelsPerUnit * (location.doubleValue() - minX));\r
+          paint.setColor(mRenderer.getXLabelsColor());\r
+          canvas\r
+          .drawLine(xLabel, bottom, xLabel, bottom + mRenderer.getLabelsTextSize() / 3, paint);\r
+          drawText(canvas, mRenderer.getXTextLabel(location), xLabel,\r
+              bottom + mRenderer.getLabelsTextSize() * 4 / 3, paint, mRenderer.getXLabelsAngle());\r
+          if (showCustomTextGrid) {\r
+            paint.setColor(mRenderer.getGridColor());\r
+            canvas.drawLine(xLabel, bottom, xLabel, top, paint);\r
+          }\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  // TODO: docs\r
+  public XYMultipleSeriesRenderer getRenderer() {\r
+    return mRenderer;\r
+  }\r
+\r
+  public XYMultipleSeriesDataset getDataset() {\r
+    return mDataset;\r
+  }\r
+\r
+  public double[] getCalcRange(int scale) {\r
+    return mCalcRange.get(scale);\r
+  }\r
+\r
+  public void setCalcRange(double[] range, int scale) {\r
+    mCalcRange.put(scale, range);\r
+  }\r
+\r
+  public double[] toRealPoint(float screenX, float screenY) {\r
+    return toRealPoint(screenX, screenY, 0);\r
+  }\r
+\r
+  public double[] toScreenPoint(double[] realPoint) {\r
+    return toScreenPoint(realPoint, 0);\r
+  }\r
+\r
+  private int getLabelLinePos(Align align) {\r
+    int pos = 4;\r
+    if (align == Align.LEFT) {\r
+      pos = -pos;\r
+    }\r
+    return pos;\r
+  }\r
+\r
+  /**\r
+   * Transforms a screen point to a real coordinates point.\r
+   * \r
+   * @param screenX the screen x axis value\r
+   * @param screenY the screen y axis value\r
+   * @return the real coordinates point\r
+   */\r
+  public double[] toRealPoint(float screenX, float screenY, int scale) {\r
+    double realMinX = mRenderer.getXAxisMin(scale);\r
+    double realMaxX = mRenderer.getXAxisMax(scale);\r
+    double realMinY = mRenderer.getYAxisMin(scale);\r
+    double realMaxY = mRenderer.getYAxisMax(scale);\r
+    return new double[] {\r
+        (screenX - mScreenR.left) * (realMaxX - realMinX) / mScreenR.width() + realMinX,\r
+        (mScreenR.top + mScreenR.height() - screenY) * (realMaxY - realMinY) / mScreenR.height()\r
+            + realMinY };\r
+  }\r
+\r
+  public double[] toScreenPoint(double[] realPoint, int scale) {\r
+    double realMinX = mRenderer.getXAxisMin(scale);\r
+    double realMaxX = mRenderer.getXAxisMax(scale);\r
+    double realMinY = mRenderer.getYAxisMin(scale);\r
+    double realMaxY = mRenderer.getYAxisMax(scale);\r
+    if (!mRenderer.isMinXSet(scale) || !mRenderer.isMaxXSet(scale) || !mRenderer.isMinXSet(scale)\r
+        || !mRenderer.isMaxYSet(scale)) {\r
+      double[] calcRange = getCalcRange(scale);\r
+      realMinX = calcRange[0];\r
+      realMaxX = calcRange[1];\r
+      realMinY = calcRange[2];\r
+      realMaxY = calcRange[3];\r
+    }\r
+    return new double[] {\r
+        (realPoint[0] - realMinX) * mScreenR.width() / (realMaxX - realMinX) + mScreenR.left,\r
+        (realMaxY - realPoint[1]) * mScreenR.height() / (realMaxY - realMinY) + mScreenR.top };\r
+  }\r
+\r
+  public SeriesSelection getSeriesAndPointForScreenCoordinate(final Point screenPoint) {\r
+    if (clickableAreas != null)\r
+      for (int seriesIndex = clickableAreas.size() - 1; seriesIndex >= 0; seriesIndex--) {\r
+        // series 0 is drawn first. Then series 1 is drawn on top, and series 2\r
+        // on top of that.\r
+        // we want to know what the user clicked on, so traverse them in the\r
+        // order they appear on the screen.\r
+        int pointIndex = 0;\r
+        if (clickableAreas.get(seriesIndex) != null) {\r
+          RectF rectangle;\r
+          for (ClickableArea area : clickableAreas.get(seriesIndex)) {\r
+            rectangle = area.getRect();\r
+            if (rectangle != null && rectangle.contains(screenPoint.getX(), screenPoint.getY())) {\r
+              return new SeriesSelection(seriesIndex, pointIndex, area.getX(), area.getY());\r
+            }\r
+            pointIndex++;\r
+          }\r
+        }\r
+      }\r
+    return super.getSeriesAndPointForScreenCoordinate(screenPoint);\r
+  }\r
+\r
+  /**\r
+   * The graphical representation of a series.\r
+   * \r
+   * @param canvas the canvas to paint to\r
+   * @param paint the paint to be used for drawing\r
+   * @param points the array of points to be used for drawing the series\r
+   * @param seriesRenderer the series renderer\r
+   * @param yAxisValue the minimum value of the y axis\r
+   * @param seriesIndex the index of the series currently being drawn\r
+   * @param startIndex the start index of the rendering points\r
+   */\r
+  public abstract void drawSeries(Canvas canvas, Paint paint, float[] points,\r
+      SimpleSeriesRenderer seriesRenderer, float yAxisValue, int seriesIndex, int startIndex);\r
+\r
+  /**\r
+   * Returns the clickable areas for all passed points\r
+   * \r
+   * @param points the array of points\r
+   * @param values the array of values of each point\r
+   * @param yAxisValue the minimum value of the y axis\r
+   * @param seriesIndex the index of the series to which the points belong\r
+   * @return an array of rectangles with the clickable area\r
+   * @param startIndex the start index of the rendering points\r
+   */\r
+  protected abstract ClickableArea[] clickableAreasForPoints(float[] points, double[] values,\r
+      float yAxisValue, int seriesIndex, int startIndex);\r
+\r
+  /**\r
+   * Returns if the chart should display the null values.\r
+   * \r
+   * @return if null values should be rendered\r
+   */\r
+  protected boolean isRenderNullValues() {\r
+    return false;\r
+  }\r
+\r
+  /**\r
+   * Returns if the chart should display the points as a certain shape.\r
+   * \r
+   * @param renderer the series renderer\r
+   */\r
+  public boolean isRenderPoints(SimpleSeriesRenderer renderer) {\r
+    return false;\r
+  }\r
+\r
+  /**\r
+   * Returns the default axis minimum.\r
+   * \r
+   * @return the default axis minimum\r
+   */\r
+  public double getDefaultMinimum() {\r
+    return MathHelper.NULL_VALUE;\r
+  }\r
+\r
+  /**\r
+   * Returns the scatter chart to be used for drawing the data points.\r
+   * \r
+   * @return the data points scatter chart\r
+   */\r
+  public ScatterChart getPointsChart() {\r
+    return null;\r
+  }\r
+\r
+  /**\r
+   * Returns the chart type identifier.\r
+   * \r
+   * @return the chart type\r
+   */\r
+  public abstract String getChartType();\r
+\r
+}\r