create changelog entry
[debian/openrocket] / android-libraries / achartengine / src / org / achartengine / chart / AbstractChart.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.io.Serializable;\r
19 import java.util.List;\r
20 \r
21 import org.achartengine.model.Point;\r
22 import org.achartengine.model.SeriesSelection;\r
23 import org.achartengine.renderer.DefaultRenderer;\r
24 import org.achartengine.renderer.SimpleSeriesRenderer;\r
25 import org.achartengine.renderer.XYMultipleSeriesRenderer;\r
26 import org.achartengine.renderer.XYMultipleSeriesRenderer.Orientation;\r
27 import org.achartengine.util.MathHelper;\r
28 \r
29 import android.graphics.Canvas;\r
30 import android.graphics.Color;\r
31 import android.graphics.Paint;\r
32 import android.graphics.Paint.Align;\r
33 import android.graphics.Paint.Style;\r
34 import android.graphics.Path;\r
35 import android.graphics.Rect;\r
36 import android.graphics.RectF;\r
37 \r
38 /**\r
39  * An abstract class to be implemented by the chart rendering classes.\r
40  */\r
41 public abstract class AbstractChart implements Serializable {\r
42   /**\r
43    * The graphical representation of the chart.\r
44    * \r
45    * @param canvas the canvas to paint to\r
46    * @param x the top left x value of the view to draw to\r
47    * @param y the top left y value of the view to draw to\r
48    * @param width the width of the view to draw to\r
49    * @param height the height of the view to draw to\r
50    * @param paint the paint\r
51    */\r
52   public abstract void draw(Canvas canvas, int x, int y, int width, int height, Paint paint);\r
53 \r
54   /**\r
55    * Draws the chart background.\r
56    * \r
57    * @param renderer the chart renderer\r
58    * @param canvas the canvas to paint to\r
59    * @param x the top left x value of the view to draw to\r
60    * @param y the top left y value of the view to draw to\r
61    * @param width the width of the view to draw to\r
62    * @param height the height of the view to draw to\r
63    * @param paint the paint used for drawing\r
64    * @param newColor if a new color is to be used\r
65    * @param color the color to be used\r
66    */\r
67   protected void drawBackground(DefaultRenderer renderer, Canvas canvas, int x, int y, int width,\r
68       int height, Paint paint, boolean newColor, int color) {\r
69     if (renderer.isApplyBackgroundColor() || newColor) {\r
70       if (newColor) {\r
71         paint.setColor(color);\r
72       } else {\r
73         paint.setColor(renderer.getBackgroundColor());\r
74       }\r
75       paint.setStyle(Style.FILL);\r
76       canvas.drawRect(x, y, x + width, y + height, paint);\r
77     }\r
78   }\r
79 \r
80   /**\r
81    * Draws the chart legend.\r
82    * \r
83    * @param canvas the canvas to paint to\r
84    * @param renderer the series renderer\r
85    * @param titles the titles to go to the legend\r
86    * @param left the left X value of the area to draw to\r
87    * @param right the right X value of the area to draw to\r
88    * @param y the y value of the area to draw to\r
89    * @param width the width of the area to draw to\r
90    * @param height the height of the area to draw to\r
91    * @param legendSize the legend size\r
92    * @param paint the paint to be used for drawing\r
93    * @param calculate if only calculating the legend size\r
94    * \r
95    * @return the legend height\r
96    */\r
97   protected int drawLegend(Canvas canvas, DefaultRenderer renderer, String[] titles, int left,\r
98       int right, int y, int width, int height, int legendSize, Paint paint, boolean calculate) {\r
99     float size = 32;\r
100     if (renderer.isShowLegend()) {\r
101       float currentX = left;\r
102       float currentY = y + height - legendSize + size;\r
103       paint.setTextAlign(Align.LEFT);\r
104       paint.setTextSize(renderer.getLegendTextSize());\r
105       int sLength = Math.min(titles.length, renderer.getSeriesRendererCount());\r
106       for (int i = 0; i < sLength; i++) {\r
107         final float lineSize = getLegendShapeWidth(i);\r
108         String text = titles[i];\r
109         if (titles.length == renderer.getSeriesRendererCount()) {\r
110           paint.setColor(renderer.getSeriesRendererAt(i).getColor());\r
111         } else {\r
112           paint.setColor(Color.LTGRAY);\r
113         }\r
114         float[] widths = new float[text.length()];\r
115         paint.getTextWidths(text, widths);\r
116         float sum = 0;\r
117         for (float value : widths) {\r
118           sum += value;\r
119         }\r
120         float extraSize = lineSize + 10 + sum;\r
121         float currentWidth = currentX + extraSize;\r
122 \r
123         if (i > 0 && getExceed(currentWidth, renderer, right, width)) {\r
124           currentX = left;\r
125           currentY += renderer.getLegendTextSize();\r
126           size += renderer.getLegendTextSize();\r
127           currentWidth = currentX + extraSize;\r
128         }\r
129         if (getExceed(currentWidth, renderer, right, width)) {\r
130           float maxWidth = right - currentX - lineSize - 10;\r
131           if (isVertical(renderer)) {\r
132             maxWidth = width - currentX - lineSize - 10;\r
133           }\r
134           int nr = paint.breakText(text, true, maxWidth, widths);\r
135           text = text.substring(0, nr) + "...";\r
136         }\r
137         if (!calculate) {\r
138           drawLegendShape(canvas, renderer.getSeriesRendererAt(i), currentX, currentY, i, paint);\r
139           drawString(canvas, text, currentX + lineSize + 5, currentY + 5, paint);\r
140         }\r
141         currentX += extraSize;\r
142       }\r
143     }\r
144     return Math.round(size + renderer.getLegendTextSize());\r
145   }\r
146 \r
147   /**\r
148    * Draw a multiple lines string.\r
149    * \r
150    * @param canvas the canvas to paint to\r
151    * @param text the text to be painted\r
152    * @param x the x value of the area to draw to\r
153    * @param y the y value of the area to draw to\r
154    * @param paint the paint to be used for drawing\r
155    */\r
156   protected void drawString(Canvas canvas, String text, float x, float y, Paint paint) {\r
157     String[] lines = text.split("\n");\r
158     Rect rect = new Rect();\r
159     int yOff = 0;\r
160     for (int i = 0; i < lines.length; ++i) {\r
161       canvas.drawText(lines[i], x, y + yOff, paint);\r
162       paint.getTextBounds(lines[i], 0, lines[i].length(), rect);\r
163       yOff = yOff + rect.height() + 5; // space between lines is 5\r
164     }\r
165   }\r
166 \r
167   /**\r
168    * Calculates if the current width exceeds the total width.\r
169    * \r
170    * @param currentWidth the current width\r
171    * @param renderer the renderer\r
172    * @param right the right side pixel value\r
173    * @param width the total width\r
174    * @return if the current width exceeds the total width\r
175    */\r
176   protected boolean getExceed(float currentWidth, DefaultRenderer renderer, int right, int width) {\r
177     boolean exceed = currentWidth > right;\r
178     if (isVertical(renderer)) {\r
179       exceed = currentWidth > width;\r
180     }\r
181     return exceed;\r
182   }\r
183 \r
184   /**\r
185    * Checks if the current chart is rendered as vertical.\r
186    * \r
187    * @param renderer the renderer\r
188    * @return if the chart is rendered as a vertical one\r
189    */\r
190   public boolean isVertical(DefaultRenderer renderer) {\r
191     return renderer instanceof XYMultipleSeriesRenderer\r
192         && ((XYMultipleSeriesRenderer) renderer).getOrientation() == Orientation.VERTICAL;\r
193   }\r
194   \r
195   /**\r
196    * Makes sure the fraction digit is not displayed, if not needed.\r
197    * \r
198    * @param label the input label value\r
199    * @return the label without the useless fraction digit\r
200    */\r
201   protected String getLabel(double label) {\r
202     String text = "";\r
203     if (label == Math.round(label)) {\r
204       text = Math.round(label) + "";\r
205     } else {\r
206       text = label + "";\r
207     }\r
208     return text;\r
209   }\r
210 \r
211   private static float[] calculateDrawPoints(float p1x, float p1y, float p2x, float p2y,\r
212       int screenHeight, int screenWidth) {\r
213     float drawP1x;\r
214     float drawP1y;\r
215     float drawP2x;\r
216     float drawP2y;\r
217 \r
218     if (p1y > screenHeight) {\r
219       // Intersection with the top of the screen\r
220       float m = (p2y - p1y) / (p2x - p1x);\r
221       drawP1x = (screenHeight - p1y + m * p1x) / m;\r
222       drawP1y = screenHeight;\r
223 \r
224       if (drawP1x < 0) {\r
225         // If Intersection is left of the screen we calculate the intersection\r
226         // with the left border\r
227         drawP1x = 0;\r
228         drawP1y = p1y - m * p1x;\r
229       } else if (drawP1x > screenWidth) {\r
230         // If Intersection is right of the screen we calculate the intersection\r
231         // with the right border\r
232         drawP1x = screenWidth;\r
233         drawP1y = m * screenWidth + p1y - m * p1x;\r
234       }\r
235     } else if (p1y < 0) {\r
236       float m = (p2y - p1y) / (p2x - p1x);\r
237       drawP1x = (-p1y + m * p1x) / m;\r
238       drawP1y = 0;\r
239       if (drawP1x < 0) {\r
240         drawP1x = 0;\r
241         drawP1y = p1y - m * p1x;\r
242       } else if (drawP1x > screenWidth) {\r
243         drawP1x = screenWidth;\r
244         drawP1y = m * screenWidth + p1y - m * p1x;\r
245       }\r
246     } else {\r
247       // If the point is in the screen use it\r
248       drawP1x = p1x;\r
249       drawP1y = p1y;\r
250     }\r
251 \r
252     if (p2y > screenHeight) {\r
253       float m = (p2y - p1y) / (p2x - p1x);\r
254       drawP2x = (screenHeight - p1y + m * p1x) / m;\r
255       drawP2y = screenHeight;\r
256       if (drawP2x < 0) {\r
257         drawP2x = 0;\r
258         drawP2y = p1y - m * p1x;\r
259       } else if (drawP2x > screenWidth) {\r
260         drawP2x = screenWidth;\r
261         drawP2y = m * screenWidth + p1y - m * p1x;\r
262       }\r
263     } else if (p2y < 0) {\r
264       float m = (p2y - p1y) / (p2x - p1x);\r
265       drawP2x = (-p1y + m * p1x) / m;\r
266       drawP2y = 0;\r
267       if (drawP2x < 0) {\r
268         drawP2x = 0;\r
269         drawP2y = p1y - m * p1x;\r
270       } else if (drawP2x > screenWidth) {\r
271         drawP2x = screenWidth;\r
272         drawP2y = m * screenWidth + p1y - m * p1x;\r
273       }\r
274     } else {\r
275       // If the point is in the screen use it\r
276       drawP2x = p2x;\r
277       drawP2y = p2y;\r
278     }\r
279 \r
280     return new float[] { drawP1x, drawP1y, drawP2x, drawP2y };\r
281   }\r
282 \r
283   /**\r
284    * The graphical representation of a path.\r
285    * \r
286    * @param canvas the canvas to paint to\r
287    * @param points the points that are contained in the path to paint\r
288    * @param paint the paint to be used for painting\r
289    * @param circular if the path ends with the start point\r
290    */\r
291   protected void drawPath(Canvas canvas, float[] points, Paint paint, boolean circular) {\r
292     Path path = new Path();\r
293     int height = canvas.getHeight();\r
294     int width = canvas.getWidth();\r
295 \r
296     float[] tempDrawPoints;\r
297     if (points.length < 4) {\r
298       return;\r
299     }\r
300     tempDrawPoints = calculateDrawPoints(points[0], points[1], points[2], points[3], height, width);\r
301     path.moveTo(tempDrawPoints[0], tempDrawPoints[1]);\r
302     path.lineTo(tempDrawPoints[2], tempDrawPoints[3]);\r
303 \r
304     for (int i = 4; i < points.length; i += 2) {\r
305       if ((points[i - 1] < 0 && points[i + 1] < 0)\r
306           || (points[i - 1] > height && points[i + 1] > height)) {\r
307         continue;\r
308       }\r
309       tempDrawPoints = calculateDrawPoints(points[i - 2], points[i - 1], points[i], points[i + 1],\r
310           height, width);\r
311       if (!circular) {\r
312         path.moveTo(tempDrawPoints[0], tempDrawPoints[1]);\r
313       }\r
314       path.lineTo(tempDrawPoints[2], tempDrawPoints[3]);\r
315     }\r
316     if (circular) {\r
317       path.lineTo(points[0], points[1]);\r
318     }\r
319     canvas.drawPath(path, paint);\r
320   }\r
321 \r
322   /**\r
323    * Returns the legend shape width.\r
324    * \r
325    * @param seriesIndex the series index\r
326    * @return the legend shape width\r
327    */\r
328   public abstract int getLegendShapeWidth(int seriesIndex);\r
329 \r
330   /**\r
331    * The graphical representation of the legend shape.\r
332    * \r
333    * @param canvas the canvas to paint to\r
334    * @param renderer the series renderer\r
335    * @param x the x value of the point the shape should be drawn at\r
336    * @param y the y value of the point the shape should be drawn at\r
337    * @param seriesIndex the series index\r
338    * @param paint the paint to be used for drawing\r
339    */\r
340   public abstract void drawLegendShape(Canvas canvas, SimpleSeriesRenderer renderer, float x,\r
341       float y, int seriesIndex, Paint paint);\r
342 \r
343   /**\r
344    * Calculates the best text to fit into the available space.\r
345    * \r
346    * @param text the entire text\r
347    * @param width the width to fit the text into\r
348    * @param paint the paint\r
349    * @return the text to fit into the space\r
350    */\r
351   private String getFitText(String text, float width, Paint paint) {\r
352     String newText = text;\r
353     int length = text.length();\r
354     int diff = 0;\r
355     while (paint.measureText(newText) > width && diff < length) {\r
356       diff++;\r
357       newText = text.substring(0, length - diff) + "...";\r
358     }\r
359     if (diff == length) {\r
360       newText = "...";\r
361     }\r
362     return newText;\r
363   }\r
364 \r
365   /**\r
366    * Calculates the current legend size.\r
367    * \r
368    * @param renderer the renderer\r
369    * @param defaultHeight the default height\r
370    * @param extraHeight the added extra height\r
371    * @return the legend size\r
372    */\r
373   protected int getLegendSize(DefaultRenderer renderer, int defaultHeight, float extraHeight) {\r
374     int legendSize = renderer.getLegendHeight();\r
375     if (renderer.isShowLegend() && legendSize == 0) {\r
376       legendSize = defaultHeight;\r
377     }\r
378     if (!renderer.isShowLegend() && renderer.isShowLabels()) {\r
379       legendSize = (int) (renderer.getLabelsTextSize() * 4 / 3 + extraHeight);\r
380     }\r
381     return legendSize;\r
382   }\r
383 \r
384   /**\r
385    * Draws a text label.\r
386    * \r
387    * @param canvas the canvas\r
388    * @param labelText the label text\r
389    * @param renderer the renderer\r
390    * @param prevLabelsBounds the previous rendered label bounds\r
391    * @param centerX the round chart center on X axis\r
392    * @param centerY the round chart center on Y axis\r
393    * @param shortRadius the short radius for the round chart\r
394    * @param longRadius the long radius for the round chart\r
395    * @param currentAngle the current angle\r
396    * @param angle the label extra angle\r
397    * @param left the left side\r
398    * @param right the right side\r
399    * @param color the label color\r
400    * @param paint the paint\r
401    * @param line if a line to the label should be drawn\r
402    */\r
403   protected void drawLabel(Canvas canvas, String labelText, DefaultRenderer renderer,\r
404       List<RectF> prevLabelsBounds, int centerX, int centerY, float shortRadius, float longRadius,\r
405       float currentAngle, float angle, int left, int right, int color, Paint paint, boolean line) {\r
406     if (renderer.isShowLabels()) {\r
407       paint.setColor(color);\r
408       double rAngle = Math.toRadians(90 - (currentAngle + angle / 2));\r
409       double sinValue = Math.sin(rAngle);\r
410       double cosValue = Math.cos(rAngle);\r
411       int x1 = Math.round(centerX + (float) (shortRadius * sinValue));\r
412       int y1 = Math.round(centerY + (float) (shortRadius * cosValue));\r
413       int x2 = Math.round(centerX + (float) (longRadius * sinValue));\r
414       int y2 = Math.round(centerY + (float) (longRadius * cosValue));\r
415 \r
416       float size = renderer.getLabelsTextSize();\r
417       float extra = Math.max(size / 2, 10);\r
418       paint.setTextAlign(Align.LEFT);\r
419       if (x1 > x2) {\r
420         extra = -extra;\r
421         paint.setTextAlign(Align.RIGHT);\r
422       }\r
423       float xLabel = x2 + extra;\r
424       float yLabel = y2;\r
425       float width = right - xLabel;\r
426       if (x1 > x2) {\r
427         width = xLabel - left;\r
428       }\r
429       labelText = getFitText(labelText, width, paint);\r
430       float widthLabel = paint.measureText(labelText);\r
431       boolean okBounds = false;\r
432       while (!okBounds && line) {\r
433         boolean intersects = false;\r
434         int length = prevLabelsBounds.size();\r
435         for (int j = 0; j < length && !intersects; j++) {\r
436           RectF prevLabelBounds = prevLabelsBounds.get(j);\r
437           if (prevLabelBounds.intersects(xLabel, yLabel, xLabel + widthLabel, yLabel + size)) {\r
438             intersects = true;\r
439             yLabel = Math.max(yLabel, prevLabelBounds.bottom);\r
440           }\r
441         }\r
442         okBounds = !intersects;\r
443       }\r
444 \r
445       if (line) {\r
446         y2 = (int) (yLabel - size / 2);\r
447         canvas.drawLine(x1, y1, x2, y2, paint);\r
448         canvas.drawLine(x2, y2, x2 + extra, y2, paint);\r
449       } else {\r
450         paint.setTextAlign(Align.CENTER);\r
451       }\r
452       canvas.drawText(labelText, xLabel, yLabel, paint);\r
453       if (line) {\r
454         prevLabelsBounds.add(new RectF(xLabel, yLabel, xLabel + widthLabel, yLabel + size));\r
455       }\r
456     }\r
457   }\r
458 \r
459   public boolean isNullValue(double value) {\r
460     return Double.isNaN(value) || Double.isInfinite(value) || value == MathHelper.NULL_VALUE;\r
461   }\r
462 \r
463   /**\r
464    * Given screen coordinates, returns the series and point indexes of a chart\r
465    * element. If there is no chart element (line, point, bar, etc) at those\r
466    * coordinates, null is returned.\r
467    * \r
468    * @param screenPoint\r
469    * @return the series and point indexes\r
470    */\r
471   public SeriesSelection getSeriesAndPointForScreenCoordinate(Point screenPoint) {\r
472     return null;\r
473   }\r
474 \r
475 }\r