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