LeafChart-实现自己的小型图表库(2)

一. 设计

  1. 上次写的七日化利率表设计很粗糙,也没有体现面向对象的思想。数据处理都在控件中,而且表格列数比较固定,对于数据和列数不匹配的情况,只能从左向右依次显示数据。另外扩展性很差。
  2. 根据图表的属性,分别抽象出坐标轴、坐标刻度、点、线等类。所以在使用的时候初始化这些数据就可以控制图表的展示。
  3. 抽象出折线统计图和条形统计图公共部分AbsLeafChart,这个类主要处理了控件的有关尺寸、初始化坐标轴和坐标轴刻度、计算每个点在控件的坐标。
  4. 折线统计图LeafLineChart继承AbsLeafChart,只负责画折线图。

二.类介绍

AxisValue 坐标轴刻度

类型属性介绍
Stringlabel刻度值
floatpointXx 坐标
floatpointYy 坐标

Axis 坐标轴

类型属性介绍
Listvalues刻度集合
booleanhasLines是否连线
booleanisShowText是否显示坐标轴刻度值
Typefacetypeface刻度字体
inttextSize刻度字体大小
inttextColor刻度字体颜色
intaxisColorx/y轴颜色
floataxisWidthx/y轴宽度
intaxisLineColor平行于x/y轴的坐标轴颜色
floataxisLineWidth平行于x/y轴的坐标轴宽度
floatstartX坐标轴起点x坐标
floatstartY坐标轴起点y坐标
floatstopX坐标轴终点x坐标
floatstopY坐标轴终点y坐标

PointValue 点

类型属性介绍
floatx占x轴总长度权重
floaty占x轴总长度权重
floatdiffX距离原点长度
floatdiffY距离原点长度
floatoriginXx坐标
floatoriginYy坐标
Stringlabel标签

ChartData 图表数据

类型属性介绍
Listvalues点集合
booleanhasLabels是否显示标签
intlabelColor标签背景色
floatlabelRadius标签背景弧度

Line extends ChartData 折线

类型属性介绍
intlineColor折线颜色
floatlineWidth折线的宽度
booleanhasPoints是否画圆点
booleanhasLines是否画线条
intpointColor圆点颜色
floatpointRadius圆点半径
booleanisCubic是否是曲线
booleanisFill是否填充
intfillColr填充色

三、实现

暂时只实现了折线图,这里只介绍折线图实现

AbsLeafChart中的相关方法:

尺寸控制

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        initViewSize();

        resetAsixSize();

        resetLineSize();
    }

控件宽高和控件内间距初始化

protected void initViewSize() {
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        leftPadding = LeafUtil.dp2px(mContext, 20);
        rightPadding = LeafUtil.dp2px(mContext, 10);
        topPadding = LeafUtil.dp2px(mContext, 15);
        bottomPadding = LeafUtil.dp2px(mContext, 20);
    }

设置坐标轴位置

protected void resetAsixSize() {
        if(axisX != null){
            List<AxisValue> values = axisX.getValues();
            int sizeX = values.size(); //几条y轴
            float xStep = (mWidth - leftPadding) / sizeX;
            axisX.setStepSize(xStep);
            for (int i = 0; i < sizeX; i++) {
                AxisValue axisValue = values.get(i);
                axisValue.setPointY(mHeight);
                if(i == 0){
                    axisValue.setPointX(leftPadding);
                } else {
                    axisValue.setPointX(leftPadding + xStep * i);
                }
            }

            axisX.setStartX(0).setStartY(mHeight - bottomPadding)
                    .setStopX(mWidth).setStopY(mHeight - bottomPadding);
        }

        if(axisY != null){
            List<AxisValue> values = axisY.getValues();
            int sizeY = values.size(); //几条x轴
            float yStep = (mHeight - topPadding - bottomPadding) / sizeY;
            axisY.setStepSize(yStep);
            for (int i = 0; i < sizeY; i++) {
                AxisValue axisValue = values.get(i);
                axisValue.setPointX(leftPadding);
                if(i == 0){
                    axisValue.setPointY(mHeight - bottomPadding );
                } else {
                    axisValue.setPointY(mHeight - bottomPadding - yStep * i);
                }
            }
            axisY.setStartX(leftPadding).setStartY(mHeight - bottomPadding)
                    .setStopX(leftPadding).setStopY(0);
        }
    }

设置点所占比重

protected void resetLineSize() {
        if(line != null && axisX != null && axisY != null){
            List<PointValue> values = line.getValues();
            int size = values.size();

            List<AxisValue> axisValuesX = axisX.getValues();
            List<AxisValue> axisValuesY = axisY .getValues();
            float totalWidth = Math.abs(axisValuesX.get(0).getPointX() - axisValuesX.get(axisValuesX.size() - 1).getPointX());

            float totalHeight = Math.abs(axisValuesY.get(0).getPointY() - axisValuesY.get(axisValuesY.size() - 1).getPointY());
//            Log.e("=====", "totalWidth:" + totalWidth + ", totalHeight:" + totalHeight);
            for (int i = 0; i < size; i++) {
                PointValue pointValue = values.get(i);
                float diffX = pointValue.getX() * totalWidth;
                pointValue.setDiffX(diffX);

                float diffY = pointValue.getY() * totalHeight;
                pointValue.setDiffY(diffY);

//                Log.e("====", "x:" + pointValue.getX() + ", y:" + pointValue.getY());
//                Log.e("=======", "diffX:" + pointValue.getDiffX() + ", diffY:" + pointValue.getDiffY());
            }
        }
    }

