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.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
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
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
51 * The XY chart rendering class.
\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
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
73 private Map<Integer, List<ClickableArea>> clickableAreas = new HashMap<Integer, List<ClickableArea>>();
\r
75 protected XYChart() {
\r
79 * Builds a new XY chart instance.
\r
81 * @param dataset the multiple series dataset
\r
82 * @param renderer the multiple series renderer
\r
84 public XYChart(XYMultipleSeriesDataset dataset, XYMultipleSeriesRenderer renderer) {
\r
86 mRenderer = renderer;
\r
90 protected void setDatasetRenderer(XYMultipleSeriesDataset dataset,
\r
91 XYMultipleSeriesRenderer renderer) {
\r
93 mRenderer = renderer;
\r
97 * The graphical representation of the XY chart.
\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
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
118 if (mRenderer.isFitLegend() && mRenderer.isShowLegend()) {
\r
119 legendSize = drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize,
\r
122 int bottom = y + height - margins[2] - legendSize;
\r
123 if (mScreenR == null) {
\r
124 mScreenR = new Rect();
\r
126 mScreenR.set(left, top, right, bottom);
\r
127 drawBackground(mRenderer, canvas, x, y, width, height, paint, false, DefaultRenderer.NO_COLOR);
\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
135 Orientation or = mRenderer.getOrientation();
\r
136 if (or == Orientation.VERTICAL) {
\r
137 right -= legendSize;
\r
138 bottom += legendSize - 20;
\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
147 mCenter = new Point((x + width) / 2, (y + height) / 2);
\r
149 transform(canvas, angle, false);
\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
157 if (maxScaleNumber < 0) {
\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
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
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
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
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
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
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
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
215 if (maxY[i] - minY[i] != 0) {
\r
216 yPixelsPerUnit[i] = (float) ((bottom - top) / (maxY[i] - minY[i]));
\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
234 SimpleSeriesRenderer seriesRenderer = mRenderer.getSeriesRendererAt(i);
\r
236 // int originalValuesLength = series.getItemCount();
\r
237 // int valuesLength = originalValuesLength;
\r
238 // int length = valuesLength * 2;
\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
245 clickableAreas.put(i, clickableArea);
\r
247 SortedMap<Double, Double> range = series.getRange(minX[scale], maxX[scale], 1);
\r
248 int startIndex = -1;
\r
250 for (Entry<Double, Double> value : range.entrySet()) {
\r
252 double xValue = value.getKey();
\r
253 double yValue = value.getValue();
\r
254 if (startIndex < 0) {
\r
255 startIndex = series.getIndexForKey(xValue);
\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
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
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
277 clickableArea.addAll(Arrays.asList(clickableAreasForSubSeries));
\r
281 clickableArea.add(null);
\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
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
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
317 int xLabelsLeft = left;
\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
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
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
348 canvas.drawLine(right, yLabel, right + getLabelLinePos(axisAlign), yLabel, paint);
\r
349 drawText(canvas, label, right, yLabel - 2, paint, mRenderer.getYLabelsAngle());
\r
352 if (showCustomTextGrid) {
\r
353 paint.setColor(mRenderer.getGridColor());
\r
354 canvas.drawLine(left, yLabel, right, yLabel, paint);
\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
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
382 drawText(canvas, mRenderer.getYTitle(i), x + width, y + height / 2, paint, -90);
\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
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
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
410 if (or == Orientation.HORIZONTAL) {
\r
411 canvas.drawLine(left, top, left, bottom, paint);
\r
413 canvas.drawLine(right, top, right, bottom, paint);
\r
415 } else if (or == Orientation.VERTICAL) {
\r
416 canvas.drawLine(right, top, right, bottom, paint);
\r
420 transform(canvas, angle, true);
\r
424 protected List<Double> getXLabels(double min, double max, int count) {
\r
425 return MathHelper.getLabels(min, max, count);
\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
432 getValidLabels(MathHelper.getLabels(minY[i], maxY[i], mRenderer.getYLabels())));
\r
437 protected Rect getScreenR() {
\r
441 protected void setScreenR(Rect screenR) {
\r
442 mScreenR = screenR;
\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
456 * Draws the series.
\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
468 protected void drawSeries(XYSeries series, Canvas canvas, Paint paint, List<Float> pointsList,
\r
469 SimpleSeriesRenderer seriesRenderer, float yAxisValue, int seriesIndex, Orientation or,
\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
482 setStroke(stroke.getCap(), stroke.getJoin(), stroke.getMiter(), Style.FILL_AND_STROKE,
\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
494 paint.setTextSize(seriesRenderer.getChartValuesTextSize());
\r
495 if (or == Orientation.HORIZONTAL) {
\r
496 paint.setTextAlign(Align.CENTER);
\r
498 paint.setTextAlign(Align.LEFT);
\r
500 if (seriesRenderer.isDisplayChartValues()) {
\r
501 paint.setTextAlign(seriesRenderer.getChartValuesTextAlign());
\r
502 drawChartValuesText(canvas, series, seriesRenderer, paint, points, seriesIndex, startIndex);
\r
504 if (stroke != null) {
\r
505 setStroke(cap, join, miter, style, pathEffect, paint);
\r
509 private void setStroke(Cap cap, Join join, float miter, Style style, PathEffect pathEffect,
\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
519 * The graphical representation of the series values as text.
\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
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
539 drawText(canvas, getLabel(series.getY(startIndex)), points[0], points[1]
\r
540 - renderer.getChartValuesSpacing(), paint, 0);
\r
542 drawText(canvas, getLabel(series.getY(startIndex + 1)), points[2], points[3]
\r
543 - renderer.getChartValuesSpacing(), paint, 0);
\r
545 previousPointX = points[2];
\r
546 previousPointY = points[3];
\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
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
567 * The graphical representation of a text, to handle both HORIZONTAL and
\r
568 * VERTICAL orientations and extra rotation angles.
\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
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
581 // canvas.scale(1 / mScale, mScale);
\r
582 canvas.rotate(angle, x, y);
\r
584 drawString(canvas, text, x, y, paint);
\r
586 canvas.rotate(-angle, x, y);
\r
587 // canvas.scale(mScale, 1 / mScale);
\r
592 * Transform the canvas such as it can handle both HORIZONTAL and VERTICAL
\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
599 private void transform(Canvas canvas, float angle, boolean inverse) {
\r
601 canvas.scale(1 / mScale, mScale);
\r
602 canvas.translate(mTranslate, -mTranslate);
\r
603 canvas.rotate(-angle, mCenter.getX(), mCenter.getY());
\r
605 canvas.rotate(angle, mCenter.getX(), mCenter.getY());
\r
606 canvas.translate(-mTranslate, mTranslate);
\r
607 canvas.scale(mScale, 1 / mScale);
\r
612 * The graphical representation of the labels on the X axis.
\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
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
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
640 paint.setColor(mRenderer.getGridColor());
\r
641 canvas.drawLine(xLabel, bottom, xLabel, top, paint);
\r
644 drawXTextLabels(xTextLabelLocations, canvas, paint, showLabels, left, top, bottom,
\r
645 xPixelsPerUnit, minX, maxX);
\r
649 * The graphical representation of the labels on the X axis.
\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
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
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
689 paint.setColor(mRenderer.getGridColor());
\r
690 canvas.drawLine(left, yLabel, right, yLabel, paint);
\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
700 paint.setColor(mRenderer.getGridColor());
\r
701 canvas.drawLine(right, yLabel, left, yLabel, paint);
\r
709 * The graphical representation of the text labels on the X axis.
\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
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
724 boolean showCustomTextGrid = mRenderer.isShowCustomTextGrid();
\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
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
745 public XYMultipleSeriesRenderer getRenderer() {
\r
749 public XYMultipleSeriesDataset getDataset() {
\r
753 public double[] getCalcRange(int scale) {
\r
754 return mCalcRange.get(scale);
\r
757 public void setCalcRange(double[] range, int scale) {
\r
758 mCalcRange.put(scale, range);
\r
761 public double[] toRealPoint(float screenX, float screenY) {
\r
762 return toRealPoint(screenX, screenY, 0);
\r
765 public double[] toScreenPoint(double[] realPoint) {
\r
766 return toScreenPoint(realPoint, 0);
\r
769 private int getLabelLinePos(Align align) {
\r
771 if (align == Align.LEFT) {
\r
778 * Transforms a screen point to a real coordinates point.
\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
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
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
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
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
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
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
832 return super.getSeriesAndPointForScreenCoordinate(screenPoint);
\r
836 * The graphical representation of a series.
\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
846 public abstract void drawSeries(Canvas canvas, Paint paint, float[] points,
\r
847 SimpleSeriesRenderer seriesRenderer, float yAxisValue, int seriesIndex, int startIndex);
\r
850 * Returns the clickable areas for all passed points
\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
859 protected abstract ClickableArea[] clickableAreasForPoints(float[] points, double[] values,
\r
860 float yAxisValue, int seriesIndex, int startIndex);
\r
863 * Returns if the chart should display the null values.
\r
865 * @return if null values should be rendered
\r
867 protected boolean isRenderNullValues() {
\r
872 * Returns if the chart should display the points as a certain shape.
\r
874 * @param renderer the series renderer
\r
876 public boolean isRenderPoints(SimpleSeriesRenderer renderer) {
\r
881 * Returns the default axis minimum.
\r
883 * @return the default axis minimum
\r
885 public double getDefaultMinimum() {
\r
886 return MathHelper.NULL_VALUE;
\r
890 * Returns the scatter chart to be used for drawing the data points.
\r
892 * @return the data points scatter chart
\r
894 public ScatterChart getPointsChart() {
\r
899 * Returns the chart type identifier.
\r
901 * @return the chart type
\r
903 public abstract String getChartType();
\r