自定义 折线图

自定义一个简单的折线图,足够展示万条数据,(千条以上数据在1070的屏幕宽度上 只会是一片茫茫,颜色会挤到一块儿。。。。)


简单说一下实现思路,

1、获取当前View在当前屏幕中的位置(Android中屏幕的左上角为整个屏幕坐标的原点往右 、往下递增,我们自己的坐标系是符合数学逻辑的二维坐标系,原点在整个折线图的左下角)

2、首先计算坐标点(比如,我们自己的坐标系的Y轴长度为画布的高度canasWidth减去50[我们默认距顶50],我们把获取到  的高度数值定为原点的Y轴坐标,X的走向与Android原生一致,  所以想让原点距离屏幕左边多远 ,就设置所多大的值为原点在屏幕的X坐标,记得留出足够的空间,需要去绘制Y轴刻度)

3、然后先绘制出X轴、Y轴,并根据数据的长度计算Y、X轴刻度(有十条数据,X轴长度除以10就是X周刻度,Y轴同理,根据       所有数据里的最大数值计算Y轴刻度)

4、别忘了绘制刻度数据,位置的计算在上一步中有相同之处,可以获取上边的值使用

5、绘制折线与虚线(X轴刻度)使用drawLines去绘制,会节省资源,虚线也要用这个去绘制,若使用(drawLine)绘制,会很慢

以下为自定义View核心类,在java代码中添加,目前没有做在布局中直接使用,另:提供demo供大家参考(点击此处),若有问题欢迎添加QQ  :1017726485.共同讨论




import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.ColorRes;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;


import com.jjf.customchartline.R;
import com.jjf.customchartline.utils.Arith;
import com.jjf.customchartline.been.chartline.ChartResultData;
import com.jjf.customchartline.been.chartline.LineBeen;
import com.jjf.customchartline.been.chartline.LinePain;

import java.math.BigDecimal;
import java.util.ArrayList;

/**
 * @author: jjf
 * @date: 2018/4/19
 * @describe:
 */
public abstract class MYChartLine extends View {
    private float canasWidth;                 //画布宽
    private float canasHeight;
    private float XPoint;                  //定义原点
    private float YPoint;
    private double XScale;                  //刻度间距
    private int YScale;
    private int topDim = 30;//距离顶部
    private int surplusHeight = 50;//Y轴超出最大刻度距离(计算Y轴时总长减去超出距离)
    private int Ylenth = 6;//Y轴刻度数量
    private int surplusWhith = 50;//X轴超出最大刻度距离
    private String TAG = "MYChartLine";
    private Context context;
    //容器宽高
    private int viewGroupWidth;
    private int viewGroupHeight;
    //虚线间隙
    int dottedspace = 3;//虚线间隙
    int dottedLength = 10;//虚线长度

    //坐标轴宽度
    private int axisWidth = 1;
    //坐标轴颜色
    private @ColorRes
    int axisColor = R.color.zuobiao;//#a6a6a6
    Resources resources;

    int color;//
    int colorBlue;

    public int getYlenth() {
        return Ylenth;
    }

    public void setYlenth(int ylenth) {
        Ylenth = ylenth;
    }

    //坐标轴线的宽度
    public void setAxisWidth(int axisWidth) {
        this.axisWidth = axisWidth;
    }

    //轴颜色
    public void setAxisColor(@ColorRes int axisColor) {
        this.axisColor = axisColor;
        color = context.getResources().getColor(axisColor);
    }

    boolean isRefresh = true;//  false   刷新数据   true   添加数据

    Activity activity;

    public MYChartLine(Context context, ViewGroup viewGroup) {
        super(context);
        this.context = context;
        if (context instanceof Activity) {
            activity = (Activity) context;
        }
        resources = context.getResources();
        //获取屏幕宽(自适应屏幕宽度)
        WindowManager wm = ((Activity) context).getWindowManager();
        viewGroupWidth = wm.getDefaultDisplay().getWidth();
        // 取父控件高度(为了在布局中可以设置高度,不获取屏幕)
        LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) viewGroup
                .getLayoutParams();
        viewGroupHeight = linearParams.height;

        //画布宽高
        canasWidth = viewGroupWidth - 10;
        canasHeight = viewGroupHeight;
        //原点坐标
        XPoint = canasWidth / 12.0F;
        YPoint = canasHeight - surplusHeight;

        color = context.getResources().getColor(axisColor);
        colorBlue = context.getResources().getColor(R.color.zhexian_blue);