画坐标轴 刻度值

protected void drawCoordinateText(Canvas canvas) {
        if(axisX != null && axisY != null){
             X 轴
            // 1.刻度
            paint.setColor(axisX.getTextColor());
            paint.setTextSize(LeafUtil.sp2px(mContext, axisX.getTextSize()));

            Paint.FontMetrics fontMetrics = paint.getFontMetrics(); // 获取标题文字的高度(fontMetrics.descent - fontMetrics.ascent)
            float textH = fontMetrics.descent - fontMetrics.ascent;

            List<AxisValue> valuesX = axisX.getValues();
            if(axisX.isShowText()){
                for (int i = 0; i < valuesX.size(); i++) {
                    AxisValue value = valuesX.get(i);
                    float textW = paint.measureText(value.getLabel());
                    canvas.drawText(value.getLabel(), value.getPointX() - textW / 2,  value.getPointY() - textH / 2,paint);
                }
            }


            /// Y 轴
            paint.setColor(axisY.getTextColor());
            paint.setTextSize(LeafUtil.sp2px(mContext, axisY.getTextSize()));

            List<AxisValue> valuesY = axisY.getValues();
            if(axisY.isShowText()){
                for (AxisValue value : valuesY){
                    float textW = paint.measureText(value.getLabel());
                    float pointx = value.getPointX() - 1.1f * textW;
                    canvas.drawText(value.getLabel(), pointx , value.getPointY(),paint);
                }
            }
        }
    }

确定每个点所在位置:根据之前计算的diff计算得到每个点的x、y坐标

protected void setPointsLoc(){
        if(line != null){
            linePaint.setStrokeWidth(LeafUtil.dp2px(mContext, line.getLineWidth()));
            List<PointValue> values = line.getValues();
            int size = values.size();
            for (int i = 0; i < size; i++) {
                PointValue point1 = values.get(i);
                float originX1 = point1.getDiffX() + leftPadding;
                float originY1 = mHeight - bottomPadding - point1.getDiffY();
                point1.setOriginX(originX1).setOriginY(originY1);
            }
        }
    }

画圆点

    protected void drawPoints(Canvas canvas) {
        if (line != null) {
            if(line.isHasPoints()){
                labelPaint.setColor(line.getPointColor());
                List<PointValue> values = line.getValues();
                for (PointValue point: values) {
                    float radius = LeafUtil.dp2px(mContext, line.getPointRadius());
                    canvas.drawCircle(point.getOriginX(), point.getOriginY(),
                            radius , labelPaint);
                }
            }
        }
    }

画每一个点上的标签 : 需要注意的是画标签的时候需要得到标签字符串的高和宽,另外需要控制的是标签边界不要超出控件边界

protected void drawLabels(Canvas canvas) {
        if (line != null) {
            if(line.isHasLabels()){
                labelPaint.setTextSize(LeafUtil.sp2px(mContext, 12));

                Paint.FontMetrics fontMetrics = labelPaint.getFontMetrics();
                List<PointValue> values = line.getValues();
                int size = values.size();
                for (int i = 0; i < size; i++) {
                    PointValue point = values.get(i);
                    String label = point.getLabel();
                    Rect bounds = new Rect();
                    int length = label.length();
                    labelPaint.getTextBounds(label, 0, length, bounds);

                    float textW = bounds.width();
                    float textH = bounds.height();
                    float left, top, right, bottom;
                    if(length == 1){
                        left = point.getOriginX() - textW * 2.2f;
                        right = point.getOriginX() + textW * 2.2f;
                    }  else if(length == 2){
                        left = point.getOriginX() - textW * 1.0f;
                        right = point.getOriginX() + textW * 1.0f;
                    } else {
                        left = point.getOriginX() - textW * 0.6f;
                        right = point.getOriginX() + textW * 0.6f;
                    }
                    top = point.getOriginY() - 2.5f*textH;
                    bottom = point.getOriginY() - 0.5f*textH;

//                    if(i > 0){
//                        PointValue prePoint = values.get(i - 1);
//                        RectF rectF = prePoint.getRectF();
//                        if(left <= rectF.right){
//                            // 左边与上一个标签重叠
//                            top = point.getOriginY() + 1.7f*textH;
//                            bottom = point.getOriginY() + 0.5f*textH;
//                        }
//                    }

                    //控制位置
                    if(left < 0){
                        left = leftPadding;
                        right += leftPadding;
                    }
                    if(top < 0){
                        top = topPadding;
                        bottom += topPadding;
                    }
                    if(right > mWidth){
                        right -= rightPadding;
                        left -= rightPadding;
                    }

                    RectF rectF = new RectF(left, top, right, bottom);
                    float labelRadius = LeafUtil.dp2px(mContext,line.getLabelRadius());
                    labelPaint.setColor(line.getLabelColor());
                    canvas.drawRoundRect(rectF, labelRadius, labelRadius, labelPaint);

                    //drawText
                    labelPaint.setColor(Color.WHITE);
                    float xCoordinate = left + (right - left - textW) / 2;
                    float yCoordinate = bottom - (bottom - top - textH) / 2 ;
                    canvas.drawText(point.getLabel(), xCoordinate, yCoordinate, labelPaint);
                }
            }
        }
    }

