/*
* Copyright 2010 Laurent Moulinier, Christian Matzat and others
*
* 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.gafmedia.graph;
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
/**
* View that renders a line chart.
* <p/>
* based on code from GAFMEDIA Studio, initiated by Laurent Moulinier
* <a href="http://blog.gafmediastudio.com/2010/04/01/draw-a-line-chart-with-android/"></a>
*/
public class LineChartView extends BaseChartView {
private static final String TAG = BASE_TAG + LineChartView.class.getSimpleName();
public final int LABEL_X_SHOW = 7;
public final int LABEL_Y_SHOW = 6;
private boolean fillGraph;
private boolean showAverageLine;
private boolean showGrid;
private boolean zoomChart;
private boolean flipYScale;
private boolean showXRangeHeadline;
private int gridColor;
private int averageLineColor;
private int textColor;
private String title;
private List<LineChartData> lineChartDataList = new ArrayList<LineChartData>();
private Bitmap cachedBitmap;
private boolean initialDrawingIsPerformed;
public LineChartView(Context context) {
super(context);
}
public LineChartView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// this method draws the chart
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
switch (state) {
case WAIT:
return;
case IS_READY_TO_DRAW:
this.cachedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas cacheCanvas = new Canvas(this.cachedBitmap);
initialDraw(cacheCanvas);
canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
break;
default:
canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
}
}
private void initialDraw(Canvas canvas) {
Paint backgroundPaint = new Paint();
backgroundPaint.setStyle(Paint.Style.FILL);
backgroundPaint.setColor(backgroundColor);
canvas.drawPaint(backgroundPaint);
Paint linePaint = new Paint();
if (fillGraph) {
linePaint.setStyle(Paint.Style.FILL_AND_STROKE);
} else {
linePaint.setStyle(Paint.Style.FILL);
}
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth(1.5f);
Paint gridPaintDot = new Paint();
gridPaintDot.setColor(gridColor);
gridPaintDot.setAntiAlias(false);
gridPaintDot.setStyle(Paint.Style.STROKE);
gridPaintDot.setStrokeWidth(0.5f);
gridPaintDot.setPathEffect(new DashPathEffect(new float[]{5, 5}, 1));
Paint gridPaintFull = new Paint();
gridPaintFull.setColor(gridColor);
gridPaintFull.setAntiAlias(false);
gridPaintFull.setStyle(Paint.Style.STROKE);
gridPaintFull.setStrokeWidth(1.0f);
Paint titlePaint = new Paint();
titlePaint.setStyle(Paint.Style.FILL);
titlePaint.setTextAlign(Paint.Align.LEFT);
titlePaint.setColor(textColor);
titlePaint.setTextSize(10);
Rect textRect = new Rect();
//----------------------------------------------------------------------------------
// Get Y Max Values
//----------------------------------------------------------------------------------
String stringLabel;
float y;
int index;
double value;
float oldX;
float oldY;
float newX;
float newY;
double maxY = 0;
double minY;
int valuesCount = lineChartDataList.get(0).lineChartItems.size();
//----------------------------------------------------------------------------------
// GET Y MIN & MAX
//----------------------------------------------------------------------------------
for (LineChartData lineChartData : lineChartDataList) {
if (maxY < lineChartData.maxY) maxY = lineChartData.maxY;
}
if (zoomChart) {
minY = maxY;
for (LineChartData lineChartData : lineChartDataList) {
if (minY > lineChartData.minY) minY = lineChartData.minY;
}
} else {
minY = 0;
}
//----------------------------------------------------------------------------------
// DRAW Y AXIS
//----------------------------------------------------------------------------------
float step = (float) (maxY - minY) / (float) (LABEL_Y_SHOW);
float stepY = (float) (height - gapTop - gapBottom) / (float) (maxY - minY);
for (int i = 0; i <= LABEL_Y_SHOW; i++) {
y = ((i * step) * stepY);
oldX = (gapLeft - 2f);
oldY = (height - gapBottom - y);
newX = (float) (width - gapRight);
newY = (height - gapBottom - y);
if (i == 0) {
canvas.drawLine(oldX, oldY, newX, newY, gridPaintFull);
} else {
if (showGrid) {
canvas.drawLine(oldX, oldY, newX, newY, gridPaintDot);
}
}
if (flipYScale) {
value = maxY - (i * step);
} else {
value = minY + (i * step);
}
// TODO implement parameter for decimals
stringLabel = String.format("%3.2f",value);
canvas.drawText(stringLabel, 2, newY, titlePaint);
}
//----------------------------------------------------------------------------------
// DRAW X AXIS
//----------------------------------------------------------------------------------
float stepX = (float) (width - gapLeft - gapRight) / (float) (LABEL_X_SHOW - 1);
float IndexStep = (float) valuesCount / (float) LABEL_X_SHOW;
newY = (float) (height - gapBottom);
for (int j = 0; j < LABEL_X_SHOW; j++) {
index = (int) ((float) (j) * (float) IndexStep);
if (index >= valuesCount) index = valuesCount - 1;
if (j == LABEL_X_SHOW - 1) index = valuesCount - 1;
newX = ((float) gapLeft + (j * stepX));
if (j == 0) {
canvas.drawLine(newX, gapTop, newX, newY + 3, gridPaintFull);
} else {
if (showGrid) {
canvas.drawLine(newX, gapTop, newX, newY + 3, gridPaintDot);
}
}
LineChartItem chartItem = lineChartDataList.get(0).lineChartItems.get(index);
titlePaint.getTextBounds(chartItem.entry, 0, chartItem.entry.length(), textRect);
newX = newX - textRect.right / 2;
canvas.drawText(chartItem.entry, newX, (height - gapBottom) + 13, titlePaint);
}
//--------------------------------------------------------------------------
// Draw Graph Lines
//--------------------------------------------------------------------------
for (LineChartData chartData : lineChartDataList) {
float pointStepX = (float) (width - gapLeft - gapRight) / (valuesCount - 1f);
float pointStepY = (float) (height - gapTop - gapBottom) / (float) (maxY - minY);
float drawOffsetX = (float) gapLeft;
float drawOffsetY = (float) (height - gapBottom);
//--------------------------------------------------------------------------
LineChartItem chartItem = chartData.lineChartItems.get(0);
oldX = drawOffsetX;
if (flipYScale) {
oldY = drawOffsetY - pointStepY * (float) (maxY - chartItem.value);
} else {
oldY = drawOffsetY - pointStepY * (float) (chartItem.value - minY);
}
Path path = new Path();
for (int k = 0; k < valuesCount; k++) {
chartItem = chartData.lineChartItems.get(k);
if (flipYScale) {
newY = drawOffsetY - (pointStepY * (float) (maxY - chartItem.value));
} else {
newY = drawOffsetY - (pointStepY * (float) (chartItem.value - minY));
}
newX = drawOffsetX + (pointStepX * (float) k);
// set default linecolor
linePaint.setColor(chartData.lineColor);
if (chartItem.color != 0) {
// set special linecolor if available
linePaint.setColor(chartItem.color);
}
if (!fillGraph) {
canvas.drawLine(oldX, oldY, newX, newY, linePaint);
} else {
path.rewind();
path.moveTo(oldX, oldY);
path.lineTo(newX, newY);
path.lineTo(newX, drawOffsetY);
path.lineTo(oldX, drawOffsetY);
path.close();
canvas.drawPath(path, linePaint);
}
oldX = newX;
oldY = newY;
}
//--------------------------------------------------------------------------
// Average Line
//--------------------------------------------------------------------------
if (showAverageLine && chartData.hasAverageLine) {
Paint averagePaint = new Paint();
averagePaint.setColor(averageLineColor);
averagePaint.setStyle(Paint.Style.FILL);
averagePaint.setAntiAlias(true);
averagePaint.setStrokeWidth(1.5f);
averagePaint.setPathEffect(new DashPathEffect(new float[]{5, 5}, 1));
Paint averageTxtPaint = new Paint();
averageTxtPaint.setStyle(Paint.Style.FILL);
averageTxtPaint.setTextAlign(Paint.Align.LEFT);
averageTxtPaint.setColor(averageLineColor);
averageTxtPaint.setTextSize(10);
DecimalFormat FloatFormatter = new DecimalFormat("0.##");
String StrAverage = "" + FloatFormatter.format(chartData.graphAverage);
averageTxtPaint.getTextBounds(StrAverage, 0, StrAverage.length(), textRect);
newX = (float) (width - gapRight - textRect.right) - 3.0f;
newY = (float) (gapTop - textRect.top) + 3.0f;
canvas.drawText(StrAverage, newX, newY, averageTxtPaint);
if (flipYScale) {
newY = (float) (drawOffsetY - (pointStepY * (maxY - chartData.graphAverage)));
} else {
newY = (float) (drawOffsetY - (pointStepY * (chartData.graphAverage - minY)));
}
canvas.drawLine(gapLeft, newY, width - gapRight, newY, averagePaint);
}
}
//--------------------------------------------------------------------------
// title
//--------------------------------------------------------------------------
if (showXRangeHeadline) {
String xRangeTitle = lineChartDataList.get(0).lineChartItems.get(0).entry + " ... "
+ lineChartDataList.get(0).lineChartItems.get(valuesCount - 1).entry;
titlePaint.getTextBounds(xRangeTitle, 0, xRangeTitle.length(), textRect);
canvas.drawText(xRangeTitle, (width - textRect.right) / 2, 10, titlePaint);
}
if (title != null) {
titlePaint.getTextBounds(title, 0, title.length(), textRect);
canvas.drawText(title, (width - textRect.right) / 2, showXRangeHeadline ? 20 : 10, titlePaint);
}
state = IS_DRAWN;
}
/**
* Sets the geometry values of the chart.
* @param width the width
* @param height the height
* @param gapLeft gap from left
* @param gapRight gap from right
* @param gapTop gap from top
* @param gapBottom gap from bottom
*/
public void setGeometry(int width, int height, int gapLeft, int gapRight, int gapTop, int gapBottom) {
this.width = width;
this.height = height;
this.gapLeft = gapLeft;
this.gapRight = gapRight;
this.gapTop = gapTop;
this.gapBottom = gapBottom;
}
/**
* Sets the paramters that define how the line chart should be displayed
*
* @param title title of the whole chart
* @param backgroundColor color of the background
* @param gridColor color of the grid
* @param textColor color of the text (legend)
* @param fillGraph switches if the area below the line graph should be filled
* @param showAverageLine switches if an average line should be displayed
* @param showGrid switches if a grid should be drawn in the chart area
* @param zoomChart switches if the chart should be zoomed in. The chart area starts then from the min value and
* ends at the max value. Otherwise the chart will always start at zero.
* @param flipYScale switches if the Y-scale should be flipped. Otherwise the chart starts with min value at the
* bottom line up to the max value (see also parameter zoomChart)
* @param showXRangeHeadline switches if a headline generated from the x-range legend values should be displayed
*/
public void setSkinParams(String title, int backgroundColor, int gridColor, int textColor, int averageLineColor,
boolean fillGraph, boolean showAverageLine, boolean showGrid, boolean zoomChart,
boolean flipYScale, boolean showXRangeHeadline) {
this.title = title;
this.backgroundColor = backgroundColor;
this.gridColor = gridColor;
this.textColor = textColor;
this.averageLineColor = averageLineColor;
this.fillGraph = fillGraph;
this.showAverageLine = showAverageLine;
this.showGrid = showGrid;
this.zoomChart = zoomChart;
this.flipYScale = flipYScale;
this.showXRangeHeadline = showXRangeHeadline;
}
/**
* Add a new list of LineChartItems.
*
* @param lineChartItems list of LineChartItems
* @param lineColor Color of the graph line
* @param hasAverageLine The graph average line is computed out of this graph line. The last added line with this value
* set to <b>true</b> shows the average line.
*/
public void addData(List<LineChartItem> lineChartItems, int lineColor, boolean hasAverageLine) {
if (lineChartDataList.size() > 0) {
if (lineChartDataList.get(0).lineChartItems.size() != lineChartItems.size()) {
throw new IllegalArgumentException("Count of ChartItemValue is different.");
}
for (LineChartData lineChartData : lineChartDataList) {
if (hasAverageLine && lineChartData.hasAverageLine) {
lineChartData.hasAverageLine = false;
}
}
}
lineChartDataList.add(new LineChartData(lineChartItems, lineColor, hasAverageLine));
state = IS_READY_TO_DRAW;
}
// Inner class that holds the graphs values
private class LineChartData {
int lineColor;
List<LineChartItem> lineChartItems;
boolean hasAverageLine;
double maxY, minY;
float graphAverage;
LineChartData(List<LineChartItem> LineChartValues, int lineColor, boolean hasAverageLine) {
this.lineColor = lineColor;
this.lineChartItems = LineChartValues;
this.hasAverageLine = hasAverageLine;
computeMinMaxAverage();
}
// Computes min and max values for each graph
private void computeMinMaxAverage() {
for (LineChartItem chartItem : lineChartItems) {
if (maxY < chartItem.value) maxY = chartItem.value;
graphAverage += chartItem.value;
}
if (zoomChart) {
minY = maxY;
for (LineChartItem chartItem : lineChartItems) {
if (minY > chartItem.value) minY = chartItem.value;
}
} else {
minY = 0;
}
if (hasAverageLine) {
graphAverage = graphAverage / lineChartItems.size();
}
}
}
}
|