2 * Copyright (C) 2009 - 2012 SC 4ViewSoft SRL
\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
8 * http://www.apache.org/licenses/LICENSE-2.0
\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
16 package org.achartengine.chart;
\r
18 import java.io.Serializable;
\r
19 import java.util.List;
\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
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
39 * An abstract class to be implemented by the chart rendering classes.
\r
41 public abstract class AbstractChart implements Serializable {
\r
43 * The graphical representation of the chart.
\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
52 public abstract void draw(Canvas canvas, int x, int y, int width, int height, Paint paint);
\r
55 * Draws the chart background.
\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
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
71 paint.setColor(color);
\r
73 paint.setColor(renderer.getBackgroundColor());
\r
75 paint.setStyle(Style.FILL);
\r
76 canvas.drawRect(x, y, x + width, y + height, paint);
\r
81 * Draws the chart legend.
\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
95 * @return the legend height
\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
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
112 paint.setColor(Color.LTGRAY);
\r
114 float[] widths = new float[text.length()];
\r
115 paint.getTextWidths(text, widths);
\r
117 for (float value : widths) {
\r
120 float extraSize = lineSize + 10 + sum;
\r
121 float currentWidth = currentX + extraSize;
\r
123 if (i > 0 && getExceed(currentWidth, renderer, right, width)) {
\r
125 currentY += renderer.getLegendTextSize();
\r
126 size += renderer.getLegendTextSize();
\r
127 currentWidth = currentX + extraSize;
\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
134 int nr = paint.breakText(text, true, maxWidth, widths);
\r
135 text = text.substring(0, nr) + "...";
\r
138 drawLegendShape(canvas, renderer.getSeriesRendererAt(i), currentX, currentY, i, paint);
\r
139 drawString(canvas, text, currentX + lineSize + 5, currentY + 5, paint);
\r
141 currentX += extraSize;
\r
144 return Math.round(size + renderer.getLegendTextSize());
\r
148 * Draw a multiple lines string.
\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
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
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
168 * Calculates if the current width exceeds the total width.
\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
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
185 * Checks if the current chart is rendered as vertical.
\r
187 * @param renderer the renderer
\r
188 * @return if the chart is rendered as a vertical one
\r
190 public boolean isVertical(DefaultRenderer renderer) {
\r
191 return renderer instanceof XYMultipleSeriesRenderer
\r
192 && ((XYMultipleSeriesRenderer) renderer).getOrientation() == Orientation.VERTICAL;
\r
196 * Makes sure the fraction digit is not displayed, if not needed.
\r
198 * @param label the input label value
\r
199 * @return the label without the useless fraction digit
\r
201 protected String getLabel(double label) {
\r
203 if (label == Math.round(label)) {
\r
204 text = Math.round(label) + "";
\r
211 private static float[] calculateDrawPoints(float p1x, float p1y, float p2x, float p2y,
\r
212 int screenHeight, int screenWidth) {
\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
225 // If Intersection is left of the screen we calculate the intersection
\r
226 // with the left border
\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
235 } else if (p1y < 0) {
\r
236 float m = (p2y - p1y) / (p2x - p1x);
\r
237 drawP1x = (-p1y + m * p1x) / m;
\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
247 // If the point is in the screen use it
\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
258 drawP2y = p1y - m * p1x;
\r
259 } else if (drawP2x > screenWidth) {
\r
260 drawP2x = screenWidth;
\r
261 drawP2y = m * screenWidth + p1y - m * p1x;
\r
263 } else if (p2y < 0) {
\r
264 float m = (p2y - p1y) / (p2x - p1x);
\r
265 drawP2x = (-p1y + m * p1x) / m;
\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
275 // If the point is in the screen use it
\r
280 return new float[] { drawP1x, drawP1y, drawP2x, drawP2y };
\r
284 * The graphical representation of a path.
\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
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
296 float[] tempDrawPoints;
\r
297 if (points.length < 4) {
\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
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
309 tempDrawPoints = calculateDrawPoints(points[i - 2], points[i - 1], points[i], points[i + 1],
\r
312 path.moveTo(tempDrawPoints[0], tempDrawPoints[1]);
\r
314 path.lineTo(tempDrawPoints[2], tempDrawPoints[3]);
\r
317 path.lineTo(points[0], points[1]);
\r
319 canvas.drawPath(path, paint);
\r
323 * Returns the legend shape width.
\r
325 * @param seriesIndex the series index
\r
326 * @return the legend shape width
\r
328 public abstract int getLegendShapeWidth(int seriesIndex);
\r
331 * The graphical representation of the legend shape.
\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
340 public abstract void drawLegendShape(Canvas canvas, SimpleSeriesRenderer renderer, float x,
\r
341 float y, int seriesIndex, Paint paint);
\r
344 * Calculates the best text to fit into the available space.
\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
351 private String getFitText(String text, float width, Paint paint) {
\r
352 String newText = text;
\r
353 int length = text.length();
\r
355 while (paint.measureText(newText) > width && diff < length) {
\r
357 newText = text.substring(0, length - diff) + "...";
\r
359 if (diff == length) {
\r
366 * Calculates the current legend size.
\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
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
378 if (!renderer.isShowLegend() && renderer.isShowLabels()) {
\r
379 legendSize = (int) (renderer.getLabelsTextSize() * 4 / 3 + extraHeight);
\r
385 * Draws a text label.
\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
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
416 float size = renderer.getLabelsTextSize();
\r
417 float extra = Math.max(size / 2, 10);
\r
418 paint.setTextAlign(Align.LEFT);
\r
421 paint.setTextAlign(Align.RIGHT);
\r
423 float xLabel = x2 + extra;
\r
425 float width = right - xLabel;
\r
427 width = xLabel - left;
\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
439 yLabel = Math.max(yLabel, prevLabelBounds.bottom);
\r
442 okBounds = !intersects;
\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
450 paint.setTextAlign(Align.CENTER);
\r
452 canvas.drawText(labelText, xLabel, yLabel, paint);
\r
454 prevLabelsBounds.add(new RectF(xLabel, yLabel, xLabel + widthLabel, yLabel + size));
\r
459 public boolean isNullValue(double value) {
\r
460 return Double.isNaN(value) || Double.isInfinite(value) || value == MathHelper.NULL_VALUE;
\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
468 * @param screenPoint
\r
469 * @return the series and point indexes
\r
471 public SeriesSelection getSeriesAndPointForScreenCoordinate(Point screenPoint) {
\r