/*
* Copyright (C) 2012 www.amsoft.cn
*
* 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 com.ab.view.chart;
import java.io.Serializable;
import java.util.List;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import com.ab.view.chart.XYMultipleSeriesRenderer.Orientation;
// TODO: Auto-generated Javadoc
/**
* An abstract class to be implemented by the chart rendering classes.
*/
public abstract class AbstractChart implements Serializable {
/**
* The graphical representation of the 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 abstract void draw(Canvas canvas, int x, int y, int width, int height, Paint paint);
/**
* Draws the chart background.
*
* @param renderer the chart renderer
* @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 used for drawing
* @param newColor if a new color is to be used
* @param color the color to be used
*/
protected void drawBackground(DefaultRenderer renderer, Canvas canvas, int x, int y, int width,
int height, Paint paint, boolean newColor, int color) {
if (renderer.isApplyBackgroundColor() || newColor) {
if (newColor) {
paint.setColor(color);
} else {
paint.setColor(renderer.getBackgroundColor());
}
paint.setStyle(Style.FILL);
canvas.drawRect(x, y, x + width, y + height, paint);
}
}
/**
* Draws the chart legend.
*
* @param canvas the canvas to paint to
* @param renderer the series renderer
* @param titles the titles to go to the legend
* @param left the left X value of the area to draw to
* @param right the right X value of the area to draw to
* @param y the y value of the area to draw to
* @param width the width of the area to draw to
* @param height the height of the area to draw to
* @param legendSize the legend size
* @param paint the paint to be used for drawing
* @param calculate if only calculating the legend size
*
* @return the legend height
*/
protected int drawLegend(Canvas canvas, DefaultRenderer renderer, String[] titles, int left,
int right, int y, int width, int height, int legendSize, Paint paint, boolean calculate) {
float size = 32;
if (renderer.isShowLegend()) {
float currentX = left;
float currentY = y + height - legendSize + size;
paint.setTextAlign(Align.LEFT);
paint.setTextSize(renderer.getLegendTextSize());
int sLength = Math.min(titles.length, renderer.getSeriesRendererCount());
for (int i = 0; i < sLength; i++) {
final float lineSize = getLegendShapeWidth(i);
String text = titles[i];
if (titles.length == renderer.getSeriesRendererCount()) {
paint.setColor(renderer.getSeriesRendererAt(i).getColor());
} else {
paint.setColor(Color.LTGRAY);
}
float[] widths = new float[text.length()];
paint.getTextWidths(text, widths);
float sum = 0;
for (float value : widths) {
sum += value;
}
float extraSize = lineSize + 10 + sum;
float currentWidth = currentX + extraSize;
if (i > 0 && getExceed(currentWidth, renderer, right, width)) {
currentX = left;
currentY += renderer.getLegendTextSize();
size += renderer.getLegendTextSize();
currentWidth = currentX + extraSize;
}
if (getExceed(currentWidth, renderer, right, width)) {
float maxWidth = right - currentX - lineSize - 10;
if (isVertical(renderer)) {
maxWidth = width - currentX - lineSize - 10;
}
int nr = paint.breakText(text, true, maxWidth, widths);
text = text.substring(0, nr) + "...";
}
if (!calculate) {
//标签说明
drawLegendShape(canvas, renderer.getSeriesRendererAt(i), currentX, currentY, i, paint);
drawString(canvas, text, currentX + lineSize + 5, currentY + 5, paint);
}
currentX += extraSize;
}
}
return Math.round(size + renderer.getLegendTextSize());
}
/**
* Draw a multiple lines string.
*
* @param canvas the canvas to paint to
* @param text the text to be painted
* @param x the x value of the area to draw to
* @param y the y value of the area to draw to
* @param paint the paint to be used for drawing
*/
protected void drawString(Canvas canvas, String text, float x, float y, Paint paint) {
String[] lines = text.split("\n");
Rect rect = new Rect();
int yOff = 0;
for (int i = 0; i < lines.length; ++i) {
canvas.drawText(lines[i], x, y + yOff, paint);
paint.getTextBounds(lines[i], 0, lines[i].length(), rect);
yOff = yOff + rect.height() + 5; // space between lines is 5
}
}
/**
* Calculates if the current width exceeds the total width.
*
* @param currentWidth the current width
* @param renderer the renderer
* @param right the right side pixel value
* @param width the total width
* @return if the current width exceeds the total width
*/
protected boolean getExceed(float currentWidth, DefaultRenderer renderer, int right, int width) {
boolean exceed = currentWidth > right;
if (isVertical(renderer)) {
exceed = currentWidth > width;
}
return exceed;
}
/**
* Checks if the current chart is rendered as vertical.
*
* @param renderer the renderer
* @return if the chart is rendered as a vertical one
*/
public boolean isVertical(DefaultRenderer renderer) {
return renderer instanceof XYMultipleSeriesRenderer
&& ((XYMultipleSeriesRenderer) renderer).getOrientation() == Orientation.VERTICAL;
}
/**
* Makes sure the fraction digit is not displayed, if not needed.
*
* @param label the input label value
* @return the label without the useless fraction digit
*/
protected String getLabel(double label) {
String text = "";
if (label == Math.round(label)) {
text = Math.round(label) + "";
} else {
text = label + "";
}
return text;
}
/**
* Calculate draw points.
*
* @param p1x the p1x
* @param p1y the p1y
* @param p2x the p2x
* @param p2y the p2y
* @param screenHeight the screen height
* @param screenWidth the screen width
* @return the float[]
*/
private static float[] calculateDrawPoints(float p1x, float p1y, float p2x, float p2y,
int screenHeight, int screenWidth) {
float drawP1x;
float drawP1y;
float drawP2x;
float drawP2y;
if (p1y > screenHeight) {
// Intersection with the top of the screen
float m = (p2y - p1y) / (p2x - p1x);
drawP1x = (screenHeight - p1y + m * p1x) / m;
drawP1y = screenHeight;
if (drawP1x < 0) {
// If Intersection is left of the screen we calculate the intersection
// with the left border
drawP1x = 0;
drawP1y = p1y - m * p1x;
} else if (drawP1x > screenWidth) {
// If Intersection is right of the screen we calculate the intersection
// with the right border
drawP1x = screenWidth;
drawP1y = m * screenWidth + p1y - m * p1x;
}
} else if (p1y < 0) {
float m = (p2y - p1y) / (p2x - p1x);
drawP1x = (-p1y + m * p1x) / m;
drawP1y = 0;
if (drawP1x < 0) {
drawP1x = 0;
drawP1y = p1y - m * p1x;
} else if (drawP1x > screenWidth) {
drawP1x = screenWidth;
drawP1y = m * screenWidth + p1y - m * p1x;
}
} else {
// If the point is in the screen use it
drawP1x = p1x;
drawP1y = p1y;
}
if (p2y > screenHeight) {
float m = (p2y - p1y) / (p2x - p1x);
drawP2x = (screenHeight - p1y + m * p1x) / m;
drawP2y = screenHeight;
if (drawP2x < 0) {
drawP2x = 0;
drawP2y = p1y - m * p1x;
} else if (drawP2x > screenWidth) {
drawP2x = screenWidth;
drawP2y = m * screenWidth + p1y - m * p1x;
}
} else if (p2y < 0) {
float m = (p2y - p1y) / (p2x - p1x);
drawP2x = (-p1y + m * p1x) / m;
drawP2y = 0;
if (drawP2x < 0) {
drawP2x = 0;
drawP2y = p1y - m * p1x;
} else if (drawP2x > screenWidth) {
drawP2x = screenWidth;
drawP2y = m * screenWidth + p1y - m * p1x;
}
} else {
// If the point is in the screen use it
drawP2x = p2x;
drawP2y = p2y;
}
return new float[] { drawP1x, drawP1y, drawP2x, drawP2y };
}
/**
* The graphical representation of a path.
*
* @param canvas the canvas to paint to
* @param points the points that are contained in the path to paint
* @param paint the paint to be used for painting
* @param circular if the path ends with the start point
*/
protected void drawPath(Canvas canvas, float[] points, Paint paint, boolean circular) {
Path path = new Path();
int height = canvas.getHeight();
int width = canvas.getWidth();
float[] tempDrawPoints;
if (points.length < 4) {
return;
}
tempDrawPoints = calculateDrawPoints(points[0], points[1], points[2], points[3], height, width);
path.moveTo(tempDrawPoints[0], tempDrawPoints[1]);
path.lineTo(tempDrawPoints[2], tempDrawPoints[3]);
for (int i = 4; i < points.length; i += 2) {
if ((points[i - 1] < 0 && points[i + 1] < 0)
|| (points[i - 1] > height && points[i + 1] > height)) {
continue;
}
tempDrawPoints = calculateDrawPoints(points[i - 2], points[i - 1], points[i], points[i + 1],
height, width);
if (!circular) {
path.moveTo(tempDrawPoints[0], tempDrawPoints[1]);
}
path.lineTo(tempDrawPoints[2], tempDrawPoints[3]);
}
if (circular) {
path.lineTo(points[0], points[1]);
}
canvas.drawPath(path, paint);
}
/**
* Returns the legend shape width.
*
* @param seriesIndex the series index
* @return the legend shape width
*/
public abstract int getLegendShapeWidth(int seriesIndex);
/**
* The graphical representation of the legend shape.
*
* @param canvas the canvas to paint to
* @param renderer the series renderer
* @param x the x value of the point the shape should be drawn at
* @param y the y value of the point the shape should be drawn at
* @param seriesIndex the series index
* @param paint the paint to be used for drawing
*/
public abstract void drawLegendShape(Canvas canvas, SimpleSeriesRenderer renderer, float x,
float y, int seriesIndex, Paint paint);
/**
* Calculates the best text to fit into the available space.
*
* @param text the entire text
* @param width the width to fit the text into
* @param paint the paint
* @return the text to fit into the space
*/
private String getFitText(String text, float width, Paint paint) {
String newText = text;
int length = text.length();
int diff = 0;
while (paint.measureText(newText) > width && diff < length) {
diff++;
newText = text.substring(0, length - diff) + "...";
}
if (diff == length) {
newText = "...";
}
return newText;
}
/**
* Calculates the current legend size.
*
* @param renderer the renderer
* @param defaultHeight the default height
* @param extraHeight the added extra height
* @return the legend size
*/
protected int getLegendSize(DefaultRenderer renderer, int defaultHeight, float extraHeight) {
int legendSize = renderer.getLegendHeight();
if (renderer.isShowLegend() && legendSize == 0) {
legendSize = defaultHeight;
}
if (!renderer.isShowLegend() && renderer.isShowLabels()) {
legendSize = (int) (renderer.getLabelsTextSize() * 4 / 3 + extraHeight);
}
return legendSize;
}
/**
* Draws a text label.
*
* @param canvas the canvas
* @param labelText the label text
* @param renderer the renderer
* @param prevLabelsBounds the previous rendered label bounds
* @param centerX the round chart center on X axis
* @param centerY the round chart center on Y axis
* @param shortRadius the short radius for the round chart
* @param longRadius the long radius for the round chart
* @param currentAngle the current angle
* @param angle the label extra angle
* @param left the left side
* @param right the right side
* @param color the label color
* @param paint the paint
* @param line if a line to the label should be drawn
*/
protected void drawLabel(Canvas canvas, String labelText, DefaultRenderer renderer,
List<RectF> prevLabelsBounds, int centerX, int centerY, float shortRadius, float longRadius,
float currentAngle, float angle, int left, int right, int color, Paint paint, boolean line) {
if (renderer.isShowLabels()) {
paint.setColor(color);
double rAngle = Math.toRadians(90 - (currentAngle + angle / 2));
double sinValue = Math.sin(rAngle);
double cosValue = Math.cos(rAngle);
int x1 = Math.round(centerX + (float) (shortRadius * sinValue));
int y1 = Math.round(centerY + (float) (shortRadius * cosValue));
int x2 = Math.round(centerX + (float) (longRadius * sinValue));
int y2 = Math.round(centerY + (float) (longRadius * cosValue));
float size = renderer.getLabelsTextSize();
float extra = Math.max(size / 2, 10);
paint.setTextAlign(Align.LEFT);
if (x1 > x2) {
extra = -extra;
paint.setTextAlign(Align.RIGHT);
}
float xLabel = x2 + extra;
float yLabel = y2;
float width = right - xLabel;
if (x1 > x2) {
width = xLabel - left;
}
labelText = getFitText(labelText, width, paint);
float widthLabel = paint.measureText(labelText);
boolean okBounds = false;
while (!okBounds && line) {
boolean intersects = false;
int length = prevLabelsBounds.size();
for (int j = 0; j < length && !intersects; j++) {
RectF prevLabelBounds = prevLabelsBounds.get(j);
if (prevLabelBounds.intersects(xLabel, yLabel, xLabel + widthLabel, yLabel + size)) {
intersects = true;
yLabel = Math.max(yLabel, prevLabelBounds.bottom);
}
}
okBounds = !intersects;
}
if (line) {
y2 = (int) (yLabel - size / 2);
canvas.drawLine(x1, y1, x2, y2, paint);
canvas.drawLine(x2, y2, x2 + extra, y2, paint);
} else {
paint.setTextAlign(Align.CENTER);
}
canvas.drawText(labelText, xLabel, yLabel, paint);
if (line) {
prevLabelsBounds.add(new RectF(xLabel, yLabel, xLabel + widthLabel, yLabel + size));
}
}
}
/**
* Checks if is null value.
*
* @param value the value
* @return true, if is null value
*/
public boolean isNullValue(double value) {
return Double.isNaN(value) || Double.isInfinite(value) || value == MathHelper.NULL_VALUE;
}
/**
* Given screen coordinates, returns the series and point indexes of a chart
* element. If there is no chart element (line, point, bar, etc) at those
* coordinates, null is returned.
*
* @param screenPoint the screen point
* @return the series and point indexes
*/
public SeriesSelection getSeriesAndPointForScreenCoordinate(Point screenPoint) {
return null;
}
}