自定义一个简单的3D柱状图

说是3D,其实暂时还跟3D绘图相关的API扯不上边,用的都是2D的API实现的。见图:
在这里插入图片描述

详细代码

public class BarChat3DView extends View {
    private Context mContext;
    private PaintFlagsDrawFilter mDrawFilter;
    //画板宽度
    private int mCanvasWidth;
    //画板高度 todo 提供set方法
    private int mCanvasHeight;
    //柱子宽度 todo 提供set方法
    private int mBarWidth;
    //柱子最高高度
    private int mBarMaxHeight;
    //x轴高度
    private int mXAxisHeight;
    //x轴颜色
    private int mXAxisColor;

    private List<BarChat3DBean> mBarChat3DBeanList = new ArrayList<>();

    public BarChat3DView(Context context) {
        super(context);
        init(context, null);
    }

    public BarChat3DView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, @Nullable AttributeSet attrs) {
        mContext = context;
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        mBarWidth = DisplayUtil.dip2px(context, 38);
        mXAxisHeight = DisplayUtil.dip2px(context, 1);
        mXAxisColor = Color.parseColor("#4723BE");
        if (attrs != null) {
            //todo xml中的个性化属性
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //最低高度200
        if (height < 200) {
            height = 200;
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCanvasWidth = w;
        mCanvasHeight = h;
        //柱子最高高度为画板高度-50dp
        mBarMaxHeight = DisplayUtil.px2dip(mContext, mCanvasHeight - 50);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //        super.onDraw(canvas);
        canvas.setDrawFilter(mDrawFilter);
        //        canvas.drawColor(Color.parseColor("#ffffff"));
        //画x轴线
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(mXAxisColor);
        paint.setStrokeWidth(mXAxisHeight);

        //设计图x轴线距离底部24dp,24dp=柱子底部标题文字高度+柱子底部标题距x轴线的距离+x轴线高度
        Rect rect = new Rect(0, mCanvasHeight - DisplayUtil.dip2px(mContext, 24),
                mCanvasWidth, mCanvasHeight - DisplayUtil.dip2px(mContext, 23));
        canvas.drawRect(rect, paint);

        //循环画柱子、柱子底部标题、柱子顶部数值
        if (mBarChat3DBeanList.size() > 0) {
            //柱子最高高度为画板高度-50dp.取出真实数值中的最大值,根据两者计算y轴比例尺,从而计算每个柱子高度
            float yAxisPlottingScale = 1;
            float maxValue = 0;
            for (int i = 0; i < mBarChat3DBeanList.size(); i++) {
                BarChat3DBean barChat3DBean = mBarChat3DBeanList.get(i);
                float value = barChat3DBean.getValue();
                if (value > maxValue) {
                    maxValue = value;
                }
            }
            //为了避免只有一个柱状图数据,且数据小于mBarMaxHeight时,该柱子高度会画到最大高度的错误情况,
            //设maxValue=mBarMaxHeight,避免y轴比例尺大于1
            if ((maxValue < mBarMaxHeight) && mBarChat3DBeanList.size() == 1) {
                maxValue = mBarMaxHeight;
            }
            //y轴比例尺,因为是用最大值来计算比例尺,减50是给该最长的柱子留出空间,画柱子顶部的数值
            yAxisPlottingScale = (mBarMaxHeight - 50) / maxValue;

            for (int i = 0; i < mBarChat3DBeanList.size(); i++) {

                if (i == 0) {
                    canvas.translate(DisplayUtil.dip2px(mContext, 32),
                            -DisplayUtil.dip2px(mContext, 24));
                } else {
                    canvas.translate(DisplayUtil.dip2px(mContext, 27) + mBarWidth,
                            -DisplayUtil.dip2px(mContext, 24));
                }

                BarChat3DBean barChat3DBean = mBarChat3DBeanList.get(i);

                float value = barChat3DBean.getValue();
                //上移到x轴线上面
                //                value = value + 24;

                //有值才画柱子
                //                if (barChat3DBean.getValue() > 0) {

                //柱子start
                int barRealHeight = mCanvasHeight - DisplayUtil.dip2px(mContext,
                        value * yAxisPlottingScale);

                Rect barRect = new Rect(0,
                        barRealHeight,
                        mBarWidth, mCanvasHeight /*- DisplayUtil.dip2px(mContext, 24)*/);
                paint.setStyle(Paint.Style.FILL);
                LinearGradient barGradient = new LinearGradient(0,
                        (barRealHeight) / 2,
                        mBarWidth, (barRealHeight) / 2
                        , barChat3DBean.getShallowColor(), barChat3DBean.getDarkColor(),
                        Shader.TileMode.CLAMP);
                paint.setShader(barGradient);
                paint.setColor(barChat3DBean.getShallowColor());
                canvas.drawRect(barRect, paint);
                //柱子end

                //柱子顶部形成立体效果的椭圆start
                LinearGradient ovalGradient = new LinearGradient(0,
                        barRealHeight,
                        mBarWidth, mCanvasHeight /*- DisplayUtil.dip2px(mContext, 24)*/,
                        barChat3DBean.getDarkColor(), barChat3DBean.getShallowColor(),
                        Shader.TileMode.CLAMP);
                paint.setShader(ovalGradient);
                canvas.drawOval(0, mCanvasHeight - DisplayUtil.dip2px(mContext,
                        value * yAxisPlottingScale + 6), mBarWidth,
                        mCanvasHeight - DisplayUtil.dip2px(mContext,
                                value * yAxisPlottingScale - 6), paint);
                //柱子顶部形成立体效果的椭圆end

                //                }

                //柱子顶部数值start
                paint.setColor(Color.parseColor("#ffffff"));
                paint.setShader(null);
                paint.setTextSize(DisplayUtil.dip2px(mContext, 12));
                float beanValue = barChat3DBean.getValue();
                String valueOf = String.valueOf(Math.round(beanValue));
                int barValueWidth = getTextWidth(paint, valueOf);
                canvas.drawText(valueOf, (mBarWidth - barValueWidth) / 2,
                        mCanvasHeight - DisplayUtil.dip2px(mContext,
                                value * yAxisPlottingScale + 14), paint);
                //柱子顶部数值end

                canvas.translate(0, DisplayUtil.dip2px(mContext, 24));
                //柱子底部标题start
                int barTitleWidth = getTextWidth(paint, barChat3DBean.getBarTitle());
                canvas.drawText(barChat3DBean.getBarTitle(), (mBarWidth - barTitleWidth) / 2,
                        mCanvasHeight-DisplayUtil.dip2px(mContext,1), paint);
                //柱子底部标题end
            }
        }

    }

    /**
     * 获取字符串长度
     *
     * @param mPaint
     * @param str
     * @return
     */
    private int getTextWidth(Paint mPaint, String str) {
        float iSum = 0;
        if (str != null && !str.equals("")) {
            int len = str.length();
            float widths[] = new float[len];
            mPaint.getTextWidths(str, widths);
            for (int i = 0; i < len; i++) {
                iSum += Math.ceil(widths[i]);
            }
        }
        return (int) iSum;
    }


    public List<BarChat3DBean> getBarChat3DBeanList() {
        return mBarChat3DBeanList;
    }

    public void setBarChat3DBeanList(List<BarChat3DBean> barChat3DBeanList) {
        if (barChat3DBeanList != null && barChat3DBeanList.size() > 0) {
            mBarChat3DBeanList.clear();
            mBarChat3DBeanList.addAll(barChat3DBeanList);
            invalidate();
        }
    }
}

实体BarChat3DBean

public class BarChat3DBean {
    private String barTitle;
    private float value;//数值
    private int shallowColor;//浅色
    private int darkColor;//深色

    public String getBarTitle() {
        return barTitle;
    }

    public void setBarTitle(String barTitle) {
        this.barTitle = barTitle;
    }

    public float getValue() {
        return value;
    }

    public void setValue(float value) {
        this.value = value;
    }

    public int getShallowColor() {
        return shallowColor;
    }

    public void setShallowColor(int shallowColor) {
        this.shallowColor = shallowColor;
    }

    public int getDarkColor() {
        return darkColor;
    }

    public void setDarkColor(int darkColor) {
        this.darkColor = darkColor;
    }
}

Tip:

1、需要自定义View时,如果遇到感觉有些伤脑壳的图而又暂时找不到思路时,可以尝试下化整为零的战略,即把整图拆分成几个独立的部分,一部分一部分的画。这里的3D效果就是由矩形+椭圆组合实现的。

2、其实自定义View就像画家画画一个道理,要记得善于利用色差,这里的3D效果就是利用渐变色来实现的,矩形从左到右的颜色由潜到深,而椭圆从左到右的颜色由深到潜,两者组合起来就显示出了3D效果。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要自定义echarts 3D柱的tooltip,可以使用tooltip组件的formatter属性。该属性允许您使用回调函数来定义自己的tooltip内容。 以下是一个基本的示例: ``` option = { tooltip: { formatter: function (params) { //params.value是柱的值 return '自定义tooltip:' + params.value; } }, xAxis3D: { type: 'category', data: ['A', 'B', 'C', 'D', 'E'] }, yAxis3D: { type: 'value' }, zAxis3D: { type: 'category', data: ['1', '2', '3', '4', '5'] }, grid3D: { boxWidth: 200, boxDepth: 80, viewControl: { // projection: 'orthographic' }, light: { main: { shadow: true, intensity: 1.2 }, ambient: { intensity: 0.3 } } }, series: [{ type: 'bar3D', data: [ ['A', 0, '1', 100], ['B', 1, '1', 200], ['C', 2, '1', 300], ['D', 3, '1', 400], ['E', 4, '1', 500], ['A', 0, '2', 600], ['B', 1, '2', 700], ['C', 2, '2', 800], ['D', 3, '2', 900], ['E', 4, '2', 1000], ['A', 0, '3', 1100], ['B', 1, '3', 1200], ['C', 2, '3', 1300], ['D', 3, '3', 1400], ['E', 4, '3', 1500], ['A', 0, '4', 1600], ['B', 1, '4', 1700], ['C', 2, '4', 1800], ['D', 3, '4', 1900], ['E', 4, '4', 2000], ['A', 0, '5', 2100], ['B', 1, '5', 2200], ['C', 2, '5', 2300], ['D', 3, '5', 2400], ['E', 4, '5', 2500] ], shading: 'lambert', label: { textStyle: { fontSize: 16, borderWidth: 1 } }, itemStyle: { opacity: 0.7 } }] }; ``` 在此示例中,tooltip的formatter属性使用了一个回调函数来定义tooltip的内容。在回调函数中,params参数包含有关当前柱的信息,包括值,系列名称,系列索引和数据索引等。 您可以使用params.value属性获取当前柱的值,并将其添加到自定义的tooltip文本中。 在以上示例中,自定义的tooltip文本为“自定义tooltip:” + params.value。这将显示一个类似于“自定义tooltip:100”的tooltip,其中100是柱的值。 您可以根据需要修改回调函数来自定义tooltip的任何其他方面,例如颜色,样式或格式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值