LeafLineChart中的方法:

画折线:记得之前话折线的方法是点与点之间连线。这里的做法是吧每个点用Path记录,最后通过drawPath来画折线

 protected void drawLines(Canvas canvas) {
        if(line != null){
            linePaint.setColor(line.getLineColor());
            linePaint.setStrokeWidth(LeafUtil.dp2px(mContext, line.getLineWidth()));
            linePaint.setStyle(Paint.Style.STROKE);
            List<PointValue> values = line.getValues();
            int size = values.size();
            for (int i = 0; i < size; i++) {
                PointValue point = values.get(i);
                if(i == 0)  path.moveTo(point.getOriginX(), point.getOriginY());
                else  path.lineTo(point.getOriginX(), point.getOriginY());
            }

            canvas.drawPath(path, linePaint);
        }
    }

画曲线

private void drawCubicPath(Canvas canvas) {
        if(line != null){
            linePaint.setColor(line.getLineColor());
            linePaint.setStrokeWidth(LeafUtil.dp2px(mContext, line.getLineWidth()));
            linePaint.setStyle(Paint.Style.STROKE);

            float prePreviousPointX = Float.NaN;
            float prePreviousPointY = Float.NaN;
            float previousPointX = Float.NaN;
            float previousPointY = Float.NaN;
            float currentPointX = Float.NaN;
            float currentPointY = Float.NaN;
            float nextPointX = Float.NaN;
            float nextPointY = Float.NaN;

            List<PointValue> values = line.getValues();
            final int lineSize = values.size();
            for (int valueIndex = 0; valueIndex < lineSize; ++valueIndex) {
                if (Float.isNaN(currentPointX)) {
                    PointValue linePoint = (PointValue) values.get(valueIndex);
                    currentPointX = linePoint.getOriginX();
                    currentPointY = linePoint.getOriginY();
                }
                if (Float.isNaN(previousPointX)) {
                    if (valueIndex > 0) {
                        PointValue linePoint = values.get(valueIndex - 1);
                        previousPointX = linePoint.getOriginX();
                        previousPointY = linePoint.getOriginY();
                    } else {
                        previousPointX = currentPointX;
                        previousPointY = currentPointY;
                    }
                }

                if (Float.isNaN(prePreviousPointX)) {
                    if (valueIndex > 1) {
                        PointValue linePoint = values.get(valueIndex - 2);
                        prePreviousPointX = linePoint.getOriginX();
                        prePreviousPointY = linePoint.getOriginY();
                    } else {
                        prePreviousPointX = previousPointX;
                        prePreviousPointY = previousPointY;
                    }
                }

                // nextPoint is always new one or it is equal currentPoint.
                if (valueIndex < lineSize - 1) {
                    PointValue linePoint = values.get(valueIndex + 1);
                    nextPointX = linePoint.getOriginX();
                    nextPointY = linePoint.getOriginY();
                } else {
                    nextPointX = currentPointX;
                    nextPointY = currentPointY;
                }

                if (valueIndex == 0) {
                    // Move to start point.
                    path.moveTo(currentPointX, currentPointY);
                } else {
                    // Calculate control points.
                    final float firstDiffX = (currentPointX - prePreviousPointX);
                    final float firstDiffY = (currentPointY - prePreviousPointY);
                    final float secondDiffX = (nextPointX - previousPointX);
                    final float secondDiffY = (nextPointY - previousPointY);
                    final float firstControlPointX = previousPointX + (LINE_SMOOTHNESS * firstDiffX);
                    final float firstControlPointY = previousPointY + (LINE_SMOOTHNESS * firstDiffY);
                    final float secondControlPointX = currentPointX - (LINE_SMOOTHNESS * secondDiffX);
                    final float secondControlPointY = currentPointY - (LINE_SMOOTHNESS * secondDiffY);
                    path.cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY,
                            currentPointX, currentPointY);
                }

                // Shift values by one back to prevent recalculation of values that have
                // been already calculated.
                prePreviousPointX = previousPointX;
                prePreviousPointY = previousPointY;
                previousPointX = currentPointX;
                previousPointY = currentPointY;
                currentPointX = nextPointX;
                currentPointY = nextPointY;
            }
            canvas.drawPath(path, linePaint);
        }
    }

用的请看源码。
GitHub:LeafChart

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值