        //初始化画笔
        //标记线(随手势移动)
        paintYTag = new Paint();
        paintYTag.setColor(colorBlue);
        paintYTag.setStrokeWidth(2);
        paintYTag.setAntiAlias(true);
        paintYTag.setStyle(Paint.Style.STROKE);//设置画直线格式
        paintYTag.setPathEffect(new DashPathEffect(new float[]{dottedLength, dottedspace}, 0));//画虚线关键代码
        //轴画笔
        paintYX = new Paint();
        paintYX.setColor(color);
        paintYX.setTextSize(30);
        paintYX.setStrokeWidth(axisWidth);
        paintYX.setAntiAlias(true);
        //垂直虚线
        paintDottenY = new Paint();
        paintDottenY.setColor(color);
        paintDottenY.setTextSize(30);
        paintDottenY.setStrokeWidth(axisWidth);
        paintDottenY.setAntiAlias(true);
        paintDottenY.setStyle(Paint.Style.STROKE);//设置画直线格式
        paintDottenY.setPathEffect(new DashPathEffect(new float[]{5, 3}, 1));//画虚线关键代码


        //X轴数据
        paintXText = new Paint();
        paintXText.setColor(color);
        paintXText.setTextSize(30);
        paintXText.setAntiAlias(true);

        YScale = (int) ((canasHeight - canasHeight / Ylenth) / Ylenth);//Y轴刻度间距
        effectiveLenthX = (int) (canasWidth - XPoint - surplusWhith);
    }

    /**
     * 桌布
     */
    private Canvas canvas;
    //Y轴
    Paint paintYX;
    //Y轴数据
    Paint paintYText;
    //X轴数据
    Paint paintXText;
    //垂直虚线
    Paint paintDottenY;

    //与Y轴平行的、可移动标记线
    Paint paintYTag;
    //标记线
    Path tagPath = new Path();

    //清除画笔

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        this.canvas = canvas;

        //标记线(随手势移动)

        canvas.drawPath(tagPath, paintYTag);
        //轴画笔
        //Y轴
        canvas.drawLine(XPoint, topDim, XPoint, YPoint, paintYX);
        //X轴
        canvas.drawLine(XPoint, YPoint, canasWidth, YPoint, paintYX);
        //X轴数据

        YScale = (int) ((canasHeight - canasHeight / Ylenth) / Ylenth);//Y轴刻度间距
        this.onMDraw();

    }

    //画笔集合  记录每一条折线 //VALUE==折线与折线标注字体
    ArrayList<Paint> painLines = new ArrayList<>();

    //是否设置Y轴数据为10、100、1000倍数
    private boolean isYDataToMultiple = true;
    private LinePain maxLenthlinePain;//折线图最长的折线

    /**
     * 控制Y轴数据为10、100、1000的倍数(只有Y轴当前数据大与10倍才会抹去尾数  如 12 不会变成10    112会变成110,1234会变成1200,12345会变成12000)
     */
    public void setYDataToMultiple(boolean isYDataToMultiple) {
        this.isYDataToMultiple = isYDataToMultiple;
    }

    int xuLineNum = 0; //虚线个数、、X轴分割区数
    LinePain[] dataList;//所有条线的数据
    double maxValue = 0;

    /**
     * 添加折线
     * isRefresh true 刷新数据 全部重新绘制
     * false 添加数据  增加画笔
     */
    public void addLine(LinePain... linePain) {

        System.out.println("开始---");
        //onDraw 方法会运行两次,这里控制一段代码 只运行一次
        if (isRefresh) {
            chartResultData.setDatasY(chartYList);
            dataList = new LinePain[linePain.length];
        }
        try {
            //虚线个数、、X轴分割区数
            xuLineNum = 0;
            //所有Y轴数据中最大一条数据值--用最大值计算Y轴刻度平均值
            maxValue = 0;
            painLines.clear();
            //初始化、创建数据
            createData(linePain);
            //四舍五入求最大位数的整数
            //返回数据
            chartResultData.setDataX(maxLenthlinePain.getLineBeens().get(maxLenthlinePain.getLineBeens().size() - 1).getX() + "");

            //单位像素 代表的数据大小(Y轴)
            //Y轴实际单位长度(对应数据)
            double heightY = Arith.div(maxY - minY, maxValue, 10).doubleValue();

            //X轴刻度间距
            if (xuLineNum > 1) {
                XScale =   ( canasWidth - XPoint - surplusWhith)/(xuLineNum-1);
            } else {
                XScale = 1;
            }
            System.out.println("刻画Y轴---");
            //画 Y轴数据
            drawYData(linePain);
            System.out.println("刻画Y轴结束---");
             //计算日期间隔(最多显示7个日期,计算相邻两个日期中间间隔的单位个数)
            int inDex = xuLineNum / 6;
            System.out.println("计算虚线,折线开始---");

            //折线位置
            ArrayList<float[]> lineschart = new ArrayList<>();

            //计算虚线有多少截
            int dottedNum = (int) ((YPoint - topDim) / (dottedspace + dottedLength));


            //(每一条虚线的每一截都要计算出来)虚线位置
            float[] dottedLine = new float[xuLineNum * dottedNum * 4];
            //虚线个数--代表单条折线数据长度
            for (int j = 0; j < xuLineNum; j++) {
                int X = (int) (XPoint + j * XScale);  //X轴刻度位置
                //折线上数值 以及X轴刻度
                if (xuLineNum > 6) {
                    if (j % inDex == 0) {
                        //X轴坐标
                        canvas.drawText(maxLenthlinePain.getLineBeens().get(j).getX() + "",
                                (int) (XPoint + (j * XScale) - surplusWhith),
                                YPoint + 30, paintXText);
                    }
                } else {
                    //X轴坐标
                    canvas.drawText(maxLenthlinePain.getLineBeens().get(j).getX() + "",
                            (int) (XPoint + (j * XScale) - maxLenthlinePain.getLineBeens().get(j).getX().length() * 8),
                            YPoint + 30, paintXText);
                }

                //每条虚线的每段虚线节点
                double dottedPosition = 0;
                if (j < xuLineNum - 1) {
                    //计算每条虚线的每一节
                    for (int i = 0; i < dottedNum; i++) {
                        //Y轴平行虚线
                        dottedLine[j * dottedNum * 4 + i * 4] = (float) (X + XScale);
                        dottedLine[j * dottedNum * 4 + i * 4 + 1] = (float) (topDim + dottedPosition);
                        dottedLine[j * dottedNum * 4 + i * 4 + 2] = (float) (X + XScale);
                        dottedLine[j * dottedNum * 4 + i * 4 + 3] = (float) (topDim + dottedPosition + dottedLength);
                        dottedPosition += (dottedLength + dottedspace);
                    }
                }
                for (int c = 0; c < linePain.length; c++) {
                    if (lineschart.size() < c + 1) {//添加一条折线的容器(有几条折线,会添加几次)
                        lineschart.add(new float[xuLineNum * 4]);
                    }

                    //   折线Y点位置
                    double startY = 0;
                    //当前折线长度是否到达  j  脚标(折线长度不一定相等 对比是否达到最长折线)
                    if (linePain[c].getLineBeens().size() > j) {
                        // 折线Y点位置
                        startY = YPoint - Arith.mul(heightY + "", linePain[c].getLineBeens().get(j).getY() + "");

                        //折线上数值 以及X轴刻度
                        if (xuLineNum > 6) {
                            if (j % inDex == 0) {
                                //查看本折线是否展示连接点值,true显示折线上的值
                                if (linePain[c].isShowPrompt()) {
                                    canvas.drawText(linePain[c].getLineBeens().get(j).getY() + "", X - 10, (float) (startY - 20), painLines.get(c));
                                }
                            }
                        } else {
                            if (linePain[c].isShowPrompt()) {
                                //显示线上的值
                                canvas.drawText(linePain[c].getLineBeens().get(j).getY() + "", X - 15, (float) (startY - 20), painLines.get(c));
                            }
                        }
                        //折线(与虚线)在倒数第二次已经完成绘制----折线数据计算
                        if (j < linePain[c].getLineBeens().size() - 1) {
                            //实际高度
                            double z1 = Arith.mul(heightY + "", linePain[c].getLineBeens().get(j + 1).getY() + "");

                            lineschart.get(c)[j * 4] = X;
                            lineschart.get(c)[j * 4 + 1] = (float) startY;
                            lineschart.get(c)[j * 4 + 2] = (float) (XPoint + (j + 1) * XScale);
                            lineschart.get(c)[j * 4 + 3] = YPoint - ((float) z1);
                        }
                    }
                }
            }
            System.out.println("渲染虚线,折线开始---");
            //画虚线
            canvas.drawLines(dottedLine, paintDottenY);
            //  折线
            for (int i = 0; i < painLines.size(); i++) {
                canvas.drawLines(lineschart.get(i), painLines.get(i));
            }
            System.out.println("渲染虚线,折线结束---" + System.currentTimeMillis());
            this.postInvalidate();
            isRefresh = false;
        } catch (Exception e) {
            Log.i(TAG, "---------" + e.toString());
            e.printStackTrace();
        }
    }

    private int lastX = 0;//记录标记线的位置
    private int lastY = 0;
    private int startYLineX = 0;//Y轴的X坐标
    private int effectiveLenthY = 0;//Y轴有效长度
    private int effectiveLenthX = 0;//X轴有效长度

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        //获取到手指处的横坐标和纵坐标
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                try {
                    getParent().requestDisallowInterceptTouchEvent(false);
                } catch (Exception e) {
                }
            case MotionEvent.ACTION_MOVE:
                System.out.println("effectiveLenthX=="+effectiveLenthX);
                if ((x > XPoint && x < effectiveLenthX + XPoint) && (y < YPoint && y > topDim)) {
                    lastX = x;
                    tagPath.reset();
                    tagPath.moveTo(lastX, topDim - 5);
                    tagPath.lineTo(lastX, YPoint);
//                    postInvalidate();
//
                    setToData(x);
                }
                break;
            case MotionEvent.ACTION_UP:
                try {
                    getParent().requestDisallowInterceptTouchEvent(true);
                } catch (Exception e) {
                }
                break;
        }

        return true;
    }

    //计算集合中最大值与最小值
    //最大值

    public double ArrayListMax(ArrayList<LineBeen> sampleList) {
        try {
            double maxDevation = 0.0;
            int totalCount = sampleList.size();
            if (totalCount >= 1) {
                double max = strToDouble(sampleList.get(0).getY());
                for (int i = 0; i < totalCount; i++) {
                    double temp = strToDouble(sampleList.get(i).getY());
                    if (temp > max) {
                        max = temp;
                    }
                }
                maxDevation = max;
            }
            return maxDevation;
        } catch (Exception ex) {
            throw ex;
        }
    }

    //调用本类的其他 需要实现本方法、在本方法去调用
    public abstract void onMDraw();

    private int minY = 0;
    private int maxY = 0;


    //监听手势 返回触摸到位置的值
    private ChartResultData chartResultData = new ChartResultData();
    //所有折线的Y值  添加到 chartResultData 中
    ArrayList<String> chartYList = new ArrayList();

    /**
     * \
     * <p>
     * 返回手势选中数据
     *
     * @return
     */
    public abstract ChartResultData setData(ChartResultData chartResultData);

    /**
     * 初始化数据
     * 创建、计算相关变量
     */
    private void createData(LinePain... linePain) throws IllegalAccessException {
        int x = 0;
        for (int i = 0; i < linePain.length; i++) {
            dataList[i] = linePain[i];
            Paint paint = new Paint();
            paint.setStrokeWidth(linePain[i].getLineWidth());
            paint.setAntiAlias(true);
            paint.setColor(resources.getColor(linePain[i].getColorId()));
            paint.setTextSize(linePain[i].getNumberPromptSize());

            if (linePain[i].getLineBeens() != null && linePain[i].getLineBeens().size() > 0) {
                //找出最多数据的折线
                if (xuLineNum < linePain[i].getLineBeens().size()) {
                    xuLineNum = linePain[i].getLineBeens().size();
                }
                //Y轴最大刻度值
                if (maxValue < ArrayListMax(linePain[i].getLineBeens())) {
                    maxValue = ArrayListMax(linePain[i].getLineBeens());
                }
                int size = linePain[i].getLineBeens().size();
                if (x < size) {//找出最长的一条折线(多条折线不一定一样长短)
                    x = size;
                    maxLenthlinePain = linePain[i];
                }
                if (maxLenthlinePain == null) {
                    maxLenthlinePain = linePain[0];
                }
            } else {
                return;
            }
            painLines.add(paint);
        }
        if (maxValue > 1000) {
            maxValue = Arith.div(maxValue, (double) 1000, 3).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue() * 1000;
        } else if (maxValue > 100) {
            maxValue = Arith.div(maxValue, (double) 100, 3).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue() * 100;
        } else if (maxValue > 10) {
            maxValue = Arith.div(maxValue, (double) 10, 3).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue() * 10;
        } else if (maxValue < 5) {
            maxValue = 5;
        }
    }

    public double strToDouble(String str) {
        return Double.parseDouble(str);
    }

    /**
     * 刻画Y轴数据
     */
    public void drawYData(LinePain... linePain) {
        //Y轴数据
        if (paintYText == null) {
            paintYText = new Paint();
        }
        paintYText.setColor(color);
        paintYText.setTextSize(30);
        paintYText.setAntiAlias(true);
        String strY;
        //Y轴一个刻度 等于的数据大小
        int v1 = (int) (new BigDecimal(maxValue / (Ylenth - 1)).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue());

        //Y轴刻度数据
        for (int i = 0; i < Ylenth; i++) {
            int i1 = (int) (maxValue - i * v1);
            if (i1 < 0) {
                i1 = 0;
            }
            /**
             *使数据为10、100、1000的倍数---纯属为了数据好看,
             * 可以通过 {@link com.jjf.customchartline.view.MYChartLine#setYDataToMultiple(boolean)} 设置
             */
            if (isYDataToMultiple) {
                if (i1 > 1000) {
                    strY = i1 / 100 * 100 + "";
                } else if (i1 > 100) {
                    strY = i1 / 10 * 10 + "";
                } else {
                    strY = i1 + "";
                }
            } else {
                strY = i1 + "";
            }
            // Y轴文字
            int heigY = i * YScale + (linePain[0].getNumberPromptSize() / 2) + topDim + surplusHeight;
            if (i < Ylenth - 1) {
                float v = XPoint - strY.length() * (linePain[0].getNumberPromptSize() / 2) - 10;
                if (v < 0) {
                    v = 0;
                }
                canvas.drawText(strY, v, heigY, paintYText);
            }
            if (i == 0) {
                minY = heigY;
            } else if (i == Ylenth - 1) {
                maxY = heigY;
            }
        }
    }

  
    //根据手势位置返回数据到页面
    public void setToData(int x) {
        try {
            double mx = XScale / 2;
            x = (int) (x + mx);
            int div = (int) Arith.div((x - XPoint), XScale == 0 ? 1 : XScale, 2).doubleValue();
            chartResultData.getDatasY().clear();
            for (int j = 0; j < dataList.length; j++) {
                if (dataList[j].getLineBeens().size() > div) {
                    chartResultData.setDataX(dataList[j].getLineBeens().get(div).getX());
                    chartResultData.getDatasY().add(dataList[j].getLineBeens().get(div).getY() + "");
                } else {
                    chartResultData.getDatasY().add("0");
                }
            }
            setData(chartResultData);
//            MYChartLine.this.postInvalidate();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

项目资源地址 自定义折线图Demo-Android文档类资源-CSDN下载

上传资源的时候 需要的C币那里没法设置。默认是5。今天也没法改,本想设置最少的那个1币。。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要自定义Qt折线图的刻度,可以使用QValueAxis类。QValueAxis类是Qt Charts模块中的一个类,用于表示数值轴。 以下是一个示例代码,演示如何自定义折线图的刻度: ```c++ // 创建一个QLineSeries对象 QLineSeries *series = new QLineSeries(); // 向series对象添加数据 series->append(0, 6); series->append(2, 4); series->append(3, 8); series->append(7, 4); series->append(10, 5); // 创建一个QChart对象,并将series对象添加到chart对象中 QChart *chart = new QChart(); chart->addSeries(series); // 创建一个QValueAxis对象,并设置相关属性 QValueAxis *axisX = new QValueAxis(); axisX->setLabelFormat("%.1f"); // 设置刻度标签格式 axisX->setRange(0, 10); // 设置轴的范围 axisX->setTickCount(5); // 设置刻度数量 axisX->setTitleText("X轴"); // 设置轴标题 // 将轴添加到chart对象中 chart->addAxis(axisX, Qt::AlignBottom); series->attachAxis(axisX); // 创建一个QValueAxis对象,并设置相关属性 QValueAxis *axisY = new QValueAxis(); axisY->setLabelFormat("%.1f"); // 设置刻度标签格式 axisY->setRange(0, 10); // 设置轴的范围 axisY->setTickCount(5); // 设置刻度数量 axisY->setTitleText("Y轴"); // 设置轴标题 // 将轴添加到chart对象中 chart->addAxis(axisY, Qt::AlignLeft); series->attachAxis(axisY); // 创建一个QChartView对象,并将chart对象设置为其属性 QChartView *chartView = new QChartView(chart); chartView->setRenderHint(QPainter::Antialiasing); // 将chartView添加到窗口中 ui->verticalLayout->addWidget(chartView); ``` 在上面的示例代码中,我们创建了一个QLineSeries对象,并向其添加了一些数据。然后,我们创建了一个QChart对象,并将QLineSeries对象添加到其中。接着,我们创建了两个QValueAxis对象,一个用于X轴,一个用于Y轴,并对它们进行了一些自定义设置。最后,我们将这两个轴添加到chart对象中,并将QLineSeries对象附加到它们上面,最终将chart对象添加到QChartView对象中,并将其添加到窗口中。 通过以上的示例代码,我们可以很容易地自定义Qt折线图的刻度。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值