Commit 0d07084b by 王宇航

首頁、廚房單打印

parent 70fa4320
apply plugin: 'com.android.library'
//apply plugin: 'com.github.dcendents.android-maven'
group='com.github.huangyanbin'
android {
compileSdkVersion 25
buildToolsVersion '26.0.2'
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
}
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\Android\Sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.david.chart">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true">
</application>
</manifest>
package com.daivd.chart.component;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import com.daivd.chart.component.base.IChartTitle;
import com.daivd.chart.component.base.PercentComponent;
import com.daivd.chart.data.style.FontStyle;
/**
* 绘制标题
* @author huangyanbin
*/
public class ChartTitle extends PercentComponent<String> implements IChartTitle {
/**
* 图表标题最大占比
*/
private static final float MAX_PERCENT =0.4f;
/**
* 标题字体样式
*/
private FontStyle fontStyle= new FontStyle();
private Path path = new Path();
/**
* 设置标题占比
* @param percent 百分比
*/
@Override
public void setPercent(float percent) {
if(percent > MAX_PERCENT){
percent = MAX_PERCENT;
}
super.setPercent(percent);
}
/**
* 绘制标题
* <p>通过设置标题方位绘制标题</p>
* @param canvas 画布
* @param chartName 图表标题
* @param paint 画笔
*/
@Override
public void draw(Canvas canvas, String chartName, Paint paint) {
fontStyle.fillPaint(paint);
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
paint.setTextAlign(Paint.Align.LEFT);
float textHeight = fontMetrics.descent - fontMetrics.ascent;
int textWidth = (int)paint.measureText(chartName);
Rect rect = getRect();
int startY = rect.centerY();
int startX = rect.centerX();
path.rewind();
switch (direction) {
case TOP:
case BOTTOM:
startY-= textHeight/2;
startX -=textWidth/2;
canvas.drawText(chartName, startX, startY, paint);
break;
case LEFT:
case RIGHT:
path.moveTo(startX,rect.top);
path.lineTo(startX,rect.bottom);
canvas.drawTextOnPath(chartName,path,(rect.height()-textWidth)/2,0,paint);
break;
}
}
/**
* 获取标题字体样式
* @return 标题字体样式
*/
public FontStyle getFontStyle() {
return fontStyle;
}
/**
* 设置标题字体样式
* @param fontStyle 标题字体样式
*/
public void setFontStyle(FontStyle fontStyle) {
this.fontStyle = fontStyle;
}
}
package com.daivd.chart.component;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import com.daivd.chart.component.base.IEmpty;
import com.daivd.chart.data.style.FontStyle;
/**
* 空白提示
* @author huangyanbin
*/
public class EmptyView implements IEmpty {
/**
* 空白提示字体样式
*/
private FontStyle fontStyle = new FontStyle();
/**
* 空白文字
*/
private String emptyTip = "No Data";
/**
* 空白范围
*/
private Rect rect;
/**
* 绘制空白
* @param canvas 画布
* @param paint 画笔
*/
@Override
public void draw(Canvas canvas, Paint paint) {
draw(canvas,emptyTip,paint);
}
/**
* 获取空白字体样式
* @return 空白字体样式
*/
public FontStyle getFontStyle() {
return fontStyle;
}
/**
* 设置空白字体样式
* @param fontStyle 空白字体样式
*/
public void setFontStyle(FontStyle fontStyle) {
this.fontStyle = fontStyle;
}
/**
* 设置空白提示
* @return 空白提示
*/
@Override
public String getEmptyTip() {
return emptyTip;
}
/**
* 获取空白提示
* @param emptyTip 空白提示
*/
@Override
public void setEmptyTip(String emptyTip) {
this.emptyTip = emptyTip;
}
/**
* 计算空白大小
* <p>因为空白提示是占整个图表不需要计算</>
* @param chartRect 图表范围
*/
@Override
public void computeRect(Rect chartRect) {
rect = chartRect;
}
/**
* 绘制空白
* @param canvas 画布
* @param emptyTip 空白提示
* @param paint 画笔
*/
@Override
public void draw(Canvas canvas, String emptyTip, Paint paint) {
fontStyle.fillPaint(paint);
paint.setTextAlign(Paint.Align.LEFT);
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
int textHeight = (int) (fontMetrics.descent - fontMetrics.ascent);
int textWidth = (int) paint.measureText(emptyTip);
canvas.drawText(emptyTip,rect.left +(rect.right-rect.left-textWidth)/2,rect.top+(rect.bottom - rect.top-textHeight)/2,paint);
}
}
package com.daivd.chart.component;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import com.daivd.chart.component.base.ILegend;
import com.daivd.chart.component.base.PercentComponent;
import com.daivd.chart.data.ChartData;
import com.daivd.chart.data.ColumnData;
import com.daivd.chart.data.style.FontStyle;
import com.daivd.chart.listener.OnClickLegendListener;
import com.daivd.chart.provider.component.point.ILegendPoint;
import com.daivd.chart.provider.component.point.IPoint;
import com.daivd.chart.provider.component.point.LegendPoint;
import com.daivd.chart.provider.component.point.Point;
import java.util.List;
/**
* 图表图例
* @author huangyanbin
*/
public class Legend<C extends ColumnData> extends PercentComponent<ChartData<C>> implements ILegend<C> {
private FontStyle fontStyle;
private ILegendPoint point;
private int padding = 10;
private PointF pointF;
private boolean isSelectColumn = true;
private OnClickLegendListener<C> onClickLegendListener;
private boolean isDisplay = true;
public Legend(){
LegendPoint p = new LegendPoint();
p.getPointStyle().setWidth(p.getPointStyle().getWidth()*2);
point = p;
fontStyle = new FontStyle();
}
@Override
public void computeRect(Rect chartRect) {
if(isDisplay) {
super.computeRect(chartRect);
}
}
/**
* 绘制
* @param canvas 画布
* @param chartData 数据
* @param paint 画笔
*/
@Override
public void draw(Canvas canvas, ChartData<C> chartData, Paint paint) {
if(isDisplay) {
paint.setTextAlign(Paint.Align.LEFT);
Rect legendRect = getRect();
List<C> columnDataList = chartData.getColumnDataList();
int maxLegendNameLength = 0;
int columnDataSize = columnDataList.size();
String maxLengthColumnName = null;
for (int i = 0; i < columnDataSize; i++) {
ColumnData columnData = columnDataList.get(i);
String name = columnData.getName();
if (maxLegendNameLength < name.length()) {
maxLengthColumnName = name;
maxLegendNameLength = name.length();
}
}
fontStyle.fillPaint(paint);
int textWidth = (int) paint.measureText(maxLengthColumnName);//文本长度
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float textHeight = fontMetrics.descent - fontMetrics.ascent;
int maxLegendLength = (int) (point.getWidth() + padding * 3 + textWidth);
int columnSize = legendRect.width() / maxLegendLength; //列
columnSize = columnSize > 0 ? columnSize : 1;
int rowSize = columnDataSize / columnSize;
rowSize = rowSize > 0 ? rowSize : 1;
int perHeight = (int) (textHeight + padding);
int perWidth = legendRect.width() / columnSize;
int offsetY = (legendRect.height() - perHeight * rowSize) / 2;
offsetY = offsetY > 0 ? offsetY : 0;
int offsetX = columnDataSize < columnSize ? (columnSize - columnDataSize) * perWidth / 2 : 0;
for (int i = 0; i < columnDataList.size(); i++) {
int column = i % columnSize;
int row = i / columnSize;
int startX = offsetX + legendRect.left + column * perWidth + (perWidth - maxLegendLength) / 2;
int startY = legendRect.top + offsetY + row * perHeight;
C columnData = columnDataList.get(i);
String name = columnData.getName();
float pointWidth = point.getWidth();
float pointHeight = point.getHeight();
if (pointF != null && isClickRect(startX - pointWidth, startX + perWidth, startY - padding / 2, startY + perHeight + padding / 2)) {
if (isSelectColumn) {
columnData.setDraw(!columnData.isDraw());
}
pointF = null;
if (onClickLegendListener != null) {
onClickLegendListener.onClickLegend(columnData, this);
}
}
paint.setColor(columnData.getColor());
drawPoint(canvas, columnData.isDraw(), startX, (int) (startY - textHeight / 2 + pointHeight / 2), paint);
startX += pointWidth + padding;
drawText(canvas, startX, startY, name, paint);
}
}
}
@Override
public void setFontStyle(FontStyle fontStyle) {
this.fontStyle = fontStyle;
}
/**
* Determine whether to click or not rect
*/
private boolean isClickRect(float left, float right, float top, float bottom){
if(pointF != null) {
return pointF.x >= left && pointF.x <= right && pointF.y >= top && pointF.y <= bottom;
}
return false;
}
/**
* 绘制文字
*/
private void drawText(Canvas canvas, int startX, int startY, String content, Paint paint) {
fontStyle.fillPaint(paint);
canvas.drawText(content, startX, startY, paint);
}
private void drawPoint(Canvas canvas,boolean isDraw,int x, int y, Paint paint){
float w = point.getWidth();
x += w/2;
point.drawPoint(canvas,x,y,0,!isDraw,paint);
}
@Override
public FontStyle getFontStyle() {
return fontStyle;
}
@Override
public void onClickLegend(PointF pointF) {
this.pointF = pointF;
}
@Override
public void setOnClickLegendListener(OnClickLegendListener<C> onClickLegendListener) {
this.onClickLegendListener = onClickLegendListener;
}
@Override
public int getPadding() {
return padding;
}
@Override
public void setPadding(int padding) {
this.padding = padding;
}
public IPoint getPoint() {
return point;
}
public void setPoint(ILegendPoint point) {
this.point = point;
}
@Override
public void setSelectColumn(boolean selectColumn) {
isSelectColumn = selectColumn;
}
@Override
public void setDisplay(boolean isDisplay) {
this.isDisplay = isDisplay;
}
}
package com.daivd.chart.component;
import android.content.ContentValues;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.View;
import com.daivd.chart.data.PicOption;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Created by huang on 2017/10/30.
* 图片生成
*/
public class PicGeneration<V extends View> {
public boolean save(V v,PicOption option){
getChartBitmap(v);
return saveToGallery(v,option);
}
private Bitmap getChartBitmap(V v) {
// 创建一个bitmap 根据我们自定义view的大小
Bitmap returnedBitmap = Bitmap.createBitmap(v.getWidth(),
v.getHeight(), Bitmap.Config.RGB_565);
// 绑定canvas
Canvas canvas = new Canvas(returnedBitmap);
// 获取视图的背景
Drawable bgDrawable = v.getBackground();
if (bgDrawable != null)
// 如果有就绘制
bgDrawable.draw(canvas);
else
// 没有就绘制白色
canvas.drawColor(Color.WHITE);
// 绘制
v.draw(canvas);
return returnedBitmap;
}
private boolean saveToGallery(V v, PicOption option) {
String mFilePath;
// 控制图片质量
if (option.getQuality() < 0 || option.getQuality() > 100)
option.setQuality(50);
long currentTime = System.currentTimeMillis();
File extBaseDir = Environment.getExternalStorageDirectory();
File file = new File(extBaseDir.getAbsolutePath() + "/DCIM/" + option.getSubFolderPath());
if (!file.exists()) {
if (!file.mkdirs()) {
return false;
}
}
String mimeType = "";
String fileName = option.getFileName();
switch (option.getFormat()) {
case PNG:
mimeType = "image/png";
if (!fileName.endsWith(".png"))
fileName += ".png";
break;
case WEBP:
mimeType = "image/webp";
if (!fileName.endsWith(".webp"))
fileName += ".webp";
break;
case JPEG:
default:
mimeType = "image/jpeg";
if (!(fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")))
fileName += ".jpg";
break;
}
mFilePath = file.getAbsolutePath() + "/" + fileName;
FileOutputStream out = null;
try {
out = new FileOutputStream(mFilePath);
Bitmap b = getChartBitmap(v);
b.compress(option.getFormat(), option.getQuality(), out);
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
long size = new File(mFilePath).length();
ContentValues values = new ContentValues(8);
// store the details
values.put(MediaStore.Images.Media.TITLE, fileName);
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
values.put(MediaStore.Images.Media.DATE_ADDED, currentTime);
values.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
values.put(MediaStore.Images.Media.DESCRIPTION, option.getFileDescription());
values.put(MediaStore.Images.Media.ORIENTATION, 0);
values.put(MediaStore.Images.Media.DATA, mFilePath);
values.put(MediaStore.Images.Media.SIZE, size);
return v.getContext().getContentResolver().
insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) != null;
}
}
package com.daivd.chart.component.axis;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.view.Gravity;
import com.daivd.chart.component.base.IAxis;
import com.daivd.chart.data.ChartData;
import com.daivd.chart.data.format.IFormat;
import com.daivd.chart.data.BarData;
import com.daivd.chart.data.ScaleData;
import com.daivd.chart.data.style.FontStyle;
import com.daivd.chart.data.style.LineStyle;
import com.daivd.chart.matrix.MatrixHelper;
/**
* 基本轴 负责轴的计算方位和绘制
* @author huangyanbin
*/
public abstract class BaseAxis<V> implements IAxis<V> {
/**
* 轴线样式
*/
protected LineStyle axisStyle = new LineStyle();
/**
* 刻度样式
*/
protected FontStyle scaleStyle = new FontStyle();
/**
* 网格样式
*/
protected LineStyle gridStyle = new LineStyle(); //网格样式
/**
* 是否绘制网格
*/
protected boolean isDrawGrid; //是否绘制网格
/**
* 是否显示轴线
*/
protected boolean isShowAxisLine = true;
/**
* 轴文字位置
*/
protected int gravity = Gravity.CENTER;
/**
* 轴方位(上下左右,横向轴可以设置上下,竖轴可以设置左右)
*/
protected int direction;
/**
* 是否是线性图
* 因为线性图一般从0开始绘制轴文字,而柱状图从每个间隔中间开始绘制
*/
protected boolean isLine;
/**
* 文字格式化
*/
private IFormat<V> format;
/**
* 是否显示轴
*/
private boolean isDisplay = true;
/**
*设置是否绘制网格
* @param drawGrid 是否绘制
*/
public void setDrawGrid(boolean drawGrid) {
isDrawGrid = drawGrid;
}
/**
* 获取轴样式
* 默认赋予样式,可以直接get来使用
* @return 轴样式
*/
public LineStyle getAxisStyle() {
return axisStyle;
}
/**
* 设置轴样式
* 如果你对LineStyle提供的样式不满意,可以自定义新的LineStyle
* @param axisStyle 轴样式
*/
public void setAxisStyle(LineStyle axisStyle) {
this.axisStyle = axisStyle;
}
/**
* 获取刻度字体样式
* 默认赋予样式,可以直接get来使用
* @return 刻度字体样式
*/
public FontStyle getScaleStyle() {
return scaleStyle;
}
/**
* 设置刻度字体样式
* 如果你对FontStyle提供的样式不满意,可以自定义新的FontStyle
* @param scaleStyle 刻度字体样式
*/
public void setScaleStyle(FontStyle scaleStyle) {
this.scaleStyle = scaleStyle;
}
/**
* 获取网格样式
* @return 网格样式
*/
public LineStyle getGridStyle() {
return gridStyle;
}
/**
* 设置网格样式
* @param gridStyle 网格样式
*/
public void setGridStyle(LineStyle gridStyle) {
this.gridStyle = gridStyle;
}
/**
* 设置轴偏移方位
* @param gravity 偏移方位
*/
public void setGravity(int gravity) {
this.gravity = gravity;
}
/**
* 是否是线性图表
* 因为线性图一般从0开始绘制轴文字,而柱状图从每个间隔中间开始绘制
* @param isLine 是否是线性图表
*/
public void isLine(boolean isLine) {
this.isLine = isLine;
}
/**
* 是否显示轴
* @return 是否显示轴
*/
public boolean isDisplay() {
return isDisplay;
}
/**
* 绘制轴
* 判断是否需要绘制以及调用绘制轴刻度和绘制轴线
* @param canvas 画布
* @param rect 绘制范围
* @param helper 缩放移动辅助类
* @param paint 画笔
* @param chartData 图表数据
*/
@Override
public void draw(Canvas canvas, Rect rect, MatrixHelper helper, Paint paint, ChartData<? extends BarData> chartData) {
if(isDisplay) {
drawScale(canvas, rect, helper, paint, chartData);
drawAxis(canvas, rect, paint, chartData);
}
}
/**
* 绘制轴刻度
* @param canvas 画布
* @param rect 绘制范围
* @param helper 缩放移动辅助类
* @param paint 画笔
* @param chartData 图表数据
*/
protected void drawScale(Canvas canvas, Rect rect, MatrixHelper helper, Paint paint, ChartData<? extends BarData> chartData) {
ScaleData scaleData = chartData.getScaleData();
Rect clipRect = new Rect(rect);
Rect scaleRect = scaleData.scaleRect;
Rect zoomRect = helper.getZoomProviderRect(scaleData.getOffsetRect(new Rect(rect), scaleRect));
chartData.getScaleData().zoom = helper.getZoom();
if (direction == AxisDirection.BOTTOM || direction == AxisDirection.TOP) {
zoomRect.top = rect.top;
zoomRect.bottom = rect.bottom;
clipRect.left = rect.left + scaleRect.left;
clipRect.right = rect.right - scaleRect.right;
} else {
zoomRect.left = rect.left;
zoomRect.right = rect.right;
clipRect.top = rect.top + scaleRect.top;
clipRect.bottom = rect.bottom - scaleRect.bottom;
}
drawScale(canvas, zoomRect, clipRect, paint, chartData);
}
/**
* 绘制轴线
* @param canvas 画布
* @param rect 绘制范围
* @param paint 画笔
* @param chartData 图表数据
*/
protected void drawAxis(Canvas canvas, Rect rect, Paint paint, ChartData<? extends BarData> chartData) {
if(isShowAxisLine) {
Rect scaleRect = chartData.getScaleData().scaleRect;
axisStyle.fillPaint(paint);
int[] r = calculation(rect, scaleRect);
Path path = new Path();
path.moveTo(r[0],r[1]);
path.lineTo(r[2],r[3]);
canvas.drawPath(path, paint);
}
}
/**
* 计算轴范围
* @param rect 原始范围
* @param scaleRect 缩放范围
* @return 轴上下左右的大小
*/
protected abstract int[] calculation(Rect rect, Rect scaleRect);
/**
* 提供给子类绘制刻度抽象方法
* @param canvas 画布
* @param rect 原始范围
* @param clipRect 裁切轴之后范围
* @param paint 画笔
* @param chartData 图表数据
*/
protected abstract void drawScale(Canvas canvas, Rect rect, Rect clipRect, Paint paint,ChartData<? extends BarData> chartData);
/**
* 获取文字格式化
* @return 文字格式化
*/
public IFormat<V> getFormat() {
return format;
}
/**
* 设置文字格式化
* @param format 文字格式化
*/
@Override
public void setFormat(IFormat<V> format) {
this.format = format;
}
/**
* 是否显示轴线
* @return 是否显示轴线
*/
public boolean isShowAxisLine() {
return isShowAxisLine;
}
/**
* 设置是否显示轴线
* @param showAxisLine 是否显示轴线
*/
public void setShowAxisLine(boolean showAxisLine) {
isShowAxisLine = showAxisLine;
}
/**
* 设置是否显示轴
* @param isShow 是否显示轴
*/
@Override
public void setDisplay(boolean isShow) {
this.isDisplay = isShow;
}
}
package com.daivd.chart.component.axis;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.view.Gravity;
import com.daivd.chart.data.ChartData;
import com.daivd.chart.data.BarData;
import com.daivd.chart.data.ScaleData;
import com.daivd.chart.exception.ChartException;
import java.util.List;
/**
* 图表横轴
* @author huangyanbin
*/
public class HorizontalAxis extends BaseAxis<String> {
/**
* 横轴默认构造方法
* 默认方位在底部
*/
public HorizontalAxis() {
direction = AxisDirection.BOTTOM;
}
/**
* 文字旋转角度
*/
private int rotateAngle= 0;
/**
* 文字是否旋转
*/
private boolean isRotateAngle;
/**
* 文字宽度
*/
private int textWidth;
/**
* 文字高度
*/
private int textHeight;
/**
* 旋转文字高度
*/
private int rotateTextHeight;
/**
* 是否需要偏移来完整显示左右两边文字
*/
private boolean isShowFullValue; //是否显示全文字
/**
* 刻度数据
*/
private ScaleData scaleData;
/**
* 计算刻度大小
* <p>通过计算刻度的宽高得到轴的大小,然后保存到scaleData对象中,以便后面的计算</p>
* @param chartData 图表数据
* @param rect 图表原始范围
* @param paint 画笔
*/
@Override
public void computeScale(ChartData<? extends BarData> chartData, Rect rect, Paint paint) {
if(isDisplay()) {
scaleData = chartData.getScaleData();
scaleStyle.fillPaint(paint);
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
textHeight = (int) (fontMetrics.descent - fontMetrics.ascent);
int maxLength = 0;
String maxLengthXData = "1";
for (String xData : chartData.getCharXDataList()) {
String formatData = formatData(xData);
if (maxLength < formatData.length()) {
maxLengthXData = formatData;
maxLength = formatData.length();
}
}
textWidth = (int) paint.measureText(maxLengthXData);
//计算旋转的高宽
int dis = textHeight;
if (isRotateAngle) {
int tempHeight = (int) Math.abs(textWidth * Math.sin(rotateAngle * Math.PI / 180)
+ textHeight * Math.cos(rotateAngle * Math.PI / 180));
int tempWidth = (int) Math.abs(textWidth * Math.cos(rotateAngle * Math.PI / 180)
+ textHeight * Math.sin(rotateAngle * Math.PI / 180));
rotateTextHeight = tempHeight;
dis += rotateTextHeight;
textWidth = tempWidth;
}
dis += (int) (scaleStyle.getPadding() * 2 + axisStyle.getWidth());
if (direction == AxisDirection.BOTTOM) {
scaleData.scaleRect.bottom = dis;
} else {
scaleData.scaleRect.top = dis;
}
}
}
/**
* 绘制刻度
* <p>通过zoomRect计算出每个刻度的宽度,迭代绘制刻度</p>
* @param canvas 画布
* @param zoomRect 缩放之后范围
* @param rect 原始范围
* @param paint 画笔
* @param chartData 图表数据
*/
protected void drawScale(Canvas canvas, Rect zoomRect, Rect rect, Paint paint, ChartData<? extends BarData> chartData) {
ScaleData scaleData = chartData.getScaleData();
List<String> groupDataList = chartData.getCharXDataList();
int rowSize = scaleData.rowSize;
int groupSize = groupDataList.size();
if (groupSize != rowSize) {
throw new ChartException("Horizontal Vertical axis data inconsistency");
}
float startY;
if (direction == AxisDirection.BOTTOM) {
startY = zoomRect.bottom -scaleData.scaleRect.bottom/2;
} else {
startY = zoomRect.top + scaleData.scaleRect.top/2;
}
int left = zoomRect.left ;
int width = zoomRect.right - left;
double perWidth = ((double) width) / (isLine? groupSize -1 : groupSize);
int filterMultiple = (int) (textWidth / perWidth +1);
for (int i = 0; i < groupSize; i++) {
String content = groupDataList.get(i);
int startX = getGravityStartX(left, i, perWidth);
//留1px缓冲
if (startX >= rect.left-1 && startX<= rect.right+1) {
if( i % filterMultiple == 0) {
drawText(canvas, content,startX, startY,i, paint);
drawGrid(canvas, startX, rect, scaleData.scaleRect, paint);
}
}
}
}
/**
* 获取刻度起始X的位置
* <p>根据gravity来判断偏移的值</p>
* @param left 左边
* @param position 位置
* @param perWidth 每个刻度的宽度
* @return 刻度起始X的位置
*/
private int getGravityStartX(int left, int position, double perWidth) {
int startX = (int) (left + position * perWidth);
if (gravity == Gravity.CENTER) {
startX += perWidth / 2;
} else if (gravity == Gravity.RIGHT) {
startX += perWidth;
}
return startX;
}
/**
* 绘制文字
* <p>完成文字偏移,文字旋转,绘制</p>
* @param canvas 画布
* @param contentStr 文字内容
* @param startX 文字绘制起始X位置
* @param startY 文字绘制起始Y位置
* @param position 刻度序号
* @param paint 画笔
*/
private void drawText(Canvas canvas, String contentStr, int startX, float startY, int position, Paint paint) {
String content = formatData(contentStr);
scaleStyle.fillPaint(paint);
paint.setTextAlign(Paint.Align.CENTER);
if (isShowFullValue && position == 0) {
int width = (int) paint.measureText(content);
startX+= width/2;
} else if (isShowFullValue && position == scaleData.rowSize - 1) {
int width = (int) paint.measureText(content);
startX-= width/2;
}
if (isRotateAngle) {
canvas.save();
canvas.rotate(rotateAngle, startX, startY);
canvas.drawText(content, startX, startY + textHeight / 2, paint);
canvas.restore();
} else {
canvas.drawText(content, startX, startY + textHeight / 2, paint);
}
}
/**
* 格式化文字
* @param data 文字
* @return 格式化完成之后的文字
*/
private String formatData(String data){
return getFormat()!= null ? getFormat().format(data) :data;
}
/**
* 绘制竖向网格
* @param canvas 画布
* @param startX 网格起始X位置
* @param rect 原始范围
* @param scaleRect 缩放范围
* @param paint 画布
*/
public void drawGrid(Canvas canvas, float startX, Rect rect, Rect scaleRect, Paint paint) {
if (gridStyle != null && isDrawGrid) {
gridStyle.fillPaint(paint);
Path path = new Path();
path.moveTo(startX, rect.top + scaleRect.top);
path.lineTo(startX, rect.bottom - scaleRect.bottom);
canvas.drawPath(path, paint);
}
}
/**
* 计算出裁切轴之后的范围
* @param rect 原始范围
* @param scaleRect 缩放范围
* @return 上下左右的大小
*/
protected int[] calculation(Rect rect, Rect scaleRect) {
int startX = rect.left + scaleRect.left;
int endX = rect.right - scaleRect.right;
int startY, endY;
if (direction == AxisDirection.BOTTOM) {
startY = rect.bottom - scaleRect.bottom;
} else {
startY = rect.top + scaleRect.top;
}
endY = startY;
return new int[]{startX, startY, endX, endY};
}
/**
* 设置轴方位
* <p>横轴只能设置上下方位</p>
* @param axisDirection 轴方位
*
*/
@Override
public void setAxisDirection(int axisDirection) {
if (axisDirection == AxisDirection.BOTTOM || axisDirection == AxisDirection.TOP) {
this.direction = axisDirection;
} else throw new ChartException("Can only set BOTTOM, TOP direction");
}
/**
* 设置文字旋转角度
* @param rotateAngle
*/
public void setRotateAngle(int rotateAngle) {
isRotateAngle = true;
this.rotateAngle = rotateAngle;
}
/**
* 是否需要偏移来完整显示左右两边文字
*
* @return 是否需要偏移
*/
public boolean isShowFullValue() {
return isShowFullValue;
}
/**
* 设置是否需要偏移来完整显示左右两边文字
*
* @param showFullValue 设置是否需要偏移
*/
public void setShowFullValue(boolean showFullValue) {
isShowFullValue = showFullValue;
}
}
package com.daivd.chart.component.axis;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import com.daivd.chart.data.ChartData;
import com.daivd.chart.data.BarData;
import com.daivd.chart.data.ScaleData;
import com.daivd.chart.data.ScaleSetData;
import com.daivd.chart.exception.ChartException;
import java.util.List;
/**
* 图表竖轴
* @author huangyanbin
*/
public class VerticalAxis extends BaseAxis<Double> {
/**
* 图表刻度设置数据
* <p>用于保存用户设置最大,最小等数据</p>
*/
private ScaleSetData scaleSetData = new ScaleSetData();
/**
* 横轴构造方法
* <p>可以设置横轴方向 左右方位</p>
* @param direction 方位
*/
public VerticalAxis(int direction) {
this.direction = direction;
}
/**
* 小数点格式化
*/
private java.text.DecimalFormat df = new java.text.DecimalFormat("0");
/**
* 计算刻度大小
* <p>通过计算刻度的宽高得到轴的大小,然后保存到scaleData对象中,以便后面的计算</p>
* @param chartData 图表数据
* @param rect 图表原始范围
* @param paint 画笔
*/
@Override
public void computeScale(ChartData<? extends BarData> chartData, Rect rect, Paint paint) {
if(isDisplay()) {
ScaleData scaleData = chartData.getScaleData();
scaleData.resetScale(scaleSetData, direction);
scaleStyle.fillPaint(paint);
int length = Math.max(formatVerticalAxisData(scaleData.getMaxScaleValue(direction)).length(),
formatVerticalAxisData(scaleData.getMinScaleValue(direction)).length());
int textHeight = (int) (paint.measureText("1", 0, 1) * length);
int dis = (int) (textHeight + scaleStyle.getPadding() + axisStyle.getWidth());
if (direction == AxisDirection.LEFT) {
scaleData.scaleRect.left = dis;
} else {
scaleData.scaleRect.right = dis;
}
}
}
/**
* 绘制刻度
* <p>通过zoomRect计算出每个刻度的宽度,迭代绘制刻度</p>
* @param canvas 画布
* @param zoomRect 缩放之后范围
* @param clipRect 原始范围
* @param paint 画笔
* @param chartData 图表数据
*/
@Override
protected void drawScale(Canvas canvas, Rect zoomRect, Rect clipRect, Paint paint, ChartData<? extends BarData> chartData) {
ScaleData scaleData = chartData.getScaleData();
List<Double> scaleList = scaleData.getScaleList(direction);
float startX;
if (direction == AxisDirection.LEFT) {
startX = zoomRect.left + scaleData.scaleRect.right + scaleStyle.getPadding();
} else {
startX = zoomRect.right - scaleData.scaleRect.right + scaleStyle.getPadding();
}
int bottom = zoomRect.bottom;
int height = zoomRect.height();
float textHeight = paint.measureText("1", 0, 1);
int perHeight = height / (scaleList.size()-1);
for (int i = 0; i < scaleList.size(); i++) {
double value = scaleList.get(i);
float startY = bottom - i * perHeight;
if (clipRect.contains(clipRect.centerX(), (int) startY-1)) {
drawText(canvas, startX, startY + textHeight / 2, value, paint);
drawGrid(canvas, startY, zoomRect, scaleData.scaleRect, paint);
}
}
}
/**
* 绘制文字
*/
private void drawText(Canvas canvas, float startX, float startY, double value, Paint paint) {
scaleStyle.fillPaint(paint);
String content = formatVerticalAxisData(value);
paint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(content, startX, startY, paint);
}
/**
* 绘制网格
*/
private void drawGrid(Canvas canvas, float startY, Rect rect, Rect scaleRect, Paint paint) {
if (gridStyle != null && isDrawGrid) {
gridStyle.fillPaint(paint);
Path path = new Path();
path.moveTo(rect.left + scaleRect.left, startY);
path.lineTo(rect.right - scaleRect.right, startY);
canvas.drawPath(path, paint);
}
}
/**
* 计算出裁切轴之后的范围
* @param rect 原始范围
* @param scaleRect 缩放范围
* @return 上下左右的大小
*/
protected int[] calculation(Rect rect, Rect scaleRect) {
int startY = rect.top + scaleRect.top;
int endY = rect.bottom - scaleRect.bottom;
int startX, endX;
if (direction == AxisDirection.LEFT) {
startX = rect.left + scaleRect.left;
} else {
startX = rect.right - scaleRect.right;
}
endX = startX;
return new int[]{startX, startY, endX, endY};
}
/**
* 设置轴方位
* <p>竖轴只能设置左右方位</p>
* @param axisDirection 轴方位
*
*/
@Override
public void setAxisDirection(int axisDirection) {
if (axisDirection == AxisDirection.LEFT || axisDirection == AxisDirection.RIGHT) {
this.direction = axisDirection;
} else throw new ChartException("Can only set LEFT, RIGHT direction");
}
/**
* 格式化竖轴数据
* @param value 数值
* @return 格式化竖轴之后数据
*/
private String formatVerticalAxisData(double value) {
if(getFormat() != null){
return getFormat().format(value);
}
return df.format(value);
}
/**
* 设置刻度是否从0开始
*/
public void setStartZero(boolean isStartZero){
this.scaleSetData.setStartZoom(isStartZero);
}
/**
* 设置刻度最大值
* @param maxValue 最大值
*/
public void setMaxValue(double maxValue) {
this.scaleSetData.setMaxValue(maxValue);
}
/**
* 设置刻度最小值
* @param minValue 最大值
*/
public void setMinValue(double minValue) {
this.scaleSetData.setMinValue(minValue);
}
}
package com.daivd.chart.component.base;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import com.daivd.chart.data.ChartData;
import com.daivd.chart.data.format.IFormat;
import com.daivd.chart.data.BarData;
import com.daivd.chart.matrix.MatrixHelper;
/**
* Created by huangYanBin on 2017/9/26.
* 轴接口
*/
public interface IAxis<V> {
void draw(Canvas canvas, Rect rect, MatrixHelper helper, Paint paint, ChartData<? extends BarData> chartData);
void computeScale(ChartData<? extends BarData> chartData, Rect rect, Paint paint);
void setAxisDirection(int axisDirection);
void setFormat(IFormat<V> format);
IFormat<V> getFormat();
void setDisplay(boolean isShow);
/**
* Created by huangYanBin on 2017/9/26.
* 轴方向
*/
interface AxisDirection {
int TOP = 1;
int BOTTOM=2;
int LEFT = 3;
int RIGHT = 4;
}
}
package com.daivd.chart.component.base;
import com.daivd.chart.data.style.FontStyle;
/**
* Created by huang on 2017/10/26.
*/
public interface IChartTitle extends IComponent<String> {
float getPercent();
void setPercent(float percent);
FontStyle getFontStyle();
int getDirection();
void setDirection(int direction);
void setFontStyle(FontStyle fontStyle);
}
package com.daivd.chart.component.base;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
/** 图表组件
* Created by huang on 2017/10/26.
*/
public interface IComponent<T> {
int LEFT = 0;
int TOP = 1;
int RIGHT =2;
int BOTTOM = 3;
/**
* 计算组件Rect
* @param chartRect
*/
void computeRect(Rect chartRect);
/**
* 绘制组件
* @param canvas 画布
* @param t 数据
* @param paint 画笔
*/
void draw(Canvas canvas, T t, Paint paint);
}
package com.daivd.chart.component.base;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import com.daivd.chart.data.style.FontStyle;
/**
* Created by huang on 2017/9/30.
*/
public interface IEmpty extends IComponent<String> {
void draw(Canvas canvas, Paint paint);
FontStyle getFontStyle();
void setFontStyle(FontStyle fontStyle) ;
String getEmptyTip();
void setEmptyTip(String emptyTip);
}
package com.daivd.chart.component.base;
import android.graphics.PointF;
import com.daivd.chart.data.ChartData;
import com.daivd.chart.data.ColumnData;
import com.daivd.chart.data.style.FontStyle;
import com.daivd.chart.provider.component.point.ILegendPoint;
import com.daivd.chart.provider.component.point.IPoint;
import com.daivd.chart.listener.OnClickLegendListener;
/**
* Created by huang on 2017/10/26.
*/
public interface ILegend<C extends ColumnData> extends IComponent<ChartData<C>> {
float getPercent();
void setPercent(float percent);
FontStyle getFontStyle();
int getDirection();
void setDirection(int direction);
void setFontStyle(FontStyle fontStyle);
void onClickLegend(PointF pointF);
void setOnClickLegendListener(OnClickLegendListener<C> onClickLegendListener);
int getPadding();
void setPadding(int padding);
IPoint getPoint();
void setPoint(ILegendPoint point);
void setSelectColumn(boolean selectColumn);
void setDisplay(boolean isDisplay);
}
\ No newline at end of file
package com.daivd.chart.component.base;
import android.graphics.Rect;
/**图表百分百组件
* Created by huang on 2017/10/26.
*/
public abstract class PercentComponent<T> implements IComponent<T> {
private static final float DEFAULT_PERCENT = 0.1f;
private float percent = DEFAULT_PERCENT;
private Rect rect = new Rect();
protected int direction;
@Override
public void computeRect(Rect chartRect) {
rect.left = chartRect.left;
rect.right = chartRect.right;
rect.top = chartRect.top;
rect.bottom = chartRect.bottom;
int h = (int) (chartRect.height()*percent);
int w = (int) (chartRect.width()*percent);
switch (direction){
case TOP:
rect.bottom = rect.top+h;
chartRect.top = chartRect.top+h;
break;
case LEFT:
rect.right = rect.left + w;
chartRect.left = chartRect.left + w;
break;
case RIGHT:
rect.left = rect.right -w;
chartRect.right = chartRect.right - w;
break;
case BOTTOM:
rect.top = rect.bottom -h;
chartRect.bottom = chartRect.bottom -h;
break;
}
}
public float getPercent() {
return percent;
}
public void setPercent(float percent) {
this.percent = percent;
}
public Rect getRect() {
return rect;
}
public void setRect(Rect rect) {
this.rect = rect;
}
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
}
package com.daivd.chart.core;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import com.daivd.chart.core.base.BaseBarLineChart;
import com.daivd.chart.data.BarData;
import com.daivd.chart.provider.barLine.Bar3DProvider;
/**
* Created by huang on 2017/9/26.
* 3DBar图表
*/
public class Bar3DChart extends BaseBarLineChart<Bar3DProvider,BarData> {
public Bar3DChart(Context context) {
super(context);
}
public Bar3DChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Bar3DChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected Bar3DProvider initProvider() {
return new Bar3DProvider();
}
}
package com.daivd.chart.core;
import android.content.Context;
import android.util.AttributeSet;
import com.daivd.chart.core.base.BaseBarLineChart;
import com.daivd.chart.data.BarData;
import com.daivd.chart.provider.barLine.BarProvider;
/**
* Created by huang on 2017/9/26.
* 柱状图
*/
public class BarChart extends BaseBarLineChart<BarProvider<BarData>,BarData> {
public BarChart(Context context) {
super(context);
}
public BarChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BarChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected BarProvider initProvider() {
return new BarProvider();
}
}
package com.daivd.chart.core;
import android.content.Context;
import android.util.AttributeSet;
import com.daivd.chart.core.base.BaseBarLineChart;
import com.daivd.chart.data.BarLineData;
import com.daivd.chart.provider.barLine.BarLineProvider;
/**
* Created by huang on 2017/9/26.
* 柱状图
*/
public class BarLineChart extends BaseBarLineChart<BarLineProvider,BarLineData> {
public BarLineChart(Context context) {
super(context);
}
public BarLineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BarLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected BarLineProvider initProvider() {
return new BarLineProvider();
}
}
package com.daivd.chart.core;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import com.daivd.chart.core.base.BaseBarLineChart;
import com.daivd.chart.data.LineData;
import com.daivd.chart.exception.ChartException;
import com.daivd.chart.provider.barLine.LineProvider;
import com.daivd.chart.provider.component.line.CurveLineModel;
import com.daivd.chart.provider.component.line.BrokenLineModel;
/**
* Created by huang on 2017/9/26.
* 线性图
*/
public class LineChart extends BaseBarLineChart<LineProvider,LineData> {
public static final int LINE_MODEL = 0;
public static final int CURVE_MODEL = 1;
public LineChart(Context context) {
super(context);
}
public LineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LineChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected LineProvider initProvider() {
horizontalAxis.setGravity(Gravity.LEFT);
horizontalAxis.isLine(true);
return new LineProvider();
}
public void setLineModel(int model){
if(model == LINE_MODEL){
provider.setLineModel(new BrokenLineModel());
}else if( model == CURVE_MODEL){
provider.setLineModel(new CurveLineModel());
}else {
throw new ChartException("Please set the correct Line model");
}
}
}
package com.daivd.chart.core;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import com.daivd.chart.core.base.BaseRotateChart;
import com.daivd.chart.data.PieData;
import com.daivd.chart.matrix.RotateHelper;
import com.daivd.chart.provider.pie.Pie3DProvider;
/**
* Created by huang on 2017/10/9.
* 饼图
*/
public class Pie3DChart extends BaseRotateChart<Pie3DProvider,PieData> {
public Pie3DChart(Context context) {
super(context);
}
public Pie3DChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Pie3DChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected Pie3DProvider initProvider(RotateHelper rotateHelper) {
Pie3DProvider provider = new Pie3DProvider(getContext());
provider.setRotateHelper(rotateHelper);
return provider;
}
}
package com.daivd.chart.core;
import android.content.Context;
import android.util.AttributeSet;
import com.daivd.chart.core.base.BaseRotateChart;
import com.daivd.chart.data.PieData;
import com.daivd.chart.matrix.RotateHelper;
import com.daivd.chart.provider.pie.PieProvider;
/**
* Created by huang on 2017/10/9.
* 饼图
*/
public class PieChart extends BaseRotateChart<PieProvider,PieData> {
public PieChart(Context context) {
super(context);
}
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PieChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected PieProvider initProvider(RotateHelper rotateHelper) {
PieProvider provider = new PieProvider();
provider.setRotateHelper(rotateHelper);
return provider;
}
}
package com.daivd.chart.core;
import android.content.Context;
import android.util.AttributeSet;
import com.daivd.chart.core.base.BaseRotateChart;
import com.daivd.chart.data.RadarData;
import com.daivd.chart.matrix.RotateHelper;
import com.daivd.chart.provider.radar.RadarProvider;
/**
* Created by huang on 2017/10/9.
* 雷达图
*/
public class RadarChart extends BaseRotateChart<RadarProvider,RadarData> implements RotateHelper.OnRotateListener{
public RadarChart(Context context) {
super(context);
}
public RadarChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RadarChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected RadarProvider initProvider(RotateHelper rotateHelper) {
RadarProvider provider = new RadarProvider();
provider.setRotateHelper(rotateHelper);
return provider;
}
}
package com.daivd.chart.core;
import android.content.Context;
import android.util.AttributeSet;
import com.daivd.chart.core.base.BaseRotateChart;
import com.daivd.chart.data.RoseData;
import com.daivd.chart.matrix.RotateHelper;
import com.daivd.chart.provider.rose.RoseProvider;
/**
* Created by huang on 2017/10/9.
* 饼图
*/
public class RoseChart extends BaseRotateChart<RoseProvider,RoseData> {
public RoseChart(Context context) {
super(context);
}
public RoseChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RoseChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected RoseProvider initProvider(RotateHelper rotateHelper) {
RoseProvider provider = new RoseProvider();
provider.setRotateHelper(rotateHelper);
return provider;
}
}
package com.daivd.chart.core.base;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import com.daivd.chart.component.axis.HorizontalAxis;
import com.daivd.chart.component.base.IAxis;
import com.daivd.chart.component.axis.VerticalAxis;
import com.daivd.chart.data.BarData;
import com.daivd.chart.provider.IProvider;
/**
* Created by huang on 2017/9/26.
* 线性和柱状图
*/
public abstract class BaseBarLineChart<P extends IProvider<C>,C extends BarData> extends BaseChart<P,C> {
protected HorizontalAxis horizontalAxis; //横轴
protected VerticalAxis leftVerticalAxis;//竖轴
protected VerticalAxis rightVerticalAxis;//竖轴
public BaseBarLineChart(Context context) {
super(context);
}
public BaseBarLineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BaseBarLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
protected void init(){
horizontalAxis = new HorizontalAxis();
leftVerticalAxis = new VerticalAxis(IAxis.AxisDirection.LEFT);
rightVerticalAxis = new VerticalAxis(IAxis.AxisDirection.RIGHT);
super.init();
}
protected void drawContent(Canvas canvas){
horizontalAxis.computeScale(chartData, chartRect, paint);
if (chartData.getScaleData().isLeftHasValue)
leftVerticalAxis.computeScale(chartData, chartRect, paint);
if (chartData.getScaleData().isRightHasValue)
rightVerticalAxis.computeScale(chartData, chartRect, paint);
if (chartData.getScaleData().isLeftHasValue)
leftVerticalAxis.draw(canvas, chartRect, matrixHelper, paint, chartData);
if (chartData.getScaleData().isRightHasValue)
rightVerticalAxis.draw(canvas, chartRect, matrixHelper, paint, chartData);
horizontalAxis.draw(canvas, chartRect, matrixHelper, paint, chartData);
computeScaleRect();
provider.drawProvider(canvas, chartRect, matrixHelper, paint);
}
/**
* 计算绘制刻度之后的大小
*/
private void computeScaleRect(){
Rect rect = chartData.getScaleData().scaleRect;
computeChartRect(rect);
}
public void setHeightPercent(){
}
public HorizontalAxis getHorizontalAxis() {
return horizontalAxis;
}
public VerticalAxis getLeftVerticalAxis() {
return leftVerticalAxis;
}
public VerticalAxis getRightVerticalAxis() {
return rightVerticalAxis;
}
}
package com.daivd.chart.core.base;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.daivd.chart.component.Legend;
import com.daivd.chart.component.PicGeneration;
import com.daivd.chart.component.base.IChartTitle;
import com.daivd.chart.component.base.ILegend;
import com.daivd.chart.data.ChartData;
import com.daivd.chart.data.ColumnData;
import com.daivd.chart.data.PicOption;
import com.daivd.chart.data.ScaleData;
import com.daivd.chart.component.ChartTitle;
import com.daivd.chart.component.EmptyView;
import com.daivd.chart.component.base.IEmpty;
import com.daivd.chart.listener.OnChartChangeListener;
import com.daivd.chart.listener.OnClickColumnListener;
import com.daivd.chart.listener.OnClickLegendListener;
import com.daivd.chart.listener.ChartGestureObserver;
import com.daivd.chart.matrix.MatrixHelper;
import com.daivd.chart.provider.IProvider;
/**
* 基本图表
* Created by huang on 2017/9/26.
*/
public abstract class BaseChart<P extends IProvider<C>, C extends ColumnData> extends View
implements ChartGestureObserver, OnClickLegendListener<C>, OnChartChangeListener {
protected int width; //高
protected int height; //宽
protected int paddingLeft = 10;
protected int paddingRight = 10;
protected int paddingTop = 30;
protected int paddingBottom = 30;
protected Rect chartRect = new Rect();
protected ILegend<C> legend;
protected IChartTitle chartTitle;
protected P provider;//内容绘制者
protected Paint paint;
protected ChartData<C> chartData;
protected MatrixHelper matrixHelper;
protected boolean isShowChartName = true;
protected boolean isCharEmpty;
protected IEmpty emptyView;
private OnClickLegendListener<C> onClickLegendListener;
private boolean isFirstAnim = true;
public BaseChart(Context context) {
super(context);
init();
}
public BaseChart(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BaseChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化组件
*/
protected void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
chartTitle = new ChartTitle();
legend = new Legend<>();
legend.setOnClickLegendListener(this);
matrixHelper = new MatrixHelper(getContext());
matrixHelper.register(this);
matrixHelper.setOnTableChangeListener(this);
emptyView = new EmptyView();
provider = initProvider();
}
/**
* 将触摸事件交给Iouch处理
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
return matrixHelper.handlerTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
matrixHelper.onDisallowInterceptEvent(this, event);
return super.dispatchTouchEvent(event);
}
/**
* 获取大小
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
width = w;
height = h;
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (chartData != null) {
resetScaleData();
computePaddingRect();
if (isShowChartName) {
//计算标题的大小
chartTitle.computeRect(chartRect);
chartTitle.draw(canvas, chartData.getChartName(), paint);
}
if (!chartData.isEmpty()) {
//计算图例的大小
legend.computeRect(chartRect);
//绘制图例
legend.draw(canvas, chartData, paint);
}
if (!isCharEmpty) {
//绘制内容
drawContent(canvas);
} else {
//绘制空白
emptyView.computeRect(chartRect);
emptyView.draw(canvas, paint);
}
}
}
//待改善
private void resetScaleData() {
ScaleData scaleData = chartData.getScaleData();
scaleData.scaleRect.set(0, 0, 0, 0);
}
protected abstract void drawContent(Canvas canvas);
protected void computeChartRect(Rect rect) {
chartRect.left = chartRect.left + rect.left;
chartRect.top = chartRect.top + rect.top;
chartRect.bottom = chartRect.bottom - rect.bottom;
chartRect.right = chartRect.right - rect.right;
}
/**
* 计算图表的区域
*
* @return
*/
private void computePaddingRect() {
chartRect.left = paddingLeft;
chartRect.top = paddingTop;
chartRect.bottom = height - paddingBottom;
chartRect.right = width - paddingRight;
}
public void setPadding(int[] padding) {
this.paddingLeft = padding[0];
this.paddingTop = padding[1];
this.paddingRight = padding[2];
this.paddingBottom = padding[3];
}
protected abstract P initProvider();
/**
* 设置图内容绘制者
*/
public void setProvider(P provider) {
this.provider = provider;
}
public P getProvider() {
return provider;
}
public ChartData<C> getChartData() {
return chartData;
}
/**
* 设置图表数据源
*/
public void setChartData(ChartData<C> chartData) {
isCharEmpty = !provider.calculation(chartData);
this.chartData = chartData;
invalidate();
}
private int duration = 400;
private Interpolator interpolator;
/**
* 动画
*/
private void startChartAnim() {
if (interpolator == null)
startChartAnim(duration);
else
startChartAnim(duration, interpolator);
}
/**
* 动画
*
* @param duration 时间
*/
public void startChartAnim(int duration) {
new DecelerateInterpolator();
provider.startAnim(this, duration, new DecelerateInterpolator());
}
public void startChartAnim(int duration, Interpolator interpolator) {
this.duration = duration;
this.interpolator = interpolator;
provider.startAnim(this, duration, interpolator);
}
@Override
public void onClick(float x, float y) {
PointF pointF = new PointF(x, y);
provider.clickPoint(pointF);
legend.onClickLegend(pointF);
invalidate();
}
@Override
public void onClickLegend(C c, Legend<C> legend) {
isCharEmpty = !provider.calculation(chartData);
if (!isFirstAnim) {
startChartAnim();
}
if (onClickLegendListener != null) {
onClickLegendListener.onClickLegend(c, legend);
}
}
public IEmpty getEmptyView() {
return emptyView;
}
public void setEmptyView(IEmpty emptyView) {
this.emptyView = emptyView;
}
public boolean isShowChartName() {
return isShowChartName;
}
public void setShowChartName(boolean showChartName) {
isShowChartName = showChartName;
}
public IChartTitle getChartTitle() {
return chartTitle;
}
public void setChartTitle(IChartTitle chartTitle) {
this.chartTitle = chartTitle;
}
public ILegend<C> getLegend() {
return legend;
}
/**
* 设置是否可以缩放
*
* @param zoom
*/
public void setZoom(boolean zoom) {
matrixHelper.setCanZoom(zoom);
}
@Override
public void onTableChanged(float translateX, float translateY) {
invalidate();
}
public MatrixHelper getMatrixHelper() {
return matrixHelper;
}
public void setOnClickColumnListener(OnClickColumnListener<C> onClickColumnListener) {
provider.setOnClickColumnListener(onClickColumnListener);
}
public void setOnClickLegendListener(OnClickLegendListener<C> onClickLegendListener) {
this.onClickLegendListener = onClickLegendListener;
}
/**
* 是否动画只执行一次
*
* @param isFirstAnim
*/
public void setFirstAnim(boolean isFirstAnim) {
this.isFirstAnim = isFirstAnim;
}
public boolean save() {
return save(new PicOption(chartData.getChartName()));
}
public boolean save(PicOption picOption) {
return new PicGeneration<BaseChart>().save(this, picOption);
}
}
\ No newline at end of file
package com.daivd.chart.core.base;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.daivd.chart.data.ColumnData;
import com.daivd.chart.matrix.RotateHelper;
import com.daivd.chart.provider.IProvider;
/**
* Created by huang on 2017/10/9.
* 饼图
*/
public abstract class BaseRotateChart<P extends IProvider<C>,C extends ColumnData> extends BaseChart<P,C> implements RotateHelper.OnRotateListener{
private RotateHelper rotateHelper;
private boolean isOnRotate;
public BaseRotateChart(Context context) {
super(context);
}
public BaseRotateChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BaseRotateChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void drawContent(Canvas canvas) {
provider.drawProvider(canvas, chartRect, matrixHelper, paint);
}
@Override
protected P initProvider() {
rotateHelper = new RotateHelper(this);
return initProvider(rotateHelper);
}
protected abstract P initProvider(RotateHelper rotateHelper);
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()== MotionEvent.ACTION_DOWN){
isOnRotate = rotateHelper.containsPoint(event);
}
if(isOnRotate){
super.onTouchEvent(event);
return rotateHelper.handlerTouchEvent(event);
}
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if(isOnRotate){
rotateHelper.onDisallowInterceptEvent(this,event);
}
return super.dispatchTouchEvent(event);
}
public void setRotate(boolean rotate) {
if(rotate){
setZoom(false);
}
rotateHelper.setRotate(rotate);
}
@Override
public void onRotate(double angle) {
invalidate();
}
}
package com.daivd.chart.data;
import com.daivd.chart.component.base.IAxis;
import java.util.List;
/**
* Created by huang on 2017/9/26.
*/
public class BarData extends ColumnData<List<Double>> {
public BarData(String name, String unit, int color, List<Double> chartYDataList) {
super(name,unit,color,chartYDataList);
}
public BarData(String name, String unit, int direction, int color, List<Double> datas) {
super(name,unit,direction,color,datas);
}
}
package com.daivd.chart.data;
import java.util.List;
/**
* Created by huang on 2017/10/19.
*/
public class BarLineData extends BarData {
public static final int BROKEN= 0;
public static final int CURVE = 1;
public static final int BAR = 2;
private int model = BAR;
public BarLineData(String name, String unit, int color, List<Double> chartYDataList) {
super(name, unit, color, chartYDataList);
}
public BarLineData(String name, int model,String unit, int color,List<Double> chartYDataList) {
super(name, unit, color, chartYDataList);
this.model = model;
}
public BarLineData(String name, String unit, int direction, int color, List<Double> datas) {
super(name, unit, direction, color, datas);
}
public BarLineData(String name,int model, String unit, int direction, int color, List<Double> datas) {
super(name, unit, direction, color, datas);
this.model = model;
}
public int getModel() {
return model;
}
public void setModel(int model) {
this.model = model;
}
}
package com.daivd.chart.data;
import java.util.Arrays;
import java.util.List;
/**
* Created by huang on 2017/9/26.
* chart data
*/
public class ChartData<T extends ColumnData> {
private String chartName;
private List<String> charXDataList;
private ScaleData scaleData = new ScaleData();
private List<T> columnDataList;
public ChartData(String chartName,List<String> charXDataList, T... columnDataList){
this.chartName = chartName;
this.charXDataList = charXDataList;
this.columnDataList = Arrays.asList(columnDataList);
}
public ChartData(String chartName,List<String> charXDataList, List<T> columnDataList){
this.chartName = chartName;
this.charXDataList = charXDataList;
this.columnDataList = columnDataList;
}
public String getChartName() {
return chartName;
}
public void setChartName(String chartName) {
this.chartName = chartName;
}
public boolean isEmpty() {
return columnDataList== null || this.columnDataList.size() ==0 ;
}
public List<String> getCharXDataList() {
return charXDataList;
}
public void setCharXDataList(List<String> charXDataList) {
this.charXDataList = charXDataList;
}
public ScaleData getScaleData() {
return scaleData;
}
public void setScaleData(ScaleData scaleData) {
this.scaleData = scaleData;
}
public List<T> getColumnDataList() {
return columnDataList;
}
public void setColumnDataList(List<T> columnDataList) {
this.columnDataList = columnDataList;
}
}
package com.daivd.chart.data;
import com.daivd.chart.component.base.IAxis;
/**
* Created by huang on 2017/9/26.
*/
public class ColumnData<T> {
public String name;
private String unit;
private int color;
private boolean isDraw = true;
private int direction = IAxis.AxisDirection.LEFT;
private T chartYDataList;
public ColumnData(String name, String unit, int color, T chartYDataList) {
this.name = name;
this.unit = unit;
this.color = color;
this.chartYDataList = chartYDataList;
}
public ColumnData(String name, String unit, int direction, int color, T datas) {
this.name = name;
this.unit = unit;
this.color = color;
this.chartYDataList = datas;
if(direction == IAxis.AxisDirection.LEFT || direction == IAxis.AxisDirection.RIGHT){
this.direction = direction;
}
}
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public T getChartYDataList() {
return chartYDataList;
}
public void setChartYDataList(T chartYDataList) {
this.chartYDataList = chartYDataList;
}
public boolean isDraw() {
return isDraw;
}
public void setDraw(boolean draw) {
isDraw = draw;
}
}
package com.daivd.chart.data;
import com.daivd.chart.data.style.LineStyle;
import com.daivd.chart.provider.component.line.ILineModel;
import com.daivd.chart.provider.component.point.IPoint;
import java.util.List;
/**
* Created by huang on 2017/9/26.
*/
public class LineData extends BarData {
private IPoint point;
private ILineModel lineModel;
private LineStyle lineStyle;
public LineData(String name, String unit, int color, List<Double> chartYDataList) {
super(name,unit,color,chartYDataList);
}
public LineData(String name, String unit, int direction, int color, List<Double> datas) {
super(name,unit,direction,color,datas);
}
public IPoint getPoint() {
return point;
}
public void setPoint(IPoint point) {
this.point = point;
}
public ILineModel getLineModel() {
return lineModel;
}
public void setLineModel(ILineModel lineModel) {
this.lineModel = lineModel;
}
public LineStyle getLineStyle() {
return lineStyle;
}
public void setLineStyle(LineStyle lineStyle) {
this.lineStyle = lineStyle;
}
}
package com.daivd.chart.data;
import android.graphics.Bitmap;
/**
* Created by huang on 2017/10/30.
* 保存图片Option
*/
public class PicOption {
private String fileName;
private String subFolderPath ="chart";
private String fileDescription="chart pic";
private Bitmap.CompressFormat format= Bitmap.CompressFormat.PNG;
private int quality = 100;
public PicOption(String fileName) {
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
public String getSubFolderPath() {
return subFolderPath;
}
public PicOption setSubFolderPath(String subFolderPath) {
this.subFolderPath = subFolderPath;
return this;
}
public String getFileDescription() {
return fileDescription;
}
public PicOption setFileDescription(String fileDescription) {
this.fileDescription = fileDescription;
return this;
}
public Bitmap.CompressFormat getFormat() {
return format;
}
public PicOption setFormat(Bitmap.CompressFormat format) {
this.format = format;
return this;
}
public int getQuality() {
return quality;
}
public PicOption setQuality(int quality) {
this.quality = quality;
return this;
}
}
package com.daivd.chart.data;
import com.daivd.chart.component.base.IAxis;
/**
* Created by huang on 2017/9/26.
*/
public class PieData extends ColumnData<Double> {
public PieData(String name, String unit, int color, Double chartYDataList) {
super(name, unit, color, chartYDataList);
}
public PieData(String name, String unit, IAxis.AxisDirection direction, int color, Double datas) {
super(name, unit, color, datas);
}
}
package com.daivd.chart.data;
import java.util.List;
/**
* Created by huang on 2017/9/26.
*/
public class RadarData extends BarData {
public RadarData(String name, String unit, int color, List<Double> chartYDataList) {
super(name,unit,color,chartYDataList);
}
}
package com.daivd.chart.data;
import java.util.List;
/**
* Created by huang on 2017/9/26.
*/
public class RoseData extends BarData {
public RoseData(String name, String unit, int color, List<Double> chartYDataList) {
super(name,unit,color,chartYDataList);
}
}
package com.daivd.chart.data;
import android.graphics.Rect;
import com.daivd.chart.component.base.IAxis;
import com.daivd.chart.matrix.MatrixHelper;
import java.util.ArrayList;
import java.util.List;
/**
* Created by huang on 2017/9/26.
* Scale data
*/
public class ScaleData {
public boolean isLeftHasValue = false;
public boolean isRightHasValue = false;
//竖轴数据
public double maxLeftValue; //最大值
public double minLeftValue; //最小值
//竖轴数据
public double maxRightValue; //最大值
public double minRightValue; //最小值
public int totalScale = 7; //刻度数量
public Rect scaleRect = new Rect();
//横轴数据
public int rowSize; //列数据
public float zoom = MatrixHelper.MIN_ZOOM; //放大比例
public List<Double> getScaleList(int direction){
List<Double> scaleList = new ArrayList<>();
int total = (int) (totalScale *zoom);
double scale = getTotalScaleLength(direction) /(total-1);
double minValue = getMinScaleValue(direction);
for(int i = 0;i < total;i++){
scaleList.add(minValue +scale*i);
}
return scaleList;
}
/**
* 获取最大刻度
* @param direction
* @return
*/
public double getMaxScaleValue(int direction){
if(direction == IAxis.AxisDirection.LEFT){
return maxLeftValue;
}
return maxRightValue;
}
/**
* 获取最小刻度
* @param direction
* @return
*/
public double getMinScaleValue(int direction){
if(direction == IAxis.AxisDirection.LEFT){
return minLeftValue;
}
return minRightValue;
}
/**
*获取总刻度值
* @param direction 方向
* @return 总刻度
*/
public double getTotalScaleLength(int direction){
if(direction == IAxis.AxisDirection.LEFT){
return maxLeftValue - minLeftValue;
}
return maxRightValue - minRightValue;
}
public Rect getOffsetRect(Rect rect, Rect offsetRect){
rect.left = rect.left + offsetRect.left;
rect.right = rect.right - offsetRect.right;
rect.top = rect.top + offsetRect.top;
rect.bottom = rect.bottom - offsetRect.bottom;
return rect;
}
public void resetScale(ScaleSetData scaleSetData,int direction){
if(direction == IAxis.AxisDirection.LEFT){
if(scaleSetData.isStartZoom()){
minLeftValue = 0;
}else {
if(scaleSetData.isHasMinValue()){
minLeftValue = scaleSetData.getMinValue();
}
}
if(scaleSetData.isHasMaxValue()){
maxLeftValue = scaleSetData.getMaxValue();
}
}else{
if(scaleSetData.isStartZoom()){
minRightValue = 0;
}else {
if(scaleSetData.isHasMinValue()){
minRightValue = scaleSetData.getMinValue();
}
}
if(scaleSetData.isHasMaxValue()){
maxRightValue = scaleSetData.getMaxValue();
}
}
}
}
package com.daivd.chart.data;
/**
* Created by huang on 2017/10/18.
*/
public class ScaleSetData {
private boolean isStartZoom;
private boolean isHasMaxValue;
private boolean isHasMinValue;
private double maxValue;
private double minValue;
public boolean isStartZoom() {
return isStartZoom;
}
public void setStartZoom(boolean startZoom) {
isHasMinValue = false;
isStartZoom = startZoom;
}
public boolean isHasMaxValue() {
return isHasMaxValue;
}
public boolean isHasMinValue() {
return isHasMinValue;
}
public double getMaxValue() {
return maxValue;
}
public void setMaxValue(double maxValue) {
isHasMaxValue = true;
this.maxValue = maxValue;
}
public double getMinValue() {
return minValue;
}
public void setMinValue(double minValue) {
isHasMinValue = true;
this.minValue = minValue;
}
}
package com.daivd.chart.data.format;
/**
* 格式化
* <p>用于横轴和竖轴文本格式化</p>
* @author huangyanbin
*/
public interface IFormat<T> {
String format( T t);
}
package com.daivd.chart.data.style;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import com.daivd.chart.utils.DensityUtils;
/**
* Created by huang on 2017/9/27.
*/
public class FontStyle implements IStyle{
public static int defaultFontSize = 12;
public static int defaultFontColor = Color.parseColor("#333333");
private int textSize;
private int textColor;
private int padding = 0;
public static void setDefaultTextSize(int defaultTextSize){
defaultFontSize = defaultTextSize;
}
public static void setDefaultTextSpSize(Context context,int defaultTextSpSize){
defaultFontSize = DensityUtils.sp2px(context,defaultTextSpSize);
}
public static void setDefaultTextColor(int defaultTextColor){
defaultFontColor = defaultTextColor;
}
public FontStyle() {
}
public FontStyle(int textSize, int textColor) {
this.textSize = textSize;
this.textColor = textColor;
}
public FontStyle(Context context,int sp, int textColor) {
this.textSize = DensityUtils.sp2px(context,sp);
this.textColor = textColor;
}
public int getTextSize() {
if(textSize == 0){
return defaultFontSize;
}
return textSize;
}
public FontStyle setTextSize(int textSize) {
this.textSize = textSize;
return this;
}
public FontStyle setTextSpSize(Context context,int sp){
this.setTextSize(DensityUtils.sp2px(context,sp));
return this;
}
public int getTextColor() {
if(textColor == 0){
return defaultFontColor;
}
return textColor;
}
public FontStyle setTextColor(int textColor) {
this.textColor = textColor;
return this;
}
public int getPadding() {
return padding;
}
public void setPadding(int padding) {
this.padding = padding;
}
@Override
public void fillPaint(Paint paint){
paint.setColor(getTextColor());
paint.setTextSize(getTextSize());
paint.setStyle(Paint.Style.FILL);
}
}
package com.daivd.chart.data.style;
import android.graphics.Paint;
/**
* Created by huang on 2017/9/27.
*/
public interface IStyle {
void fillPaint(Paint paint);
}
package com.daivd.chart.data.style;
import android.content.Context;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.PathEffect;
import com.daivd.chart.utils.DensityUtils;
/**
* Created by huang on 2017/9/27.
*/
public class LineStyle implements IStyle {
private float width;
private int color;
private PathEffect effect = new PathEffect();
private static float defaultLineSize = 2f;
private static int defaultLineColor = Color.parseColor("#888888");
public LineStyle() {
}
public LineStyle(float width, int color) {
this.width = width;
this.color = color;
}
public LineStyle(Context context,float dp, int color) {
this.width = DensityUtils.dp2px(context,dp);
this.color = color;
}
public static void setDefaultLineSize(float width){
defaultLineSize = width;
}
public static void setDefaultLineSize(Context context,float dp){
defaultLineSize = DensityUtils.dp2px(context,dp);
}
public static void setDefaultLineColor(int color){
defaultLineColor = color;
}
public float getWidth() {
if(width == 0){
return defaultLineSize;
}
return width;
}
public LineStyle setWidth(float width) {
this.width = width;
return this;
}
public LineStyle setWidth(Context context,int dp) {
this.width = DensityUtils.dp2px(context,dp);
return this;
}
public int getColor() {
if(color == 0){
return defaultLineColor;
}
return color;
}
public LineStyle setColor(int color) {
this.color = color;
return this;
}
public LineStyle setEffect(PathEffect effect) {
this.effect = effect;
return this;
}
@Override
public void fillPaint(Paint paint){
paint.setColor(getColor());
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(getWidth());
paint.setPathEffect(effect);
}
}
package com.daivd.chart.data.style;
import android.content.Context;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import com.daivd.chart.utils.DensityUtils;
/**
* Created by huang on 2017/9/27.
*/
public class PointStyle implements IStyle {
public static final int CIRCLE = 0;
public static final int SQUARE = 1;
public static final int RECT = 2;
private float width;
private int color;
private int shape;
private Paint.Style style;
private static float defaultPointSize = 10f;
private static int defaultPointColor = Color.parseColor("#888888");
private boolean isDraw = true;
public PointStyle() {
}
public PointStyle(float width, int color) {
this.width = width;
this.color = color;
}
public PointStyle(Context context, float dp, int color) {
this.width = DensityUtils.dp2px(context,dp);
this.color = color;
}
public static void setDefaultPointSize(float width){
defaultPointSize = width;
}
public static void setDefaultLineSize(Context context,float dp){
defaultPointSize = DensityUtils.dp2px(context,dp);
}
public static void setDefaultPointColor(int color){
defaultPointColor = color;
}
public float getWidth() {
if(width == 0){
return defaultPointSize;
}
return width;
}
public void setWidth(float width) {
this.width = width;
}
public void setWidth(Context context,int dp) {
this.width = DensityUtils.dp2px(context,dp);
}
public int getColor() {
if(color == 0){
return defaultPointColor;
}
return color;
}
public void setColor(int color) {
this.color = color;
}
public int getShape() {
return shape;
}
public void setShape(int shape) {
this.shape = shape;
}
public Paint.Style getStyle() {
if(style == null){
return Paint.Style.FILL;
}
return style;
}
public void setStyle(Paint.Style style) {
this.style = style;
}
public boolean isDraw() {
return isDraw;
}
public void setDraw(boolean draw) {
isDraw = draw;
}
@Override
public void fillPaint(Paint paint){
paint.setColor(getColor());
paint.setStyle(getStyle());
paint.setStrokeWidth(getWidth());
}
}
package com.daivd.chart.exception;
/**
* Created by huang on 2017/9/26.
*/
public class ChartException extends RuntimeException {
public ChartException(String message) {
super(message);
}
}
package com.daivd.chart.listener;
/**
* Created by huang on 2017/9/29.
*/
public interface ChartGestureObserver {
void onClick(float x,float y);
}
package com.daivd.chart.listener;
import java.util.ArrayList;
import java.util.List;
/**
* Created by huang on 2017/9/29.
*/
public abstract class Observable<T> {
public final ArrayList<T> observables = new ArrayList<>();
/**AttachObserver(通过实例注册观察者)
**/
public void register(T observer){
if(observer==null) throw new NullPointerException();
synchronized(observables){
if(!observables.contains(observer)){
observables.add(observer);
}
}
}
/**UnattachObserver(注销观察者)
**/
public void unRegister(T observer){
if(observer==null) throw new NullPointerException();
if(observables.contains(observer)){
observables.remove(observer);
}
}
public void unRegisterAll(){
synchronized(observables){
observables.clear();
}
}
/**Ruturnthesizeofobservers*/
public int countObservers(){
synchronized(observables){
return observables.size();
}
}
/**
*notify all observer(通知所有观察者,在子类中实现)
*/
public abstract void notifyObservers(List<T> observers);
}
package com.daivd.chart.listener;
/**
* Created by huang on 2017/11/4.
*/
public interface OnChartChangeListener {
void onTableChanged(float translateX, float translateY);
}
package com.daivd.chart.listener;
import com.daivd.chart.data.ColumnData;
/**
* Created by huang on 2017/9/30.
*/
public interface OnClickColumnListener<C extends ColumnData> {
void onClickColumn(C c,int position);
}
package com.daivd.chart.listener;
import com.daivd.chart.component.Legend;
import com.daivd.chart.data.ColumnData;
/**
* Created by huang on 2017/9/30.
*/
public interface OnClickLegendListener<C extends ColumnData> {
void onClickLegend(C c, Legend<C> legend);
}
package com.daivd.chart.matrix;
import android.view.MotionEvent;
import com.daivd.chart.core.base.BaseChart;
/**
* Created by huang on 2017/10/18.
*/
public interface ITouch {
/**
* 用于判断是否请求不拦截事件
* 解决手势冲突问题
* @param chart
* @param event
*/
void onDisallowInterceptEvent(BaseChart chart, MotionEvent event);
/**
* 处理touchEvent
* @param event
*/
boolean handlerTouchEvent(MotionEvent event);
}
package com.daivd.chart.matrix;
import android.animation.TypeEvaluator;
import android.graphics.Point;
/**
* Created by huang on 2017/11/3.
*/
public class PointEvaluator implements TypeEvaluator {
private Point point = new Point();
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));
point.set(x,y);
return point;
}
}
package com.daivd.chart.matrix;
import android.graphics.Rect;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.ViewParent;
import com.daivd.chart.core.base.BaseChart;
/**
* Created by huang on 2017/10/10.
*/
public class RotateHelper implements ITouch {
private int mRadius;
/**
* 检测按下到抬起时旋转的角度
*/
private float mTmpAngle;
/**
* 检测按下到抬起时使用的时间
*/
private long mDownTime;
/**
* 判断是否正在自动滚动
*/
private boolean isFling;
/**
* 布局时的开始角度
*/
private double mStartAngle = 0;
/**
* 当每秒移动角度达到该值时,认为是快速移动
*/
private static final int FLINGABLE_VALUE = 300;
/**
* 如果移动角度达到该值,则屏蔽点击
*/
private static final int NOCLICK_VALUE = 3;
/**
* 当每秒移动角度达到该值时,认为是快速移动
*/
private int mFlingableValue = FLINGABLE_VALUE;
private Handler mHandler = new Handler();
/**
* 记录上一次的x,y坐标
*/
private float mLastX;
private float mLastY;
private boolean isRotate;
private Rect originRect;
/**
* 自动滚动的Runnable
*/
private AutoFlingRunnable mFlingRunnable;
private OnRotateListener listener;
public RotateHelper(OnRotateListener listener){
this.listener = listener;
}
@Override
public boolean handlerTouchEvent(MotionEvent event) {
if(!isRotate()){
return false;
}
float x = event.getX();
float y = event.getY();
// Log.e("TAG", "x = " + x + " , y = " + y);
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
mDownTime = System.currentTimeMillis();
mTmpAngle = 0;
// 如果当前已经在快速滚动
if (isFling)
{
// 移除快速滚动的回调
mHandler.removeCallbacks(mFlingRunnable);
isFling = false;
return true;
}
break;
case MotionEvent.ACTION_MOVE:
/**
* 获得开始的角度
*/
float start = getAngle(mLastX, mLastY);
/**
* 获得当前的角度
*/
float end = getAngle(x, y);
// Log.e("TAG", "start = " + start + " , end =" + end);
// 如果是一、四象限,则直接end-start,角度值都是正值
if (getQuadrant(x, y) == 1 || getQuadrant(x, y) == 4)
{
mStartAngle += end - start;
mTmpAngle += end - start;
} else
// 二、三象限,色角度值是付值
{
mStartAngle += start - end;
mTmpAngle += start - end;
}
// 重新布局
listener.onRotate(mStartAngle);
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
// 计算,每秒移动的角度
float anglePerSecond = mTmpAngle * 1000
/ (System.currentTimeMillis() - mDownTime);
// Log.e("TAG", anglePrMillionSecond + " , mTmpAngel = " +
// mTmpAngle);
// 如果达到该值认为是快速移动
if (Math.abs(anglePerSecond) > mFlingableValue && !isFling)
{
// post一个任务,去自动滚动
mHandler.post(mFlingRunnable = new AutoFlingRunnable(anglePerSecond));
return true;
}
// 如果当前旋转角度超过NOCLICK_VALUE屏蔽点击
if (Math.abs(mTmpAngle) > NOCLICK_VALUE)
{
return true;
}
break;
}
return true;
}
@Override
public void onDisallowInterceptEvent(BaseChart chart, MotionEvent event){
ViewParent parent = chart.getParent();
if (!isRotate() || originRect == null) {
parent.requestDisallowInterceptTouchEvent(false);
return;
}
switch (event.getAction()&MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
if(containsPoint(event)){
parent.requestDisallowInterceptTouchEvent(true);
}else {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
parent.requestDisallowInterceptTouchEvent(false);
break;
}
}
public boolean containsPoint(MotionEvent event){
if(originRect != null) {
float x = event.getX();
float y = event.getY();
int centerY = originRect.centerY();
int centerX = originRect.centerX();
return x >= centerX - mRadius && x <= centerX + mRadius
&& y >= centerY - mRadius && y <= centerY + mRadius;
}
return false;
}
/**
* 自动滚动的任务
*
* @author zhy
*
*/
private class AutoFlingRunnable implements Runnable
{
private float angelPerSecond;
public AutoFlingRunnable(float velocity)
{
this.angelPerSecond = velocity;
}
public void run()
{
// 如果小于20,则停止
if ((int) Math.abs(angelPerSecond) < 20)
{
isFling = false;
return;
}
isFling = true;
// 不断改变mStartAngle,让其滚动,/30为了避免滚动太快
mStartAngle += (angelPerSecond / 30);
// 逐渐减小这个值
angelPerSecond /= 1.0666F;
mHandler.postDelayed(this, 30);
// 重新布局
listener.onRotate(mStartAngle);
}
}
/**
* 根据触摸的位置,计算角度
*
* @param xTouch
* @param yTouch
* @return
*/
private float getAngle(float xTouch, float yTouch)
{
double x = xTouch- originRect.left - (mRadius );
double y = yTouch- originRect.top - (mRadius );
return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
}
/**
* 根据当前位置计算象限
*
* @param x
* @param y
* @return
*/
private int getQuadrant(float x, float y)
{
int tmpX = (int) (x- originRect.left - mRadius );
int tmpY = (int) (y- originRect.top - mRadius );
if (tmpX >= 0)
{
return tmpY >= 0 ? 4 : 1;
} else
{
return tmpY >= 0 ? 3 : 2;
}
}
public interface OnRotateListener{
void onRotate(double angle);
}
public void setRadius(int radius) {
this.mRadius = radius;
}
public void setOriginRect(Rect originRect){
this.originRect = originRect;
}
public double getStartAngle() {
return mStartAngle;
}
public boolean isRotate() {
return isRotate;
}
public void setRotate(boolean rotate) {
isRotate = rotate;
}
}
package com.daivd.chart.provider;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.animation.Interpolator;
import com.daivd.chart.core.base.BaseChart;
import com.daivd.chart.data.ChartData;
import com.daivd.chart.data.ColumnData;
import com.daivd.chart.data.style.FontStyle;
import com.daivd.chart.listener.OnClickColumnListener;
import com.daivd.chart.provider.component.mark.IMark;
import com.daivd.chart.matrix.MatrixHelper;
import com.daivd.chart.provider.component.text.IText;
/**
* Created by huang on 2017/9/26.
*/
public abstract class BaseProvider<C extends ColumnData> implements IProvider<C> {
private float progress = 1;
protected PointF pointF;
protected IMark<C> markView;
private boolean isOpenMark;
private boolean isShowText =true;
protected ChartData<C> chartData;
private Rect providerRect;
private IText text;
protected OnClickColumnListener<C> onClickColumnListener;
@Override
public boolean calculation(ChartData<C> chartData) {
this.chartData = chartData;
return calculationChild(chartData);
}
public abstract boolean calculationChild(ChartData<C> chartData);
@Override
public void drawProvider(Canvas canvas, Rect rect, MatrixHelper helper, Paint paint) {
providerRect = rect;
matrixRectStart(canvas,rect);
Rect zoomRect = helper.getZoomProviderRect(rect);
drawProvider(canvas,zoomRect,rect,paint);
matrixRectEnd(canvas,rect);
}
protected void matrixRectStart(Canvas canvas, Rect rect){
canvas.save();
canvas.clipRect(rect);
}
protected void matrixRectEnd(Canvas canvas, Rect rect){
canvas.restore();
}
protected abstract void drawProvider(Canvas canvas, Rect zoomRect,Rect rect, Paint paint);
@Override
public void clickPoint(PointF point) {
pointF = point;
}
@Override
public void startAnim(final BaseChart chartView, int duration, Interpolator interpolator) {
ValueAnimator animator = ValueAnimator.ofFloat(0,1);
animator.setDuration(duration);
animator.setInterpolator(interpolator);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
progress = (float) animation.getAnimatedValue();
chartView.invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
progress = 1;
chartView.invalidate();
}
@Override
public void onAnimationEnd(Animator animation) {
progress = 1;
chartView.invalidate();
}
});
animator.start();
}
public void drawPointText(Canvas canvas,double value,float x,float y,int position,int line,Paint paint){
if(text != null) {
String val = String.valueOf(value);
if (containsRect(x,y)) {
text.drawText(canvas,val,x,y,position,line,paint);
}
}
}
public boolean containsRect( float x,float y){
Rect rect = providerRect;
return y>= rect.top -1 && y<= rect.bottom+1
&& x>= rect.left-1 && x<= rect.right+1;
}
public boolean isOpenMark() {
return isOpenMark;
}
public void setOpenMark(boolean openMark) {
isOpenMark = openMark;
}
public abstract double[] setMaxMinValue(double maxMinValue, double minValue);
@Override
public void setMarkView(IMark markView) {
this.markView = markView;
}
protected float getAnimValue(float value){
return value*progress;
}
public Rect getProviderRect() {
return providerRect;
}
public float getProgress() {
return progress;
}
public boolean isShowText() {
return isShowText;
}
public void setShowText(boolean showText) {
isShowText = showText;
}
public void setOnClickColumnListener(OnClickColumnListener<C> onClickColumnListener) {
this.onClickColumnListener = onClickColumnListener;
}
public IText getText() {
return text;
}
public void setText(IText text) {
this.text = text;
}
}
package com.daivd.chart.provider;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.animation.Interpolator;
import com.daivd.chart.core.base.BaseChart;
import com.daivd.chart.data.ChartData;
import com.daivd.chart.data.ColumnData;
import com.daivd.chart.listener.OnClickColumnListener;
import com.daivd.chart.provider.component.mark.IMark;
import com.daivd.chart.matrix.MatrixHelper;
/**
* Created by huang on 2017/9/26.
*/
public interface IProvider<C extends ColumnData> {
boolean calculation( ChartData<C> chartData);
void drawProvider(Canvas canvas, Rect rect, MatrixHelper helper, Paint paint);
void clickPoint(PointF point);
void startAnim(BaseChart chartView, int duration, Interpolator interpolator);
void setMarkView(IMark markView);
void setOnClickColumnListener(OnClickColumnListener<C> onClickColumnListener);
}
package com.daivd.chart.provider.barLine;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import com.daivd.chart.data.BarData;
/**柱状内容绘制
* Created by huang on 2017/9/26.
*/
public class Bar3DProvider extends BarProvider<BarData> {
/**
* 绘制柱
* @param canvas 画布
* @param rect 范围
* @param value 数值
* @param position 位置
* @param line 线位置
* @param paint 画笔
*/
@Override
protected void drawBar(Canvas canvas,Rect rect,double value,int position,int line,Paint paint){
canvas.drawRect(rect, paint);
float w = rect.right - rect.left;
float offsetY= w/2;
float offsetX = w/3f;
Path path = new Path();
path.moveTo(rect.left,rect.top);
path.lineTo(rect.left+offsetX,rect.top-offsetY);
path.lineTo(rect.right+offsetX,rect.top-offsetY);
path.lineTo(rect.right,rect.top);
path.close();
canvas.drawPath(path,paint);
path.reset();
path.moveTo(rect.right,rect.top);
path.lineTo(rect.right,rect.bottom);
path.lineTo(rect.right+offsetX,rect.bottom-offsetY);
path.lineTo(rect.right+offsetX,rect.top-offsetY);
path.close();
canvas.drawPath(path,paint);
drawPointText(canvas,value,(rect.right + rect.left) / 2, rect.top-offsetY,position,line, paint );
}
@Override
public double[] setMaxMinValue(double maxValue, double minValue) {
double dis = Math.abs(maxValue -minValue);
maxValue = maxValue + dis*0.4;
if(minValue >0){
minValue = 0;
}else{
minValue = minValue - dis*0.4;
}
return new double[]{maxValue,minValue};
}
}
package com.daivd.chart.provider.barLine;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import com.daivd.chart.data.BarLineData;
import com.daivd.chart.data.style.LineStyle;
import com.daivd.chart.exception.ChartException;
import com.daivd.chart.provider.component.line.BrokenLineModel;
import com.daivd.chart.provider.component.line.CurveLineModel;
import java.util.ArrayList;
import java.util.List;
/**状状图和线性图结合
* Created by huang on 2017/9/26.
*/
public class BarLineProvider extends BarProvider<BarLineData> {
private List<Float> pointX = new ArrayList<>();
private List<Float> pointY = new ArrayList<>();
private LineStyle lineStyle = new LineStyle();
@Override
public void drawProvider(Canvas canvas, Rect zoomRect, Rect rect, Paint paint) {
paint.setStyle(Paint.Style.FILL);
List<BarLineData> columnDataList = chartData.getColumnDataList();
int barColumnSize = getBarColumnSize();
int rowSize = chartData.getCharXDataList().size();
double perWidth = zoomRect.width()/(barColumnSize*rowSize);
double perBarWidth =perWidth - getCategoryPadding() / 2;
int columnSize = columnDataList.size();
int barPosition =-1;
for (int i = 0; i < columnSize; i++) {
BarLineData columnData = columnDataList.get(i);
if (!columnData.isDraw()) {
continue;
}
if(columnData.getModel() == BarLineData.BAR) {
barPosition++;
}
for (int j = 0; j < rowSize; j++) {
double value = columnData.getChartYDataList().get(j);
int top = (int) getStartY(zoomRect, value, columnData.getDirection());
if(columnData.getModel() == BarLineData.BAR) {
int left = (int) (((j * barColumnSize + barPosition) * perBarWidth)
+ j * getCategoryPadding() + zoomRect.left);
int right = (int) (left + perBarWidth) - getSeriesPadding();
int bottom = zoomRect.bottom;
paint.setColor(columnData.getColor());
Rect barRect = new Rect(left, top, right, bottom);
drawBar(canvas, barRect, value,i,j, paint);
}else{
float x = (float)(zoomRect.left+perWidth*(j+0.5));
drawLine(canvas,j== 0,j == rowSize-1,columnData,x,top,value,i,j,paint);
}
}
}
drawLevelLine(canvas, zoomRect, paint);
}
protected void drawLine(Canvas canvas, boolean isNewBar,boolean isLast, BarLineData columnData, float x,float y, double value,int position,int line, Paint paint) {
if(columnData.getModel() ==BarLineData.CURVE
|| columnData.getModel() ==BarLineData.BROKEN){
if(isNewBar){
pointX.clear();
pointY.clear();
}
pointX.add(x);
pointY.add(y);
drawPointText(canvas,value,x, y,position,line,paint);
if(isLast){
Path path;
if(columnData.getModel() ==BarLineData.CURVE){
path = new CurveLineModel().getLinePath(pointX,pointY);
}else{
path = new BrokenLineModel().getLinePath(pointX,pointY);
}
lineStyle.setColor(paint.getColor());
lineStyle.fillPaint(paint);
canvas.drawPath(path,paint);
}
}
}
protected int getBarColumnSize() {
List<BarLineData> lineDates = chartData.getColumnDataList();
int barSize = 0;
for(int i =0;i < lineDates.size();i++){
if(lineDates.get(i).getModel() == BarLineData.BAR){
barSize++;
}
}
if(barSize == 0){
throw new ChartException("There must be a set of bar data!");
}
return barSize;
}
public LineStyle getLineStyle() {
return lineStyle;
}
public void setLineStyle(LineStyle lineStyle) {
this.lineStyle = lineStyle;
}
}
package com.daivd.chart.provider.barLine;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import com.daivd.chart.data.BarData;
import com.daivd.chart.utils.ColorUtils;
import java.util.List;
/**
* 柱状内容绘制
* Created by huang on 2017/9/26.
*/
public class BarProvider<C extends BarData> extends BaseBarLineProvider<C> {
private int categoryPadding = 20;
private int seriesPadding = 10;
@Override
public void drawProvider(Canvas canvas, Rect zoomRect, Rect rect, Paint paint) {
paint.setStyle(Paint.Style.FILL);
paint.setTextAlign(Paint.Align.LEFT);
List<C> columnDataList = chartData.getColumnDataList();
int columnSize = columnDataList.size();
int rowSize = chartData.getCharXDataList().size();
double width = (zoomRect.width()-(columnSize+1)*categoryPadding) / (columnSize * rowSize);
PointF clickPoint = null;
int clickPosition = 0;
int clickColumnPosition = 0;
boolean isClickRect1 = (pointF != null && rect.contains((int) pointF.x, (int) pointF.y));
boolean isClickRect = false;
for (int i = 0; i < columnSize; i++) {
for (int j = 0; j < rowSize; j++) {
C columnData = columnDataList.get(i);
if (!columnData.isDraw()) {
continue;
}
double value = columnData.getChartYDataList().get(j);
int left = (int) (((j * columnSize + i) * width)
+ j * categoryPadding + zoomRect.left);
int right = (int) (left + width) - seriesPadding;
int top = (int) getStartY(zoomRect, value, columnData.getDirection());
int bottom = zoomRect.bottom;
paint.setColor(columnData.getColor());
if (isClickRect1 && isClickRect(left, right, top, bottom)) {
paint.setColor(ColorUtils.getDarkerColor(columnData.getColor()));
isClickRect = true;
clickPoint = new PointF((left + right) / 2, top);
clickPosition = j;
clickColumnPosition = i;
if (onClickColumnListener != null) {
onClickColumnListener.onClickColumn(columnData, j);
}
}
Rect barRect = new Rect(left, top, right, bottom);
drawBar(canvas, barRect, value,i,j, paint);
drawTip(canvas,left+barRect.width()/2,top,columnData,j);
}
}
drawLevelLine(canvas, zoomRect, paint);
if (isClickRect && containsRect(clickPoint.x, clickPoint.y)) {
drawCross(canvas, clickPoint.x, clickPoint.y, zoomRect, paint);
super.matrixRectEnd(canvas, rect);
drawMark(canvas,clickPoint.x, clickPoint.y,rect, clickPosition, clickColumnPosition);
}
}
@Override
protected void matrixRectEnd(Canvas canvas, Rect rect) {
}
protected void drawBar(Canvas canvas, Rect rect,double value, int position,int line,Paint paint) {
canvas.drawRect(rect, paint);
drawPointText(canvas, value, (rect.right + rect.left) / 2, rect.top, position,line,paint);
}
private void drawCross(Canvas canvas, float x, float y, Rect rect, Paint paint) {
if (isOpenCross() && getCross() != null) {
PointF pointF = new PointF(x, y);
getCross().drawCross(canvas, pointF, rect, paint);
}
}
protected boolean isClickRect(float left, float right, float top, float bottom) {
return pointF != null && pointF.x >= left && pointF.x <= right && pointF.y >= top && pointF.y <= bottom;
}
@Override
public double[] setMaxMinValue(double maxValue, double minValue) {
double dis = Math.abs(maxValue - minValue);
maxValue = maxValue + dis * 0.2;
if (minValue > 0) {
minValue = 0;
} else {
minValue = minValue - dis * 0.2;
}
return new double[]{maxValue, minValue};
}
public int getSeriesPadding() {
return seriesPadding;
}
public void setSeriesPadding(int seriesPadding) {
this.seriesPadding = seriesPadding;
}
public int getCategoryPadding() {
return categoryPadding;
}
public void setCategoryPadding(int categoryPadding) {
this.categoryPadding = categoryPadding;
}
}
package com.daivd.chart.provider.barLine;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import com.daivd.chart.component.base.IAxis;
import com.daivd.chart.data.ChartData;
import com.daivd.chart.provider.component.cross.ICross;
import com.daivd.chart.provider.component.grid.IGrid;
import com.daivd.chart.provider.component.level.ILevel;
import com.daivd.chart.provider.component.level.LevelLine;
import com.daivd.chart.data.BarData;
import com.daivd.chart.data.ScaleData;
import com.daivd.chart.exception.ChartException;
import com.daivd.chart.provider.BaseProvider;
import com.daivd.chart.provider.component.path.IPath;
import com.daivd.chart.provider.component.tip.ITip;
import java.util.ArrayList;
import java.util.List;
/**线和柱状内容绘制
* Created by huang on 2017/9/26.
*/
public abstract class BaseBarLineProvider<C extends BarData> extends BaseProvider<C> {
private ICross cross;
private boolean isOpenCross;
protected ITip<C,?> tip;
private List<ILevel> levelLine = new ArrayList<>();
private float chartPercent = 1;
private boolean isFromBottom;
@Override
public boolean calculationChild( ChartData<C> chartData) {
this.chartData = chartData;
ScaleData scaleData =this.chartData.getScaleData();
List<C> columnDataList = chartData.getColumnDataList();
if(columnDataList == null || columnDataList.size() == 0){
return false;
}
scaleData.rowSize = chartData.getCharXDataList().size();
int columnSize = columnDataList.size();
for(int i = 0 ; i <columnSize; i++){
BarData columnData = columnDataList.get(i);
if(!columnData.isDraw()){
continue;
}
List<Double> chartYDataList = columnData.getChartYDataList();
if(chartYDataList == null || chartYDataList.size() == 0){
throw new ChartException("Please set up Column data");
}
// if(chartYDataList.size() != scaleData.rowSize){
// throw new ChartException("Column rows data inconsistency");
// }
double[] scale = getColumnScale(chartYDataList);
scale = setMaxMinValue(scale[0],scale[1]);
if(columnData.getDirection() == IAxis.AxisDirection.LEFT){
if(!scaleData.isLeftHasValue){
scaleData.maxLeftValue = scale[0];
scaleData.minLeftValue = scale[1];
scaleData.isLeftHasValue = true;
}else{
scaleData.maxLeftValue = Math.max( scaleData.maxLeftValue,scale[0]);
scaleData.minLeftValue = Math.min( scaleData.minLeftValue,scale[1]);
}
}else{
if(!scaleData.isRightHasValue){
scaleData.maxRightValue = scale[0];
scaleData.minRightValue= scale[1];
scaleData.isRightHasValue = true;
}else{
scaleData.maxRightValue = Math.max(scaleData.maxRightValue,scale[0]);
scaleData.minRightValue = Math.min(scaleData.minRightValue,scale[1]);
}
}
}
return chartData.getScaleData().rowSize != 0;
}
private double[] getColumnScale(List<Double> values) {
double maxValue = 0;
double minValue =0;
int size = values.size();
for(int j= 0;j < size;j++) {
double d = values.get(j) ;
if(j == 0){
maxValue = d;
minValue = d;
}
if (maxValue < d){
maxValue = d;
}else if(minValue >d){
minValue = d;
}
}
return new double[] {maxValue,minValue};
}
protected float getStartY(Rect zoomRect, double value, int direction){
ScaleData scaleData = chartData.getScaleData();
double minValue = scaleData.getMinScaleValue(direction);
double totalScaleLength = scaleData.getTotalScaleLength(direction);
float length = (float) ((value - minValue) * zoomRect.height()*chartPercent / totalScaleLength);
return zoomRect.bottom-(isFromBottom?0:(1-chartPercent)/2*zoomRect.height()) - length;
}
/**
* 绘制水平线
*/
void drawLevelLine(Canvas canvas, Rect zoomRect, Paint paint){
if(levelLine.size() > 0) {
for(ILevel level:levelLine){
float levelY = getStartY(zoomRect,level.getValue(),level.getAxisDirection());
if(containsRect(getProviderRect().centerX(),levelY)) {
level.drawLevel(canvas, getProviderRect(), levelY, paint);
}
}
}
}
/**
*绘制提示
*/
void drawTip(Canvas canvas,float x, float y,C c,int position){
if(tip != null){
tip.drawTip(canvas,x,y,getProviderRect(),c,position);
}
}
void drawMark(Canvas canvas,float x, float y,Rect rect, int position, int columnPosition) {
if (markView != null && isOpenMark()) {
markView.drawMark(canvas,x, y, rect,chartData.getCharXDataList().get(position),
chartData.getColumnDataList().get(columnPosition), position);
}
}
public abstract double[] setMaxMinValue(double maxMinValue, double minValue);
public void addLevelLine(ILevel levelLine) {
this.levelLine.add(levelLine);
}
public boolean isOpenCross() {
return isOpenCross;
}
public void setOpenCross(boolean openCross) {
isOpenCross = openCross;
}
public ICross getCross() {
return cross;
}
public void setCross(ICross cross) {
this.cross = cross;
}
public List<ILevel> getLevelLine() {
return levelLine;
}
public ITip<C,?> getTip() {
return tip;
}
public void setTip(ITip<C,?> tip) {
this.tip = tip;
}
public float getChartPercent() {
return chartPercent;
}
public boolean isFromBottom() {
return isFromBottom;
}
public void setFromBottom(boolean fromBottom) {
isFromBottom = fromBottom;
}
public void setChartPercent(float chartPercent) {
this.chartPercent = chartPercent;
}
}
package com.daivd.chart.provider.component.cross;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import com.daivd.chart.data.style.LineStyle;
/**
* Created by huang on 2017/10/19.
* 双方向
*/
public class DoubleDriCross implements ICross{
LineStyle crossStyle = new LineStyle();
@Override
public void drawCross(Canvas canvas, PointF pointF, Rect rect, Paint paint) {
crossStyle.fillPaint(paint);
canvas.drawLine(pointF.x, rect.top, pointF.x, rect.bottom, paint);
canvas.drawLine(rect.left, pointF.y, rect.right, pointF.y, paint);
}
public LineStyle getCrossStyle() {
return crossStyle;
}
public void setCrossStyle(LineStyle crossStyle) {
this.crossStyle = crossStyle;
}
}
package com.daivd.chart.provider.component.cross;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
/**
* Created by huang on 2017/10/19.
* 十字轴
*/
public interface ICross {
void drawCross(Canvas canvas, PointF pointF, Rect rect, Paint paint);
}
package com.daivd.chart.provider.component.cross;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import com.daivd.chart.data.style.LineStyle;
import com.daivd.chart.provider.component.point.IPoint;
import com.daivd.chart.provider.component.point.Point;
/**
* Created by huang on 2017/10/19.
* 竖轴方向
*/
public class VerticalCross implements ICross {
LineStyle crossStyle = new LineStyle();
IPoint point = new Point();
@Override
public void drawCross(Canvas canvas, PointF pointF, Rect rect, Paint paint) {
crossStyle.fillPaint(paint);
canvas.drawLine(pointF.x, rect.top, pointF.x, rect.bottom, paint);
if (point != null) {
point.drawPoint(canvas, pointF.x, pointF.y, 0, false, paint);
}
}
public IPoint getPoint() {
return point;
}
public void setPoint(IPoint point) {
this.point = point;
}
public LineStyle getCrossStyle() {
return crossStyle;
}
public void setCrossStyle(LineStyle crossStyle) {
this.crossStyle = crossStyle;
}
}
package com.daivd.chart.provider.component.grid;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
/**
* Created by huang on 2017/12/1.
*/
public interface IGrid {
/**
* 绘制网格
* @param canvas
* @param x
* @param rect
* @param perWidth
* @param paint
*/
void drawGrid(Canvas canvas, float x, Rect rect, int perWidth, Paint paint);
/**
* 绘制点击背景
* @param canvas
* @param pointF
* @param rect
* @param perWidth
* @param paint
*/
void drawClickBg(Canvas canvas, PointF pointF, Rect rect, int perWidth, Paint paint);
/**
* 绘制列内容
* @param canvas
* @param position
* @param rect
* @param perWidth
* @param paint
*/
void drawColumnContent(Canvas canvas, int position, Rect zoomRect, Rect rect, int perWidth, Paint paint);
}
package com.daivd.chart.provider.component.level;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
/**
* Created by huang on 2017/10/19.
*/
public interface ILevel {
int getAxisDirection();
void drawLevel(Canvas canvas, Rect rect, float y, Paint paint);
double getValue();
}
package com.daivd.chart.provider.component.level;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import com.daivd.chart.component.base.IAxis;
import com.daivd.chart.data.style.FontStyle;
import com.daivd.chart.data.style.LineStyle;
/**
* Created by huang on 2017/9/30.
* LevelLine
*/
public class LevelLine implements ILevel{
public static int left = 0;
public static int right = 1;
private LineStyle lineStyle = new LineStyle();
private double value;
private int textDirection = right;
private FontStyle textStyle = new FontStyle();
private int direction = IAxis.AxisDirection.LEFT;
public LevelLine(double value) {
this.value = value;
}
public LineStyle getLineStyle() {
return lineStyle;
}
public void setLineStyle(LineStyle lineStyle) {
this.lineStyle = lineStyle;
}
public double getValue() {
return value;
}
public void setValue(double value) {
this.value = value;
}
public int getTextDirection() {
return textDirection;
}
public void setTextDirection(int textDirection) {
this.textDirection = textDirection;
}
public FontStyle getTextStyle() {
return textStyle;
}
public int getAxisDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
public void setTextStyle(FontStyle textStyle) {
this.textStyle = textStyle;
}
@Override
public void drawLevel(Canvas canvas, Rect rect, float y, Paint paint) {
getLineStyle().fillPaint(paint);
Path path = new Path();
path.moveTo(rect.left, y);
path.lineTo(rect.right, y);
canvas.drawPath(path, paint);
getTextStyle().fillPaint(paint);
float textHeight = paint.measureText("1", 0, 1);
float startX;
float startY = y - textHeight + getLineStyle().getWidth();
String levelLineValue = String.valueOf(getValue());
if (getTextDirection() == LevelLine.left) {
startX = rect.left;
} else {
startX = rect.right - textHeight * levelLineValue.length();
}
canvas.drawText(levelLineValue, startX, startY, paint);
}
}
package com.daivd.chart.provider.component.line;
import android.graphics.Path;
import java.util.List;
public class BrokenLineModel implements ILineModel {
@Override
public Path getLinePath(List<Float> pointX, List<Float> pointY) {
Path path = new Path();
for(int i = 0; i < pointY.size();i++){
float x = pointX.get(i);
float y = pointY.get(i);
if(i == 0){
path.moveTo(x,y);
}else{
path.lineTo(x,y);
}
}
return path;
}
}
package com.daivd.chart.provider.component.line;
import android.graphics.Path;
import java.util.LinkedList;
import java.util.List;
/**曲线内容绘制
* Created by huang on 2017/9/27.
*/
public class CurveLineModel implements ILineModel {
private final int STEP = 12;
@Override
public Path getLinePath(List<Float> pointX, List<Float> pointY) {
List<Cubic> calculate_x = calculate(pointX);
List<Cubic> calculate_y = calculate(pointY);
Path path = new Path();
if (null != calculate_x && null != calculate_y && calculate_y.size() >= calculate_x.size()) {
path.moveTo(calculate_x.get(0).eval(0), calculate_y.get(0).eval(0));
for (int i = 0; i < calculate_x.size(); i++) {
for (int j = 1; j <= STEP; j++) {
float u = j / (float) STEP;
path.lineTo(calculate_x.get(i).eval(u), calculate_y.get(i).eval(u));
}
}
}
return path;
}
/**
* 计算曲线.
* @param x
* @return
*/
private List<Cubic> calculate(List<Float> x) {
if (null != x && x.size() > 0) {
int n = x.size() - 1;
float[] gamma = new float[n + 1];
float[] delta = new float[n + 1];
float[] D = new float[n + 1];
int i;
gamma[0] = 1.0f / 2.0f;
for (i = 1; i < n; i++) {
gamma[i] = 1 / (4 - gamma[i - 1]);
}
gamma[n] = 1 / (2 - gamma[n - 1]);
delta[0] = 3 * (x.get(1) - x.get(0)) * gamma[0];
for (i = 1; i < n; i++) {
delta[i] = (3 * (x.get(i + 1) - x.get(i - 1)) - delta[i - 1]) * gamma[i];
}
delta[n] = (3 * (x.get(n) - x.get(n - 1)) - delta[n - 1]) * gamma[n];
D[n] = delta[n];
for (i = n - 1; i >= 0; i--) {
D[i] = delta[i] - gamma[i] * D[i + 1];
}
List<Cubic> cubicList = new LinkedList<>();
for (i = 0; i < n; i++) {
Cubic c = new Cubic(x.get(i), D[i], 3 * (x.get(i + 1) - x.get(i)) - 2 * D[i] - D[i + 1], 2 * (x.get(i) - x.get(i + 1)) + D[i] + D[i + 1]);
cubicList.add(c);
}
return cubicList;
}
return null;
}
private class Cubic {
float a, b, c, d; /* a + b*u + c*u^2 +d*u^3 */
Cubic(float a, float b, float c, float d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
/** evaluate cubic */
float eval(float u) {
return (((d * u) + c) * u + b) * u + a;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment