Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / android-libraries / achartengine / src / org / achartengine / chart / AbstractChart.java
diff --git a/android-libraries/achartengine/src/org/achartengine/chart/AbstractChart.java b/android-libraries/achartengine/src/org/achartengine/chart/AbstractChart.java
new file mode 100644 (file)
index 0000000..f99a5c2
--- /dev/null
@@ -0,0 +1,475 @@
+/**\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.io.Serializable;\r
+import java.util.List;\r
+\r
+import org.achartengine.model.Point;\r
+import org.achartengine.model.SeriesSelection;\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.Color;\r
+import android.graphics.Paint;\r
+import android.graphics.Paint.Align;\r
+import android.graphics.Paint.Style;\r
+import android.graphics.Path;\r
+import android.graphics.Rect;\r
+import android.graphics.RectF;\r
+\r
+/**\r
+ * An abstract class to be implemented by the chart rendering classes.\r
+ */\r
+public abstract class AbstractChart implements Serializable {\r
+  /**\r
+   * The graphical representation of the 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 abstract void draw(Canvas canvas, int x, int y, int width, int height, Paint paint);\r
+\r
+  /**\r
+   * Draws the chart background.\r
+   * \r
+   * @param renderer the chart renderer\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 used for drawing\r
+   * @param newColor if a new color is to be used\r
+   * @param color the color to be used\r
+   */\r
+  protected void drawBackground(DefaultRenderer renderer, Canvas canvas, int x, int y, int width,\r
+      int height, Paint paint, boolean newColor, int color) {\r
+    if (renderer.isApplyBackgroundColor() || newColor) {\r
+      if (newColor) {\r
+        paint.setColor(color);\r
+      } else {\r
+        paint.setColor(renderer.getBackgroundColor());\r
+      }\r
+      paint.setStyle(Style.FILL);\r
+      canvas.drawRect(x, y, x + width, y + height, paint);\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Draws the chart legend.\r
+   * \r
+   * @param canvas the canvas to paint to\r
+   * @param renderer the series renderer\r
+   * @param titles the titles to go to the legend\r
+   * @param left the left X value of the area to draw to\r
+   * @param right the right X value of the area to draw to\r
+   * @param y the y value of the area to draw to\r
+   * @param width the width of the area to draw to\r
+   * @param height the height of the area to draw to\r
+   * @param legendSize the legend size\r
+   * @param paint the paint to be used for drawing\r
+   * @param calculate if only calculating the legend size\r
+   * \r
+   * @return the legend height\r
+   */\r
+  protected int drawLegend(Canvas canvas, DefaultRenderer renderer, String[] titles, int left,\r
+      int right, int y, int width, int height, int legendSize, Paint paint, boolean calculate) {\r
+    float size = 32;\r
+    if (renderer.isShowLegend()) {\r
+      float currentX = left;\r
+      float currentY = y + height - legendSize + size;\r
+      paint.setTextAlign(Align.LEFT);\r
+      paint.setTextSize(renderer.getLegendTextSize());\r
+      int sLength = Math.min(titles.length, renderer.getSeriesRendererCount());\r
+      for (int i = 0; i < sLength; i++) {\r
+        final float lineSize = getLegendShapeWidth(i);\r
+        String text = titles[i];\r
+        if (titles.length == renderer.getSeriesRendererCount()) {\r
+          paint.setColor(renderer.getSeriesRendererAt(i).getColor());\r
+        } else {\r
+          paint.setColor(Color.LTGRAY);\r
+        }\r
+        float[] widths = new float[text.length()];\r
+        paint.getTextWidths(text, widths);\r
+        float sum = 0;\r
+        for (float value : widths) {\r
+          sum += value;\r
+        }\r
+        float extraSize = lineSize + 10 + sum;\r
+        float currentWidth = currentX + extraSize;\r
+\r
+        if (i > 0 && getExceed(currentWidth, renderer, right, width)) {\r
+          currentX = left;\r
+          currentY += renderer.getLegendTextSize();\r
+          size += renderer.getLegendTextSize();\r
+          currentWidth = currentX + extraSize;\r
+        }\r
+        if (getExceed(currentWidth, renderer, right, width)) {\r
+          float maxWidth = right - currentX - lineSize - 10;\r
+          if (isVertical(renderer)) {\r
+            maxWidth = width - currentX - lineSize - 10;\r
+          }\r
+          int nr = paint.breakText(text, true, maxWidth, widths);\r
+          text = text.substring(0, nr) + "...";\r
+        }\r
+        if (!calculate) {\r
+          drawLegendShape(canvas, renderer.getSeriesRendererAt(i), currentX, currentY, i, paint);\r
+          drawString(canvas, text, currentX + lineSize + 5, currentY + 5, paint);\r
+        }\r
+        currentX += extraSize;\r
+      }\r
+    }\r
+    return Math.round(size + renderer.getLegendTextSize());\r
+  }\r
+\r
+  /**\r
+   * Draw a multiple lines string.\r
+   * \r
+   * @param canvas the canvas to paint to\r
+   * @param text the text to be painted\r
+   * @param x the x value of the area to draw to\r
+   * @param y the y value of the area to draw to\r
+   * @param paint the paint to be used for drawing\r
+   */\r
+  protected void drawString(Canvas canvas, String text, float x, float y, Paint paint) {\r
+    String[] lines = text.split("\n");\r
+    Rect rect = new Rect();\r
+    int yOff = 0;\r
+    for (int i = 0; i < lines.length; ++i) {\r
+      canvas.drawText(lines[i], x, y + yOff, paint);\r
+      paint.getTextBounds(lines[i], 0, lines[i].length(), rect);\r
+      yOff = yOff + rect.height() + 5; // space between lines is 5\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Calculates if the current width exceeds the total width.\r
+   * \r
+   * @param currentWidth the current width\r
+   * @param renderer the renderer\r
+   * @param right the right side pixel value\r
+   * @param width the total width\r
+   * @return if the current width exceeds the total width\r
+   */\r
+  protected boolean getExceed(float currentWidth, DefaultRenderer renderer, int right, int width) {\r
+    boolean exceed = currentWidth > right;\r
+    if (isVertical(renderer)) {\r
+      exceed = currentWidth > width;\r
+    }\r
+    return exceed;\r
+  }\r
+\r
+  /**\r
+   * Checks if the current chart is rendered as vertical.\r
+   * \r
+   * @param renderer the renderer\r
+   * @return if the chart is rendered as a vertical one\r
+   */\r
+  public boolean isVertical(DefaultRenderer renderer) {\r
+    return renderer instanceof XYMultipleSeriesRenderer\r
+        && ((XYMultipleSeriesRenderer) renderer).getOrientation() == Orientation.VERTICAL;\r
+  }\r
+  \r
+  /**\r
+   * Makes sure the fraction digit is not displayed, if not needed.\r
+   * \r
+   * @param label the input label value\r
+   * @return the label without the useless fraction digit\r
+   */\r
+  protected String getLabel(double label) {\r
+    String text = "";\r
+    if (label == Math.round(label)) {\r
+      text = Math.round(label) + "";\r
+    } else {\r
+      text = label + "";\r
+    }\r
+    return text;\r
+  }\r
+\r
+  private static float[] calculateDrawPoints(float p1x, float p1y, float p2x, float p2y,\r
+      int screenHeight, int screenWidth) {\r
+    float drawP1x;\r
+    float drawP1y;\r
+    float drawP2x;\r
+    float drawP2y;\r
+\r
+    if (p1y > screenHeight) {\r
+      // Intersection with the top of the screen\r
+      float m = (p2y - p1y) / (p2x - p1x);\r
+      drawP1x = (screenHeight - p1y + m * p1x) / m;\r
+      drawP1y = screenHeight;\r
+\r
+      if (drawP1x < 0) {\r
+        // If Intersection is left of the screen we calculate the intersection\r
+        // with the left border\r
+        drawP1x = 0;\r
+        drawP1y = p1y - m * p1x;\r
+      } else if (drawP1x > screenWidth) {\r
+        // If Intersection is right of the screen we calculate the intersection\r
+        // with the right border\r
+        drawP1x = screenWidth;\r
+        drawP1y = m * screenWidth + p1y - m * p1x;\r
+      }\r
+    } else if (p1y < 0) {\r
+      float m = (p2y - p1y) / (p2x - p1x);\r
+      drawP1x = (-p1y + m * p1x) / m;\r
+      drawP1y = 0;\r
+      if (drawP1x < 0) {\r
+        drawP1x = 0;\r
+        drawP1y = p1y - m * p1x;\r
+      } else if (drawP1x > screenWidth) {\r
+        drawP1x = screenWidth;\r
+        drawP1y = m * screenWidth + p1y - m * p1x;\r
+      }\r
+    } else {\r
+      // If the point is in the screen use it\r
+      drawP1x = p1x;\r
+      drawP1y = p1y;\r
+    }\r
+\r
+    if (p2y > screenHeight) {\r
+      float m = (p2y - p1y) / (p2x - p1x);\r
+      drawP2x = (screenHeight - p1y + m * p1x) / m;\r
+      drawP2y = screenHeight;\r
+      if (drawP2x < 0) {\r
+        drawP2x = 0;\r
+        drawP2y = p1y - m * p1x;\r
+      } else if (drawP2x > screenWidth) {\r
+        drawP2x = screenWidth;\r
+        drawP2y = m * screenWidth + p1y - m * p1x;\r
+      }\r
+    } else if (p2y < 0) {\r
+      float m = (p2y - p1y) / (p2x - p1x);\r
+      drawP2x = (-p1y + m * p1x) / m;\r
+      drawP2y = 0;\r
+      if (drawP2x < 0) {\r
+        drawP2x = 0;\r
+        drawP2y = p1y - m * p1x;\r
+      } else if (drawP2x > screenWidth) {\r
+        drawP2x = screenWidth;\r
+        drawP2y = m * screenWidth + p1y - m * p1x;\r
+      }\r
+    } else {\r
+      // If the point is in the screen use it\r
+      drawP2x = p2x;\r
+      drawP2y = p2y;\r
+    }\r
+\r
+    return new float[] { drawP1x, drawP1y, drawP2x, drawP2y };\r
+  }\r
+\r
+  /**\r
+   * The graphical representation of a path.\r
+   * \r
+   * @param canvas the canvas to paint to\r
+   * @param points the points that are contained in the path to paint\r
+   * @param paint the paint to be used for painting\r
+   * @param circular if the path ends with the start point\r
+   */\r
+  protected void drawPath(Canvas canvas, float[] points, Paint paint, boolean circular) {\r
+    Path path = new Path();\r
+    int height = canvas.getHeight();\r
+    int width = canvas.getWidth();\r
+\r
+    float[] tempDrawPoints;\r
+    if (points.length < 4) {\r
+      return;\r
+    }\r
+    tempDrawPoints = calculateDrawPoints(points[0], points[1], points[2], points[3], height, width);\r
+    path.moveTo(tempDrawPoints[0], tempDrawPoints[1]);\r
+    path.lineTo(tempDrawPoints[2], tempDrawPoints[3]);\r
+\r
+    for (int i = 4; i < points.length; i += 2) {\r
+      if ((points[i - 1] < 0 && points[i + 1] < 0)\r
+          || (points[i - 1] > height && points[i + 1] > height)) {\r
+        continue;\r
+      }\r
+      tempDrawPoints = calculateDrawPoints(points[i - 2], points[i - 1], points[i], points[i + 1],\r
+          height, width);\r
+      if (!circular) {\r
+        path.moveTo(tempDrawPoints[0], tempDrawPoints[1]);\r
+      }\r
+      path.lineTo(tempDrawPoints[2], tempDrawPoints[3]);\r
+    }\r
+    if (circular) {\r
+      path.lineTo(points[0], points[1]);\r
+    }\r
+    canvas.drawPath(path, paint);\r
+  }\r
+\r
+  /**\r
+   * Returns the legend shape width.\r
+   * \r
+   * @param seriesIndex the series index\r
+   * @return the legend shape width\r
+   */\r
+  public abstract int getLegendShapeWidth(int seriesIndex);\r
+\r
+  /**\r
+   * The graphical representation of the legend shape.\r
+   * \r
+   * @param canvas the canvas to paint to\r
+   * @param renderer the series renderer\r
+   * @param x the x value of the point the shape should be drawn at\r
+   * @param y the y value of the point the shape should be drawn at\r
+   * @param seriesIndex the series index\r
+   * @param paint the paint to be used for drawing\r
+   */\r
+  public abstract void drawLegendShape(Canvas canvas, SimpleSeriesRenderer renderer, float x,\r
+      float y, int seriesIndex, Paint paint);\r
+\r
+  /**\r
+   * Calculates the best text to fit into the available space.\r
+   * \r
+   * @param text the entire text\r
+   * @param width the width to fit the text into\r
+   * @param paint the paint\r
+   * @return the text to fit into the space\r
+   */\r
+  private String getFitText(String text, float width, Paint paint) {\r
+    String newText = text;\r
+    int length = text.length();\r
+    int diff = 0;\r
+    while (paint.measureText(newText) > width && diff < length) {\r
+      diff++;\r
+      newText = text.substring(0, length - diff) + "...";\r
+    }\r
+    if (diff == length) {\r
+      newText = "...";\r
+    }\r
+    return newText;\r
+  }\r
+\r
+  /**\r
+   * Calculates the current legend size.\r
+   * \r
+   * @param renderer the renderer\r
+   * @param defaultHeight the default height\r
+   * @param extraHeight the added extra height\r
+   * @return the legend size\r
+   */\r
+  protected int getLegendSize(DefaultRenderer renderer, int defaultHeight, float extraHeight) {\r
+    int legendSize = renderer.getLegendHeight();\r
+    if (renderer.isShowLegend() && legendSize == 0) {\r
+      legendSize = defaultHeight;\r
+    }\r
+    if (!renderer.isShowLegend() && renderer.isShowLabels()) {\r
+      legendSize = (int) (renderer.getLabelsTextSize() * 4 / 3 + extraHeight);\r
+    }\r
+    return legendSize;\r
+  }\r
+\r
+  /**\r
+   * Draws a text label.\r
+   * \r
+   * @param canvas the canvas\r
+   * @param labelText the label text\r
+   * @param renderer the renderer\r
+   * @param prevLabelsBounds the previous rendered label bounds\r
+   * @param centerX the round chart center on X axis\r
+   * @param centerY the round chart center on Y axis\r
+   * @param shortRadius the short radius for the round chart\r
+   * @param longRadius the long radius for the round chart\r
+   * @param currentAngle the current angle\r
+   * @param angle the label extra angle\r
+   * @param left the left side\r
+   * @param right the right side\r
+   * @param color the label color\r
+   * @param paint the paint\r
+   * @param line if a line to the label should be drawn\r
+   */\r
+  protected void drawLabel(Canvas canvas, String labelText, DefaultRenderer renderer,\r
+      List<RectF> prevLabelsBounds, int centerX, int centerY, float shortRadius, float longRadius,\r
+      float currentAngle, float angle, int left, int right, int color, Paint paint, boolean line) {\r
+    if (renderer.isShowLabels()) {\r
+      paint.setColor(color);\r
+      double rAngle = Math.toRadians(90 - (currentAngle + angle / 2));\r
+      double sinValue = Math.sin(rAngle);\r
+      double cosValue = Math.cos(rAngle);\r
+      int x1 = Math.round(centerX + (float) (shortRadius * sinValue));\r
+      int y1 = Math.round(centerY + (float) (shortRadius * cosValue));\r
+      int x2 = Math.round(centerX + (float) (longRadius * sinValue));\r
+      int y2 = Math.round(centerY + (float) (longRadius * cosValue));\r
+\r
+      float size = renderer.getLabelsTextSize();\r
+      float extra = Math.max(size / 2, 10);\r
+      paint.setTextAlign(Align.LEFT);\r
+      if (x1 > x2) {\r
+        extra = -extra;\r
+        paint.setTextAlign(Align.RIGHT);\r
+      }\r
+      float xLabel = x2 + extra;\r
+      float yLabel = y2;\r
+      float width = right - xLabel;\r
+      if (x1 > x2) {\r
+        width = xLabel - left;\r
+      }\r
+      labelText = getFitText(labelText, width, paint);\r
+      float widthLabel = paint.measureText(labelText);\r
+      boolean okBounds = false;\r
+      while (!okBounds && line) {\r
+        boolean intersects = false;\r
+        int length = prevLabelsBounds.size();\r
+        for (int j = 0; j < length && !intersects; j++) {\r
+          RectF prevLabelBounds = prevLabelsBounds.get(j);\r
+          if (prevLabelBounds.intersects(xLabel, yLabel, xLabel + widthLabel, yLabel + size)) {\r
+            intersects = true;\r
+            yLabel = Math.max(yLabel, prevLabelBounds.bottom);\r
+          }\r
+        }\r
+        okBounds = !intersects;\r
+      }\r
+\r
+      if (line) {\r
+        y2 = (int) (yLabel - size / 2);\r
+        canvas.drawLine(x1, y1, x2, y2, paint);\r
+        canvas.drawLine(x2, y2, x2 + extra, y2, paint);\r
+      } else {\r
+        paint.setTextAlign(Align.CENTER);\r
+      }\r
+      canvas.drawText(labelText, xLabel, yLabel, paint);\r
+      if (line) {\r
+        prevLabelsBounds.add(new RectF(xLabel, yLabel, xLabel + widthLabel, yLabel + size));\r
+      }\r
+    }\r
+  }\r
+\r
+  public boolean isNullValue(double value) {\r
+    return Double.isNaN(value) || Double.isInfinite(value) || value == MathHelper.NULL_VALUE;\r
+  }\r
+\r
+  /**\r
+   * Given screen coordinates, returns the series and point indexes of a chart\r
+   * element. If there is no chart element (line, point, bar, etc) at those\r
+   * coordinates, null is returned.\r
+   * \r
+   * @param screenPoint\r
+   * @return the series and point indexes\r
+   */\r
+  public SeriesSelection getSeriesAndPointForScreenCoordinate(Point screenPoint) {\r
+    return null;\r
+  }\r
+\r
+}\r