折线图实现
来源:互联网 发布:快速排序算法实现 编辑:程序博客网 时间:2024/06/05 21:04
这样的图用来做统计最方便了,今天,我们又要摆脱第三方的约束,自己来实现了,是不是很开心,现在就来动手吧。
本文内容需要读者具备一定的自定义view基础,否则看起来可能比较费力,不过懂的看门道,不懂的可以凑个热闹,能看懂可以自己去改不好的地方,不懂的也可以直接拿来用。
先给个效果图:
看起来还可以吧,废话不多说,开始我们的绘制。
首先要确定我们要达成的效果,下面列出来
1:数据的获取2:折线的绘制3:X坐标文字的绘制4:数字的绘制5:数字的位置判断,防止与折线重合6:随手指滑动的效果
这样思路清晰了,下面逐个击破
1 数据的获取
这恐怕是最简单的一步了,我在里面定义了一个方法,包含三个参数,使用的时候直接传进去,再刷新试图即可,给出方法
//设置整个坐标上的一些文字和数字 public void setData(String[] coordinate, String[] number, float extremum) { this.coordinate = coordinate; this.number = number; this.extremum = extremum; }
可以看见传入了三个参数,一个是坐标数组,一个是数字数组,这里用字符数组代替的,再一个就是Y轴上最大值
2 折线的绘制
绘制折线肯定是要使用到path,那么就要把path拿来,然后添上路径即可,那么主要就是路径坐标的问题了,X坐标我用的文字宽度加上文字和文字之间距离形成最后的坐标,然后遍历数组就能获得一个个X坐标,Y坐标我添加了一个功能,那就是能设置绘制图的上边距和下边距,其实X坐标我也设置了左边距,具体的使用大家可以去里面更改数值感受一下,如果上边距加上下编剧正好等于控件的高度的话,那就整个图就是一个直线,你懂的。
给出绘制路径代码:
float deviationY = (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM; //这里式子没有简化,是为了方便你们清楚这是怎么一个过程 float deviationX = movedistance + OFFSETX + txtrect.width() * i + txtpading * i; //这里式子没有简化,是为了方便你们清楚这是怎么一个过程 //画出折线图的路径 path.lineTo(deviationX, deviationY); canvas.drawPath(path, linepaint); //绘制折线
解释一下,这里的movedistance就是随手指滑动会产生的位移,OFFSETX则是X轴左边距,OFFSETYTOP是Y轴上边距,OFFSETYBOTTOM是Y轴下边距。
3 X坐标文字的绘制
文字的绘制比较简单了,主要就是坐标的把握,Y轴没难度,都用问题的高度即可,X轴呢,其实就是刚才绘制折线的X坐标再减去文字宽度的一半即可,给出代码参考:
canvas.drawText(coordinate[i] + "", deviationX - txtrect.width() / 2, height - txtrect.height() / 2, extremumpaint); //绘制X轴上的坐标
4 数字的绘制
数字的绘制相对于文字的绘制就是多了一个Y轴上的变换,当然了,X轴这里要减去的是数字的宽度一半,而不是文字了,高度我们其实用之前折线的高度减去上我们数字的高度的一半即可,给出代码:
canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY - numberrect.height() / 2, txtpaint); //绘制数字
5 数字的位置判断,防止与折线重合
这个问题呢我们可以从折线的走势判断解决,那么当前的数字所在的折线坐标会有以下情况:
1: 中间最高
2: 左边最高,右边最低
3: 左边最低,右边最高
4: 中间最低
就这四种情况,我们可以从Y轴进行判断,判断我提取了成了一个方法,给出代码:
/*这个函数的作用是判断当前数字坐标的y轴和其左右两边的数字坐标的y进行比较,从而判断数字显示的位置,避免折线和数字重复在一起的情况 */ public int judgmentposition(int i) { int status = 1; if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) > (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM && (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM < (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) { status = 1; } else if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) < (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM && (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM < (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) { status = 2; } else if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) > (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM && (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM > (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) { status = 3; } else { status = 4; } Log.d("statusnumbers", status + ""); return status; }
使用时的代码:
//下面的绘制要保证文字在合适的位置,想了解的可参考我另一个博客:http://blog.csdn.net/wanxuedong/article/details/69396732 if (i > 0 && i < number.length - 1) { switch (judgmentposition(i)) { case 1: //中间最高 canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY - numberrect.height(), txtpaint); //绘制数字 break; case 2: //左边最高,右边最低 canvas.drawText(number[i] + "", deviationX, deviationY - numberrect.height() / 2, txtpaint); //绘制数字 break; case 3: //左边最低,右边最高 canvas.drawText(number[i] + "", deviationX - numberrect.width(), deviationY - numberrect.height() / 2, txtpaint); //绘制数字 break; case 4: //中间最低 canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY + numberrect.height() * 2, txtpaint); //绘制数字 break; } } else { canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY - numberrect.height() / 2, txtpaint); //绘制数字 }
6 随手指滑动的效果
和手势有关的就不得不使用onTouchEvent方法了,给出代码:
//这里面我们实现了让该控件可以随手指挥动而滚动,但是没写滚动条(因为觉得有滚动条也不好看) @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: positionx = event.getRawX(); break; case MotionEvent.ACTION_MOVE: //如果是往右滑动,那么左侧距离就要判断不能过大 if (event.getRawX() - positionx > 0) { if (!(movedistance > leftdistance)) { movedistance += event.getRawX() - positionx; positionx = event.getRawX(); invalidate(); } } else { //如果是往右滑动,那么左侧距离也要判断不能过大 if (!(movedistance < -rightdistance)) { movedistance += event.getRawX() - positionx; positionx = event.getRawX(); invalidate(); } } positionx = event.getRawX(); break; case MotionEvent.ACTION_UP: break; } return true; }
有两个值需要解释一下,leftdistance和rightdistance,这是我们可以再开始时设置的左右滑动的最大距离,具体可以自己进去设置。
好了,总的效果也就这样完成了,解剖后是不是看起来又有点简单了,学习就是这样,能者不惧,你就是能者。
下面给出整个代码(可以直接粘贴复制进行使用):
import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.Rect;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;/** * Created by wanxuedong on 2017/08/30. * 联系QQ:2381144912 */public class LineChart extends View { //这三个值是我们传进来的,绘图的数据从这里来 private String[] coordinate; //X坐标上的标记 private String[] number; //折线图上的一个个数字,传值的时候只能传数字,别瞎传哈 private float extremum; //Y坐标的最大值// private int width; //控件的宽度 private int height; //控件的高度 private Path path; //折线的路径 private Paint linepaint; //折线的画笔 private Paint rectpaint; //方块的画笔 private Paint extremumpaint; //坐标的画笔 private Paint txtpaint; //数值文字的画笔 // private Rect rect; //方块的模型 private Rect txtrect; //利用rect获取X坐标上文字的宽度,好精确的放置文字位置 private Rect numberrect; //利用rect获取数字的宽度,好精确的放置文字位置 //下面两个属性用来操作手势有关的 private float positionx = 0; //手指移动的时候,记录X坐标的作用 private float movedistance; //手指每次移动的位移,整个图像会根据手势进行移动 /* * 上面的数据基本上不需要动,下面的属性我们可以自行更改 * */ // private int RECTWIDTH = 5; //小方块的宽度 private int RECTHEIGHT = 6; //小方块的高度和圆的半径 private int OFFSETYTOP = 100; //Y轴方向顶端留下的长度 private int OFFSETYBOTTOM = 100; //Y轴方向底端留下的长度 private int OFFSETX = 100; //x轴方向左端留下的长度 private int XTXTSIZE = 24; //X轴文字大小 private int NUMBERSIZE = 18; //数字文字大小 private int LINEWIDTH = 3; //折线粗度 private int txtpading = 80; //X坐标轴上每个文字之间的间隔大小,这个决定了你的控件有多宽 //下面两个属性需要根据实际情况自己改动,如果有好的方案,望请告知 private int leftdistance = 50; //X轴手势往右滑动的最大距离,这个数字会和OFFSETX加起来,总的数值才是左侧看起来的距离 private int rightdistance = 500; //X轴手势往左滑动的最大距离 public LineChart(Context context) { super(context); path = new Path(); initpaint(); } public LineChart(Context context, AttributeSet attrs) { super(context, attrs); path = new Path(); initpaint(); } //测量获取控件宽高 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec);// width = getMeasuredWidth(); height = getMeasuredHeight(); } //这里面开始绘制文字,数字,和折线 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //定位折线的起始位置,也就是第一个数字坐标点 path.moveTo(OFFSETX + movedistance, (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[0]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM); for (int i = 0; i < number.length; i++) {// rect = new Rect(); txtrect = new Rect(); numberrect = new Rect(); extremumpaint.getTextBounds(coordinate[i], 0, coordinate[i].length(), txtrect); txtpaint.getTextBounds(number[i], 0, number[i].length(), numberrect); float deviationY = (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM; //这里式子没有简化,是为了方便你们清楚这是怎么一个过程 float deviationX = movedistance + OFFSETX + txtrect.width() * i + txtpading * i; //这里式子没有简化,是为了方便你们清楚这是怎么一个过程 //画出折线图的路径 path.lineTo(deviationX, deviationY); canvas.drawPath(path, linepaint); //绘制折线 //设置方块的位置和宽高属性// rect.set(width / number.length * i - RECTWIDTH / 2 + OFFSETX, (int) (deviationY - RECTHEIGHT / 2), width / number.length * i + RECTWIDTH / 2 + OFFSETX, (int) (deviationY + RECTHEIGHT / 2));// canvas.drawRect(rect, rectpaint); //绘制小方块// 下面是绘制小圆球,上面是绘制小方块,有需要的可以恢复上面的小方块(不过坐标自己去设置了,懒的再去看去改了) canvas.drawCircle(deviationX, deviationY, RECTHEIGHT, rectpaint); //下面的绘制要保证文字在合适的位置,想了解的可参考我另一个博客:http://blog.csdn.net/wanxuedong/article/details/69396732 if (i > 0 && i < number.length - 1) { switch (judgmentposition(i)) { case 1: //中间最高 canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY - numberrect.height(), txtpaint); //绘制数字 break; case 2: //左边最高,右边最低 canvas.drawText(number[i] + "", deviationX, deviationY - numberrect.height() / 2, txtpaint); //绘制数字 break; case 3: //左边最低,右边最高 canvas.drawText(number[i] + "", deviationX - numberrect.width(), deviationY - numberrect.height() / 2, txtpaint); //绘制数字 break; case 4: //中间最低 canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY + numberrect.height() * 2, txtpaint); //绘制数字 break; } } else { canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY - numberrect.height() / 2, txtpaint); //绘制数字 } //上面为了数字位置进行了判断设置,但是不包括第一个数字和最后一个数字,有需要的可以自行添加 canvas.drawText(coordinate[i] + "", deviationX - txtrect.width() / 2, height - txtrect.height() / 2, extremumpaint); //绘制X轴上的坐标 } path.reset(); //每次重绘的时候需要把path恢复成空的路径,否则,所有路径会叠加在一起,那到时就热闹了 } //这里面我们实现了让该控件可以随手指挥动而滚动,但是没写滚动条(因为觉得有滚动条也不好看) @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: positionx = event.getRawX(); break; case MotionEvent.ACTION_MOVE: //如果是往右滑动,那么左侧距离就要判断不能过大 if (event.getRawX() - positionx > 0) { if (!(movedistance > leftdistance)) { movedistance += event.getRawX() - positionx; positionx = event.getRawX(); invalidate(); } } else { //如果是往右滑动,那么左侧距离也要判断不能过大 if (!(movedistance < -rightdistance)) { movedistance += event.getRawX() - positionx; positionx = event.getRawX(); invalidate(); } } positionx = event.getRawX(); break; case MotionEvent.ACTION_UP: break; } return true; } //初始化画笔,读者看的时候可略过 public void initpaint() { linepaint = new Paint(); rectpaint = new Paint(); extremumpaint = new Paint(); txtpaint = new Paint(); //折线的一些属性 linepaint.setColor(Color.argb(255, 245, 133, 61)); //设置颜色 linepaint.setStyle(Paint.Style.STROKE); //设置空心填充 linepaint.setAntiAlias(true); //设置抗锯齿 linepaint.setStrokeWidth(LINEWIDTH); //设置粗度 //方块的一些属性 rectpaint.setColor(Color.argb(255, 245, 133, 61)); //X坐标轴上文字的一些属性 extremumpaint.setColor(Color.argb(255, 51, 51, 51)); extremumpaint.setTextSize(XTXTSIZE); //设置坐标文字大小 extremumpaint.setAntiAlias(true); //数字的一些属性 txtpaint.setColor(Color.argb(255, 145, 145, 145)); txtpaint.setTextSize(NUMBERSIZE); //设置数字大小 txtpaint.setAntiAlias(true); } /*这个函数的作用是判断当前数字坐标的y轴和其左右两边的数字坐标的y进行比较,从而判断数字显示的位置,避免折线和数字重复在一起的情况 */ public int judgmentposition(int i) { int status = 1; if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) > (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM && (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM < (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) { status = 1; } else if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) < (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM && (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM < (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) { status = 2; } else if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) > (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM && (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM > (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) { status = 3; } else { status = 4; } Log.d("statusnumbers", status + ""); return status; } //设置整个坐标上的一些文字和数字 public void setData(String[] coordinate, String[] number, float extremum) { this.coordinate = coordinate; this.number = number; this.extremum = extremum; }}
最后还是给出demo,走你:http://download.csdn.net/download/wanxuedong/9958951
- JFreeChart实现折线图
- canvas实现折线图
- 实现折线图
- 折线图实现
- Android实现折线图
- C#.NET实现折线图
- extjs 双折线图实现
- unity实现折线图功能
- andriod折线图简单实现
- QCustomPlot实现动态折线图
- jqplot折线图实现 例子
- Android 折线图的实现
- MPAndroidChart实现的折线图
- iOS 实现折线图(二)
- html5 canvas 实现折线图
- HighCharts 折线图的实现
- php实现动态折线图,highcharts折线图
- HighCharts 折线图,柱形图,饼图实现
- Mac上画图工具
- 文章标题
- leetcode 21. Merge Two Sorted Lists
- 深入研究Servlet线程安全性问题
- 新路程------wlan0 连接wifi操作
- 折线图实现
- Android Studio 基本设置
- form表单中删除、增加tr
- iOS之屏幕旋转(横屏),看我就够了
- 洛谷 P1071 潜伏者
- Tensorflow的中文网站
- eclipse中的svn突然连不上服务器:connection refused by the server
- Android防QQ侧滑菜单
- java 数组打印四种方法