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