X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=android-libraries%2Fachartengine%2Fsrc%2Forg%2Fachartengine%2Fchart%2FXYChart.java;fp=android-libraries%2Fachartengine%2Fsrc%2Forg%2Fachartengine%2Fchart%2FXYChart.java;h=6b531d60b395856d52fda81781559e667f447ae8;hb=dfe153139b917cc91dddc1efa0298d4204616462;hp=0000000000000000000000000000000000000000;hpb=a2fe0ba2c348102bb0c6486b201be097523c2c9a;p=debian%2Fopenrocket diff --git a/android-libraries/achartengine/src/org/achartengine/chart/XYChart.java b/android-libraries/achartengine/src/org/achartengine/chart/XYChart.java new file mode 100644 index 00000000..6b531d60 --- /dev/null +++ b/android-libraries/achartengine/src/org/achartengine/chart/XYChart.java @@ -0,0 +1,905 @@ +/** + * Copyright (C) 2009 - 2012 SC 4ViewSoft SRL + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.achartengine.chart; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedMap; + +import org.achartengine.model.Point; +import org.achartengine.model.SeriesSelection; +import org.achartengine.model.XYMultipleSeriesDataset; +import org.achartengine.model.XYSeries; +import org.achartengine.renderer.BasicStroke; +import org.achartengine.renderer.DefaultRenderer; +import org.achartengine.renderer.SimpleSeriesRenderer; +import org.achartengine.renderer.XYMultipleSeriesRenderer; +import org.achartengine.renderer.XYMultipleSeriesRenderer.Orientation; +import org.achartengine.util.MathHelper; + +import android.graphics.Canvas; +import android.graphics.DashPathEffect; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Paint.Cap; +import android.graphics.Paint.Join; +import android.graphics.Paint.Style; +import android.graphics.PathEffect; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Typeface; + +/** + * The XY chart rendering class. + */ +public abstract class XYChart extends AbstractChart { + /** The multiple series dataset. */ + protected XYMultipleSeriesDataset mDataset; + /** The multiple series renderer. */ + protected XYMultipleSeriesRenderer mRenderer; + /** The current scale value. */ + private float mScale; + /** The current translate value. */ + private float mTranslate; + /** The canvas center point. */ + private Point mCenter; + /** The visible chart area, in screen coordinates. */ + private Rect mScreenR; + /** The calculated range. */ + private final Map mCalcRange = new HashMap(); + + /** + * The clickable areas for all points. The array index is the series index, + * and the RectF list index is the point index in that series. + */ + private Map> clickableAreas = new HashMap>(); + + protected XYChart() { + } + + /** + * Builds a new XY chart instance. + * + * @param dataset the multiple series dataset + * @param renderer the multiple series renderer + */ + public XYChart(XYMultipleSeriesDataset dataset, XYMultipleSeriesRenderer renderer) { + mDataset = dataset; + mRenderer = renderer; + } + + // TODO: javadoc + protected void setDatasetRenderer(XYMultipleSeriesDataset dataset, + XYMultipleSeriesRenderer renderer) { + mDataset = dataset; + mRenderer = renderer; + } + + /** + * The graphical representation of the XY chart. + * + * @param canvas the canvas to paint to + * @param x the top left x value of the view to draw to + * @param y the top left y value of the view to draw to + * @param width the width of the view to draw to + * @param height the height of the view to draw to + * @param paint the paint + */ + public void draw(Canvas canvas, int x, int y, int width, int height, Paint paint) { + paint.setAntiAlias(mRenderer.isAntialiasing()); + int legendSize = getLegendSize(mRenderer, height / 5, mRenderer.getAxisTitleTextSize()); + int[] margins = mRenderer.getMargins(); + int left = x + margins[1]; + int top = y + margins[0]; + int right = x + width - margins[3]; + int sLength = mDataset.getSeriesCount(); + String[] titles = new String[sLength]; + for (int i = 0; i < sLength; i++) { + titles[i] = mDataset.getSeriesAt(i).getTitle(); + } + if (mRenderer.isFitLegend() && mRenderer.isShowLegend()) { + legendSize = drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize, + paint, true); + } + int bottom = y + height - margins[2] - legendSize; + if (mScreenR == null) { + mScreenR = new Rect(); + } + mScreenR.set(left, top, right, bottom); + drawBackground(mRenderer, canvas, x, y, width, height, paint, false, DefaultRenderer.NO_COLOR); + + if (paint.getTypeface() == null + || !paint.getTypeface().toString().equals(mRenderer.getTextTypefaceName()) + || paint.getTypeface().getStyle() != mRenderer.getTextTypefaceStyle()) { + paint.setTypeface(Typeface.create(mRenderer.getTextTypefaceName(), + mRenderer.getTextTypefaceStyle())); + } + Orientation or = mRenderer.getOrientation(); + if (or == Orientation.VERTICAL) { + right -= legendSize; + bottom += legendSize - 20; + } + int angle = or.getAngle(); + boolean rotate = angle == 90; + mScale = (float) (height) / width; + mTranslate = Math.abs(width - height) / 2; + if (mScale < 1) { + mTranslate *= -1; + } + mCenter = new Point((x + width) / 2, (y + height) / 2); + if (rotate) { + transform(canvas, angle, false); + } + + int maxScaleNumber = -Integer.MAX_VALUE; + for (int i = 0; i < sLength; i++) { + maxScaleNumber = Math.max(maxScaleNumber, mDataset.getSeriesAt(i).getScaleNumber()); + } + maxScaleNumber++; + if (maxScaleNumber < 0) { + return; + } + double[] minX = new double[maxScaleNumber]; + double[] maxX = new double[maxScaleNumber]; + double[] minY = new double[maxScaleNumber]; + double[] maxY = new double[maxScaleNumber]; + boolean[] isMinXSet = new boolean[maxScaleNumber]; + boolean[] isMaxXSet = new boolean[maxScaleNumber]; + boolean[] isMinYSet = new boolean[maxScaleNumber]; + boolean[] isMaxYSet = new boolean[maxScaleNumber]; + + for (int i = 0; i < maxScaleNumber; i++) { + minX[i] = mRenderer.getXAxisMin(i); + maxX[i] = mRenderer.getXAxisMax(i); + minY[i] = mRenderer.getYAxisMin(i); + maxY[i] = mRenderer.getYAxisMax(i); + isMinXSet[i] = mRenderer.isMinXSet(i); + isMaxXSet[i] = mRenderer.isMaxXSet(i); + isMinYSet[i] = mRenderer.isMinYSet(i); + isMaxYSet[i] = mRenderer.isMaxYSet(i); + if (mCalcRange.get(i) == null) { + mCalcRange.put(i, new double[4]); + } + } + double[] xPixelsPerUnit = new double[maxScaleNumber]; + double[] yPixelsPerUnit = new double[maxScaleNumber]; + for (int i = 0; i < sLength; i++) { + XYSeries series = mDataset.getSeriesAt(i); + int scale = series.getScaleNumber(); + if (series.getItemCount() == 0) { + continue; + } + if (!isMinXSet[scale]) { + double minimumX = series.getMinX(); + minX[scale] = Math.min(minX[scale], minimumX); + mCalcRange.get(scale)[0] = minX[scale]; + } + if (!isMaxXSet[scale]) { + double maximumX = series.getMaxX(); + maxX[scale] = Math.max(maxX[scale], maximumX); + mCalcRange.get(scale)[1] = maxX[scale]; + } + if (!isMinYSet[scale]) { + double minimumY = series.getMinY(); + minY[scale] = Math.min(minY[scale], (float) minimumY); + mCalcRange.get(scale)[2] = minY[scale]; + } + if (!isMaxYSet[scale]) { + double maximumY = series.getMaxY(); + maxY[scale] = Math.max(maxY[scale], (float) maximumY); + mCalcRange.get(scale)[3] = maxY[scale]; + } + } + for (int i = 0; i < maxScaleNumber; i++) { + if (maxX[i] - minX[i] != 0) { + xPixelsPerUnit[i] = (right - left) / (maxX[i] - minX[i]); + } + if (maxY[i] - minY[i] != 0) { + yPixelsPerUnit[i] = (float) ((bottom - top) / (maxY[i] - minY[i])); + } + } + + boolean hasValues = false; + // use a linked list for these reasons: + // 1) Avoid a large contiguous memory allocation + // 2) We don't need random seeking, only sequential reading/writing, so + // linked list makes sense + clickableAreas = new HashMap>(); + for (int i = 0; i < sLength; i++) { + XYSeries series = mDataset.getSeriesAt(i); + int scale = series.getScaleNumber(); + if (series.getItemCount() == 0) { + continue; + } + + hasValues = true; + SimpleSeriesRenderer seriesRenderer = mRenderer.getSeriesRendererAt(i); + + // int originalValuesLength = series.getItemCount(); + // int valuesLength = originalValuesLength; + // int length = valuesLength * 2; + + List points = new ArrayList(); + List values = new ArrayList(); + float yAxisValue = Math.min(bottom, (float) (bottom + yPixelsPerUnit[scale] * minY[scale])); + LinkedList clickableArea = new LinkedList(); + + clickableAreas.put(i, clickableArea); + + SortedMap range = series.getRange(minX[scale], maxX[scale], 1); + int startIndex = -1; + + for (Entry value : range.entrySet()) { + + double xValue = value.getKey(); + double yValue = value.getValue(); + if (startIndex < 0) { + startIndex = series.getIndexForKey(xValue); + } + + // points.add((float) (left + xPixelsPerUnit[scale] + // * (value.getKey().floatValue() - minX[scale]))); + // points.add((float) (bottom - yPixelsPerUnit[scale] + // * (value.getValue().floatValue() - minY[scale]))); + values.add(value.getKey()); + values.add(value.getValue()); + + if (!isNullValue(yValue)) { + points.add((float) (left + xPixelsPerUnit[scale] * (xValue - minX[scale]))); + points.add((float) (bottom - yPixelsPerUnit[scale] * (yValue - minY[scale]))); + } else if (isRenderNullValues()) { + points.add((float) (left + xPixelsPerUnit[scale] * (xValue - minX[scale]))); + points.add((float) (bottom - yPixelsPerUnit[scale] * (-minY[scale]))); + } else { + if (points.size() > 0) { + drawSeries(series, canvas, paint, points, seriesRenderer, yAxisValue, i, or, startIndex); + ClickableArea[] clickableAreasForSubSeries = clickableAreasForPoints( + MathHelper.getFloats(points), MathHelper.getDoubles(values), yAxisValue, i, + startIndex); + clickableArea.addAll(Arrays.asList(clickableAreasForSubSeries)); + points.clear(); + values.clear(); + } + clickableArea.add(null); + } + } + + if (points.size() > 0) { + drawSeries(series, canvas, paint, points, seriesRenderer, yAxisValue, i, or, startIndex); + ClickableArea[] clickableAreasForSubSeries = clickableAreasForPoints( + MathHelper.getFloats(points), MathHelper.getDoubles(values), yAxisValue, i, startIndex); + clickableArea.addAll(Arrays.asList(clickableAreasForSubSeries)); + } + } + + // draw stuff over the margins such as data doesn't render on these areas + drawBackground(mRenderer, canvas, x, bottom, width, height - bottom, paint, true, + mRenderer.getMarginsColor()); + drawBackground(mRenderer, canvas, x, y, width, margins[0], paint, true, + mRenderer.getMarginsColor()); + if (or == Orientation.HORIZONTAL) { + drawBackground(mRenderer, canvas, x, y, left - x, height - y, paint, true, + mRenderer.getMarginsColor()); + drawBackground(mRenderer, canvas, right, y, margins[3], height - y, paint, true, + mRenderer.getMarginsColor()); + } else if (or == Orientation.VERTICAL) { + drawBackground(mRenderer, canvas, right, y, width - right, height - y, paint, true, + mRenderer.getMarginsColor()); + drawBackground(mRenderer, canvas, x, y, left - x, height - y, paint, true, + mRenderer.getMarginsColor()); + } + + boolean showLabels = mRenderer.isShowLabels() && hasValues; + boolean showGridX = mRenderer.isShowGridX(); + boolean showCustomTextGrid = mRenderer.isShowCustomTextGrid(); + if (showLabels || showGridX) { + List xLabels = getValidLabels(getXLabels(minX[0], maxX[0], mRenderer.getXLabels())); + Map> allYLabels = getYLabels(minY, maxY, maxScaleNumber); + + int xLabelsLeft = left; + if (showLabels) { + paint.setColor(mRenderer.getXLabelsColor()); + paint.setTextSize(mRenderer.getLabelsTextSize()); + paint.setTextAlign(mRenderer.getXLabelsAlign()); + if (mRenderer.getXLabelsAlign() == Align.LEFT) { + xLabelsLeft += mRenderer.getLabelsTextSize() / 4; + } + } + drawXLabels(xLabels, mRenderer.getXTextLabelLocations(), canvas, paint, xLabelsLeft, top, + bottom, xPixelsPerUnit[0], minX[0], maxX[0]); + drawYLabels(allYLabels, canvas, paint, maxScaleNumber, left, right, bottom, yPixelsPerUnit, + minY); + + if (showLabels) { + paint.setColor(mRenderer.getLabelsColor()); + for (int i = 0; i < maxScaleNumber; i++) { + Align axisAlign = mRenderer.getYAxisAlign(i); + Double[] yTextLabelLocations = mRenderer.getYTextLabelLocations(i); + for (Double location : yTextLabelLocations) { + if (minY[i] <= location && location <= maxY[i]) { + float yLabel = (float) (bottom - yPixelsPerUnit[i] + * (location.doubleValue() - minY[i])); + String label = mRenderer.getYTextLabel(location, i); + paint.setColor(mRenderer.getYLabelsColor(i)); + paint.setTextAlign(mRenderer.getYLabelsAlign(i)); + if (or == Orientation.HORIZONTAL) { + if (axisAlign == Align.LEFT) { + canvas.drawLine(left + getLabelLinePos(axisAlign), yLabel, left, yLabel, paint); + drawText(canvas, label, left, yLabel - 2, paint, mRenderer.getYLabelsAngle()); + } else { + canvas.drawLine(right, yLabel, right + getLabelLinePos(axisAlign), yLabel, paint); + drawText(canvas, label, right, yLabel - 2, paint, mRenderer.getYLabelsAngle()); + } + + if (showCustomTextGrid) { + paint.setColor(mRenderer.getGridColor()); + canvas.drawLine(left, yLabel, right, yLabel, paint); + } + } else { + canvas.drawLine(right - getLabelLinePos(axisAlign), yLabel, right, yLabel, paint); + drawText(canvas, label, right + 10, yLabel - 2, paint, mRenderer.getYLabelsAngle()); + if (showCustomTextGrid) { + paint.setColor(mRenderer.getGridColor()); + canvas.drawLine(right, yLabel, left, yLabel, paint); + } + } + } + } + } + } + + if (showLabels) { + paint.setColor(mRenderer.getLabelsColor()); + float size = mRenderer.getAxisTitleTextSize(); + paint.setTextSize(size); + paint.setTextAlign(Align.CENTER); + if (or == Orientation.HORIZONTAL) { + drawText(canvas, mRenderer.getXTitle(), x + width / 2, + bottom + mRenderer.getLabelsTextSize() * 4 / 3 + size, paint, 0); + for (int i = 0; i < maxScaleNumber; i++) { + Align axisAlign = mRenderer.getYAxisAlign(i); + if (axisAlign == Align.LEFT) { + drawText(canvas, mRenderer.getYTitle(i), x + size, y + height / 2, paint, -90); + } else { + drawText(canvas, mRenderer.getYTitle(i), x + width, y + height / 2, paint, -90); + } + } + paint.setTextSize(mRenderer.getChartTitleTextSize()); + drawText(canvas, mRenderer.getChartTitle(), x + width / 2, + y + mRenderer.getChartTitleTextSize(), paint, 0); + } else if (or == Orientation.VERTICAL) { + drawText(canvas, mRenderer.getXTitle(), x + width / 2, y + height - size, paint, -90); + drawText(canvas, mRenderer.getYTitle(), right + 20, y + height / 2, paint, 0); + paint.setTextSize(mRenderer.getChartTitleTextSize()); + drawText(canvas, mRenderer.getChartTitle(), x + size, top + height / 2, paint, 0); + } + } + } + if (or == Orientation.HORIZONTAL) { + drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize, paint, false); + } else if (or == Orientation.VERTICAL) { + transform(canvas, angle, true); + drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize, paint, false); + transform(canvas, angle, false); + } + if (mRenderer.isShowAxes()) { + paint.setColor(mRenderer.getAxesColor()); + canvas.drawLine(left, bottom, right, bottom, paint); + boolean rightAxis = false; + for (int i = 0; i < maxScaleNumber && !rightAxis; i++) { + rightAxis = mRenderer.getYAxisAlign(i) == Align.RIGHT; + } + if (or == Orientation.HORIZONTAL) { + canvas.drawLine(left, top, left, bottom, paint); + if (rightAxis) { + canvas.drawLine(right, top, right, bottom, paint); + } + } else if (or == Orientation.VERTICAL) { + canvas.drawLine(right, top, right, bottom, paint); + } + } + if (rotate) { + transform(canvas, angle, true); + } + } + + protected List getXLabels(double min, double max, int count) { + return MathHelper.getLabels(min, max, count); + } + + protected Map> getYLabels(double[] minY, double[] maxY, int maxScaleNumber) { + Map> allYLabels = new HashMap>(); + for (int i = 0; i < maxScaleNumber; i++) { + allYLabels.put(i, + getValidLabels(MathHelper.getLabels(minY[i], maxY[i], mRenderer.getYLabels()))); + } + return allYLabels; + } + + protected Rect getScreenR() { + return mScreenR; + } + + protected void setScreenR(Rect screenR) { + mScreenR = screenR; + } + + private List getValidLabels(List labels) { + List result = new ArrayList(labels); + for (Double label : labels) { + if (label.isNaN()) { + result.remove(label); + } + } + return result; + } + + /** + * Draws the series. + * + * @param series the series + * @param canvas the canvas + * @param paint the paint object + * @param pointsList the points to be rendered + * @param seriesRenderer the series renderer + * @param yAxisValue the y axis value in pixels + * @param seriesIndex the series index + * @param or the orientation + * @param startIndex the start index of the rendering points + */ + protected void drawSeries(XYSeries series, Canvas canvas, Paint paint, List pointsList, + SimpleSeriesRenderer seriesRenderer, float yAxisValue, int seriesIndex, Orientation or, + int startIndex) { + BasicStroke stroke = seriesRenderer.getStroke(); + Cap cap = paint.getStrokeCap(); + Join join = paint.getStrokeJoin(); + float miter = paint.getStrokeMiter(); + PathEffect pathEffect = paint.getPathEffect(); + Style style = paint.getStyle(); + if (stroke != null) { + PathEffect effect = null; + if (stroke.getIntervals() != null) { + effect = new DashPathEffect(stroke.getIntervals(), stroke.getPhase()); + } + setStroke(stroke.getCap(), stroke.getJoin(), stroke.getMiter(), Style.FILL_AND_STROKE, + effect, paint); + } + float[] points = MathHelper.getFloats(pointsList); + drawSeries(canvas, paint, points, seriesRenderer, yAxisValue, seriesIndex, startIndex); + if (isRenderPoints(seriesRenderer)) { + ScatterChart pointsChart = getPointsChart(); + if (pointsChart != null) { + pointsChart.drawSeries(canvas, paint, points, seriesRenderer, yAxisValue, seriesIndex, + startIndex); + } + } + paint.setTextSize(seriesRenderer.getChartValuesTextSize()); + if (or == Orientation.HORIZONTAL) { + paint.setTextAlign(Align.CENTER); + } else { + paint.setTextAlign(Align.LEFT); + } + if (seriesRenderer.isDisplayChartValues()) { + paint.setTextAlign(seriesRenderer.getChartValuesTextAlign()); + drawChartValuesText(canvas, series, seriesRenderer, paint, points, seriesIndex, startIndex); + } + if (stroke != null) { + setStroke(cap, join, miter, style, pathEffect, paint); + } + } + + private void setStroke(Cap cap, Join join, float miter, Style style, PathEffect pathEffect, + Paint paint) { + paint.setStrokeCap(cap); + paint.setStrokeJoin(join); + paint.setStrokeMiter(miter); + paint.setPathEffect(pathEffect); + paint.setStyle(style); + } + + /** + * The graphical representation of the series values as text. + * + * @param canvas the canvas to paint to + * @param series the series to be painted + * @param renderer the series renderer + * @param paint the paint to be used for drawing + * @param points the array of points to be used for drawing the series + * @param seriesIndex the index of the series currently being drawn + * @param startIndex the start index of the rendering points + */ + protected void drawChartValuesText(Canvas canvas, XYSeries series, SimpleSeriesRenderer renderer, + Paint paint, float[] points, int seriesIndex, int startIndex) { + if (points.length > 1) { // there are more than one point + // record the first point's position + float previousPointX = points[0]; + float previousPointY = points[1]; + for (int k = 0; k < points.length; k += 2) { + if (k == 2) { // decide whether to display first two points' values or not + if (Math.abs(points[2]- points[0]) > 100 || Math.abs(points[3] - points[1]) > 100) { + // first point + drawText(canvas, getLabel(series.getY(startIndex)), points[0], points[1] + - renderer.getChartValuesSpacing(), paint, 0); + // second point + drawText(canvas, getLabel(series.getY(startIndex + 1)), points[2], points[3] + - renderer.getChartValuesSpacing(), paint, 0); + + previousPointX = points[2]; + previousPointY = points[3]; + } + } else if (k > 2) { + // compare current point's position with the previous point's, if they are not too close, display + if (Math.abs(points[k]- previousPointX) > 100 || Math.abs(points[k+1] - previousPointY) > 100) { + drawText(canvas, getLabel(series.getY(startIndex + k / 2)), points[k], points[k + 1] + - renderer.getChartValuesSpacing(), paint, 0); + previousPointX = points[k]; + previousPointY = points[k+1]; + } + } + } + } else { // if only one point, display it + for (int k = 0; k < points.length; k += 2) { + drawText(canvas, getLabel(series.getY(startIndex + k / 2)), points[k], points[k + 1] + - renderer.getChartValuesSpacing(), paint, 0); + } + } + } + + /** + * The graphical representation of a text, to handle both HORIZONTAL and + * VERTICAL orientations and extra rotation angles. + * + * @param canvas the canvas to paint to + * @param text the text to be rendered + * @param x the X axis location of the text + * @param y the Y axis location of the text + * @param paint the paint to be used for drawing + * @param extraAngle the text angle + */ + protected void drawText(Canvas canvas, String text, float x, float y, Paint paint, + float extraAngle) { + float angle = -mRenderer.getOrientation().getAngle() + extraAngle; + if (angle != 0) { + // canvas.scale(1 / mScale, mScale); + canvas.rotate(angle, x, y); + } + drawString(canvas, text, x, y, paint); + if (angle != 0) { + canvas.rotate(-angle, x, y); + // canvas.scale(mScale, 1 / mScale); + } + } + + /** + * Transform the canvas such as it can handle both HORIZONTAL and VERTICAL + * orientations. + * + * @param canvas the canvas to paint to + * @param angle the angle of rotation + * @param inverse if the inverse transform needs to be applied + */ + private void transform(Canvas canvas, float angle, boolean inverse) { + if (inverse) { + canvas.scale(1 / mScale, mScale); + canvas.translate(mTranslate, -mTranslate); + canvas.rotate(-angle, mCenter.getX(), mCenter.getY()); + } else { + canvas.rotate(angle, mCenter.getX(), mCenter.getY()); + canvas.translate(-mTranslate, mTranslate); + canvas.scale(mScale, 1 / mScale); + } + } + + /** + * The graphical representation of the labels on the X axis. + * + * @param xLabels the X labels values + * @param xTextLabelLocations the X text label locations + * @param canvas the canvas to paint to + * @param paint the paint to be used for drawing + * @param left the left value of the labels area + * @param top the top value of the labels area + * @param bottom the bottom value of the labels area + * @param xPixelsPerUnit the amount of pixels per one unit in the chart labels + * @param minX the minimum value on the X axis in the chart + * @param maxX the maximum value on the X axis in the chart + */ + protected void drawXLabels(List xLabels, Double[] xTextLabelLocations, Canvas canvas, + Paint paint, int left, int top, int bottom, double xPixelsPerUnit, double minX, double maxX) { + int length = xLabels.size(); + boolean showLabels = mRenderer.isShowLabels(); + boolean showGridY = mRenderer.isShowGridY(); + for (int i = 0; i < length; i++) { + double label = xLabels.get(i); + float xLabel = (float) (left + xPixelsPerUnit * (label - minX)); + if (showLabels) { + paint.setColor(mRenderer.getXLabelsColor()); + canvas.drawLine(xLabel, bottom, xLabel, bottom + mRenderer.getLabelsTextSize() / 3, paint); + drawText(canvas, getLabel(label), xLabel, bottom + mRenderer.getLabelsTextSize() * 4 / 3, + paint, mRenderer.getXLabelsAngle()); + } + if (showGridY) { + paint.setColor(mRenderer.getGridColor()); + canvas.drawLine(xLabel, bottom, xLabel, top, paint); + } + } + drawXTextLabels(xTextLabelLocations, canvas, paint, showLabels, left, top, bottom, + xPixelsPerUnit, minX, maxX); + } + + /** + * The graphical representation of the labels on the X axis. + * + * @param allYLabels the Y labels values + * @param canvas the canvas to paint to + * @param paint the paint to be used for drawing + * @param maxScaleNumber the maximum scale number + * @param left the left value of the labels area + * @param right the right value of the labels area + * @param bottom the bottom value of the labels area + * @param yPixelsPerUnit the amount of pixels per one unit in the chart labels + * @param minY the minimum value on the Y axis in the chart + */ + protected void drawYLabels(Map> allYLabels, Canvas canvas, Paint paint, + int maxScaleNumber, int left, int right, int bottom, double[] yPixelsPerUnit, double[] minY) { + Orientation or = mRenderer.getOrientation(); + boolean showGridX = mRenderer.isShowGridX(); + boolean showLabels = mRenderer.isShowLabels(); + for (int i = 0; i < maxScaleNumber; i++) { + paint.setTextAlign(mRenderer.getYLabelsAlign(i)); + List yLabels = allYLabels.get(i); + int length = yLabels.size(); + for (int j = 0; j < length; j++) { + double label = yLabels.get(j); + Align axisAlign = mRenderer.getYAxisAlign(i); + boolean textLabel = mRenderer.getYTextLabel(label, i) != null; + float yLabel = (float) (bottom - yPixelsPerUnit[i] * (label - minY[i])); + if (or == Orientation.HORIZONTAL) { + if (showLabels && !textLabel) { + paint.setColor(mRenderer.getYLabelsColor(i)); + if (axisAlign == Align.LEFT) { + canvas.drawLine(left + getLabelLinePos(axisAlign), yLabel, left, yLabel, paint); + drawText(canvas, getLabel(label), left, yLabel - 2, paint, + mRenderer.getYLabelsAngle()); + } else { + canvas.drawLine(right, yLabel, right + getLabelLinePos(axisAlign), yLabel, paint); + drawText(canvas, getLabel(label), right, yLabel - 2, paint, + mRenderer.getYLabelsAngle()); + } + } + if (showGridX) { + paint.setColor(mRenderer.getGridColor()); + canvas.drawLine(left, yLabel, right, yLabel, paint); + } + } else if (or == Orientation.VERTICAL) { + if (showLabels && !textLabel) { + paint.setColor(mRenderer.getYLabelsColor(i)); + canvas.drawLine(right - getLabelLinePos(axisAlign), yLabel, right, yLabel, paint); + drawText(canvas, getLabel(label), right + 10, yLabel - 2, paint, + mRenderer.getYLabelsAngle()); + } + if (showGridX) { + paint.setColor(mRenderer.getGridColor()); + canvas.drawLine(right, yLabel, left, yLabel, paint); + } + } + } + } + } + + /** + * The graphical representation of the text labels on the X axis. + * + * @param xTextLabelLocations the X text label locations + * @param canvas the canvas to paint to + * @param paint the paint to be used for drawing + * @param left the left value of the labels area + * @param top the top value of the labels area + * @param bottom the bottom value of the labels area + * @param xPixelsPerUnit the amount of pixels per one unit in the chart labels + * @param minX the minimum value on the X axis in the chart + * @param maxX the maximum value on the X axis in the chart + */ + protected void drawXTextLabels(Double[] xTextLabelLocations, Canvas canvas, Paint paint, + boolean showLabels, int left, int top, int bottom, double xPixelsPerUnit, double minX, + double maxX) { + boolean showCustomTextGrid = mRenderer.isShowCustomTextGrid(); + if (showLabels) { + paint.setColor(mRenderer.getXLabelsColor()); + for (Double location : xTextLabelLocations) { + if (minX <= location && location <= maxX) { + float xLabel = (float) (left + xPixelsPerUnit * (location.doubleValue() - minX)); + paint.setColor(mRenderer.getXLabelsColor()); + canvas + .drawLine(xLabel, bottom, xLabel, bottom + mRenderer.getLabelsTextSize() / 3, paint); + drawText(canvas, mRenderer.getXTextLabel(location), xLabel, + bottom + mRenderer.getLabelsTextSize() * 4 / 3, paint, mRenderer.getXLabelsAngle()); + if (showCustomTextGrid) { + paint.setColor(mRenderer.getGridColor()); + canvas.drawLine(xLabel, bottom, xLabel, top, paint); + } + } + } + } + } + + // TODO: docs + public XYMultipleSeriesRenderer getRenderer() { + return mRenderer; + } + + public XYMultipleSeriesDataset getDataset() { + return mDataset; + } + + public double[] getCalcRange(int scale) { + return mCalcRange.get(scale); + } + + public void setCalcRange(double[] range, int scale) { + mCalcRange.put(scale, range); + } + + public double[] toRealPoint(float screenX, float screenY) { + return toRealPoint(screenX, screenY, 0); + } + + public double[] toScreenPoint(double[] realPoint) { + return toScreenPoint(realPoint, 0); + } + + private int getLabelLinePos(Align align) { + int pos = 4; + if (align == Align.LEFT) { + pos = -pos; + } + return pos; + } + + /** + * Transforms a screen point to a real coordinates point. + * + * @param screenX the screen x axis value + * @param screenY the screen y axis value + * @return the real coordinates point + */ + public double[] toRealPoint(float screenX, float screenY, int scale) { + double realMinX = mRenderer.getXAxisMin(scale); + double realMaxX = mRenderer.getXAxisMax(scale); + double realMinY = mRenderer.getYAxisMin(scale); + double realMaxY = mRenderer.getYAxisMax(scale); + return new double[] { + (screenX - mScreenR.left) * (realMaxX - realMinX) / mScreenR.width() + realMinX, + (mScreenR.top + mScreenR.height() - screenY) * (realMaxY - realMinY) / mScreenR.height() + + realMinY }; + } + + public double[] toScreenPoint(double[] realPoint, int scale) { + double realMinX = mRenderer.getXAxisMin(scale); + double realMaxX = mRenderer.getXAxisMax(scale); + double realMinY = mRenderer.getYAxisMin(scale); + double realMaxY = mRenderer.getYAxisMax(scale); + if (!mRenderer.isMinXSet(scale) || !mRenderer.isMaxXSet(scale) || !mRenderer.isMinXSet(scale) + || !mRenderer.isMaxYSet(scale)) { + double[] calcRange = getCalcRange(scale); + realMinX = calcRange[0]; + realMaxX = calcRange[1]; + realMinY = calcRange[2]; + realMaxY = calcRange[3]; + } + return new double[] { + (realPoint[0] - realMinX) * mScreenR.width() / (realMaxX - realMinX) + mScreenR.left, + (realMaxY - realPoint[1]) * mScreenR.height() / (realMaxY - realMinY) + mScreenR.top }; + } + + public SeriesSelection getSeriesAndPointForScreenCoordinate(final Point screenPoint) { + if (clickableAreas != null) + for (int seriesIndex = clickableAreas.size() - 1; seriesIndex >= 0; seriesIndex--) { + // series 0 is drawn first. Then series 1 is drawn on top, and series 2 + // on top of that. + // we want to know what the user clicked on, so traverse them in the + // order they appear on the screen. + int pointIndex = 0; + if (clickableAreas.get(seriesIndex) != null) { + RectF rectangle; + for (ClickableArea area : clickableAreas.get(seriesIndex)) { + rectangle = area.getRect(); + if (rectangle != null && rectangle.contains(screenPoint.getX(), screenPoint.getY())) { + return new SeriesSelection(seriesIndex, pointIndex, area.getX(), area.getY()); + } + pointIndex++; + } + } + } + return super.getSeriesAndPointForScreenCoordinate(screenPoint); + } + + /** + * The graphical representation of a series. + * + * @param canvas the canvas to paint to + * @param paint the paint to be used for drawing + * @param points the array of points to be used for drawing the series + * @param seriesRenderer the series renderer + * @param yAxisValue the minimum value of the y axis + * @param seriesIndex the index of the series currently being drawn + * @param startIndex the start index of the rendering points + */ + public abstract void drawSeries(Canvas canvas, Paint paint, float[] points, + SimpleSeriesRenderer seriesRenderer, float yAxisValue, int seriesIndex, int startIndex); + + /** + * Returns the clickable areas for all passed points + * + * @param points the array of points + * @param values the array of values of each point + * @param yAxisValue the minimum value of the y axis + * @param seriesIndex the index of the series to which the points belong + * @return an array of rectangles with the clickable area + * @param startIndex the start index of the rendering points + */ + protected abstract ClickableArea[] clickableAreasForPoints(float[] points, double[] values, + float yAxisValue, int seriesIndex, int startIndex); + + /** + * Returns if the chart should display the null values. + * + * @return if null values should be rendered + */ + protected boolean isRenderNullValues() { + return false; + } + + /** + * Returns if the chart should display the points as a certain shape. + * + * @param renderer the series renderer + */ + public boolean isRenderPoints(SimpleSeriesRenderer renderer) { + return false; + } + + /** + * Returns the default axis minimum. + * + * @return the default axis minimum + */ + public double getDefaultMinimum() { + return MathHelper.NULL_VALUE; + } + + /** + * Returns the scatter chart to be used for drawing the data points. + * + * @return the data points scatter chart + */ + public ScatterChart getPointsChart() { + return null; + } + + /** + * Returns the chart type identifier. + * + * @return the chart type + */ + public abstract String getChartType(); + +}