--- /dev/null
+/**\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