微信小程序接入ec-echarts(echarts)以及部分方案

209 阅读5分钟

前言

除了web端需要处理大量的表格之外(使用 e-charts),有时候小程序也会需要用到表格,当然交互上会比web端要差一些,这没办法,平台和设备限制就这样,下面简单介绍 ec-echart 在小程序使用,以及部分方案

echarts-for-weixin(github)echarts表格案例

接入 ec-echart

首先打开上面地址,下载下来需要的库,将 ec-canvas 内容复制到自己应用(有些调用微信api要过期了,可以自己更新)

然后在.json中引入该组件

"usingComponents": {
    "ec-canvas": "../ec-canvas/ec-canvas"
},

在 .wxml 中引入 ec-canvas(可以写入到 component 中,也可以直接写入到 page 中)

//默认使用新版本 canvas,建议使用
<ec-canvas class="pie" id="mychart-dom-bar" ec="{{ ec }}" />

//forceUseOldCanvas 使用微信老版本的 canvas,碰到问题可以切换,可能会存在不太清晰的问题
<ec-canvas forceUseOldCanvas class="pie" id="mychart-dom-bar" ec="{{ ec }}" />

.js 中初始化以及渲染我们的组件

data: {
    //可以设置基础属性,可以设置是否能点击,可以不用设置
    //移动端体验没那么好,小程序可以直接设置不点击
    ec: {
      lazyLoad: true, //懒加载
      disableTouch: true // 是否能点击
    }
}

通过 selectComponent 获取 ec-canvas 组件,然后调用其初始化方法,初始渲染我们的内容
this.ecComponent = this.selectComponent('#mychart-dom-bar');
this.ecComponent.init((canvas, width, height, dpr) => {
    //初始化 chart
    const chart = echarts.init(canvas, null, {
      width: width,
      height: height,
      devicePixelRatio: dpr // new
    });
    //打开
    const option = {
      color: this.data.colors,
      series: [{
        type: 'pie',
        radius: ['50%', '66%'],
        avoidLabelOverlap: false,
        itemStyle: {
          borderColor: '#fff',
          borderWidth: 8
        },
        label: {
          show: false,
          position: 'center'
        },
        data
      }]
    };
    chart.setOption(option);
    return chart;
});

就这样就可以完成表格渲染了

问题与解决方案

问题

对于显示问题,小程序本身 canvas 就会存在一些问题,无论是新版还是旧版本都会存在这样那样的问题,例如:滚动视图上无法跟随滚动,跟随时抖动问题,模糊等问题,这个只能找偏方处理了,正常手段是没办法的,对于偏方个人有时候不怎么赞同,毕竟不少偏方在不同设备的显示一直是一个迷😂,好的偏方自然可以用

解决方案(饼图)

能在小程序展示的,一般效果不太复杂,一般也不带那么多交互,能够自己使用css做出来的效果,那当然是最好了

例如:自己做了一个环形饼图,效果如下所示,想要其他饼图也能做到哈,就算是柱状图也有不少手段(就是交互差点,展示没问题)

QQ_1731466552518.png

下面是模版和css代码,其中最重要的是利用 conic-gradient 编写的逻辑代码

wxml
<view class="chart_bkg">
    <view class="pie_bkg">
        <view class="pie" style="{{utils.getBackground(colors, data, true)}}" />
        <view class="circle_center_bkg" />
        <image class="img_center" src="./assets/chart_center.png" />
    </view>

    <view class="right_bkg">
        <view wx:for="{{data}}" wx:key="index" class="item_view">
            <view class="item_icon" style="background-color: {{colors[index]}};" />
            <text class="item_text">{{item.name}}</text>
            <text class="item_content">{{item.value}}</text>
        </view>
    </view>
</view>	

//css
.chart_bkg {
    display: flex;
    width: 100%;
    flex: 1;
    background: linear-gradient(to bottom, #FBFCFD, #F2FAFF);
}

.pie_bkg {
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 420rpx;
    height: 420rpx;
    background: linear-gradient(to bottom, #FBFCFD, #F2FAFF);
}

.pie {
    position: absolute;
    left: 16%;
    top: 16%;
    width: 68%;
    height: 68%;
    border-radius: 50%;
}

.circle_center_bkg {
    position: absolute;
    width: 240rpx;
    height: 240rpx;
    border-radius: 50%;
    background-color: white;
}

.img_center {
    position: absolute;
    width: 194rpx;
    height: 194rpx;
    border-radius: 50%;
}

.right_bkg {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-right: 32rpx;
    padding: 30rpx 0;
}

.item_view {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 50rpx;
}

.item_icon {
    width: 12rpx;
    height: 12rpx;
    border-radius: 50%;
    margin-right: 10rpx;
}

.item_text {
    font-size: 24rpx;
    color: #666666;
    line-height: 1em;
    flex: 1;
}

.item_content {
    color: #3F9EFF;
    font-weight: bold;
    font-size: 26rpx;
    line-height: 1em;
    text-align: right;
    flex-shrink: 0;
    margin-left: 12rpx;
}

下面是 conic-gradient 核心代码,如下所示

//自己的colors, data数据表示数据,valueKey 表示数据对象 value 的 key,split 是否是带分隔袋的饼图
//split 计算中总会有少量误差不到100%,但实际误差比例可能比彩票概率斗低,只是显示也看不出来,不存在问题
function getBackground(colors, data, valueKey, split) {
    if (!colors || colors.length < 1 || !data || data.length < 1) return ''
    var totolValue = 0
    var values = []
    for (var idx = 0; idx < data.length; idx++) {
        var value = parseFloat(data[idx][valueKey])
        value = isNaN(value) ? 0 : value
        if (value) {
            totolValue += value
            values.push(value)
        }
    }
    var splitPerc = 1
    if (split) {
        var effectPerc = 1 - splitPerc * values.length / 100
        values = values.map(function(value) {
                return value * effectPerc
        })
    }
    var percs = values.map(function(value) {
        return value / totolValue * 100
    })

    var background = 'background: conic-gradient('
    var temperc = 0
    percs.forEach(function(perc, idx) {
        var last = ''
        if (idx > 0) {
            last = ', '
        }
        var itemColor = colors[idx]
        //颜色不够时支持,自己可以写出更好的备用颜色
        itemColor = itemColor ? itemColor : ('#' + parseInt(Math.random() * 1000000))
        last += itemColor + ' ' + temperc + '%'
        temperc += perc
        var next =  ', ' + itemColor + ' ' + temperc + '%'
        background += last + next
        if (split) {
            background += ', white' + ' ' + temperc + '%'
            temperc += splitPerc
            background += ', ' + 'white' + ' ' + temperc + '%'
        }
    })
    background += ')')
    return background
}

解决方案(柱状图)

柱状图也类似,只不过我们可以直接以列表的形式展开就行了,就像普通布局一样,这边随便做一做,如下所示

QQ_1732866917677.png

部分实现代码如下所示

//wxm
<!--component/bar-chart/index.wxml-->
<wxs src="./index.wxs" module="utils" />

<view class="bar_container">
    <view class="top_bkg">
        <view wx:for="{{data}}" wx:key="index" class="top_item">
            <view class="top_left" />
            <text class="top_text">{{item.title}}</text>
        </view>
    </view>

    <view class="bar_bkg">
        <view wx:for="{{utils.getPercData(data)}}" wx:key="gIndex" class="bar_item_view" wx:for-index="gIndex">
            <view class="bar_text">{{item.name}}</view>
            <view class="bars">
                <block 
                    wx:for="{{item.values}}" 
                    wx:key="index" 
                    wx:for-item="value">
                    <view 
                        class="bar" style="background:{{colors[index] ? colors[index] : 'red'}};width:{{item.percs[index] + '%'}};;{{index > 0 ? 'margin-top: 12rpx' : ''">{{value}}</view>
                </block>
            </view>
        </view>
    </view>
</view>

//css
.bar_container {
    display: flex;
    flex-direction: column;
    width: 690rpx;
    background: linear-gradient(to bottom, #FBFCFD, #F2FAFF);
    border-radius: 12rpx;
    padding: 20rpx 0;
}

.top_bkg {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    flex-wrap: wrap;
}

.top_item {
    display: flex;
    align-items: center;
    margin-right: 28rpx;
    margin-top: 12rpx;
}

.top_left {
    width: 22rpx;
    height: 14rpx;
    background: linear-gradient(to bottom, #2890EC 0%, #17C6FF 100%);
    border-radius: 4rpx;
}

.top_text {
    font-size: 24rpx;
    color: #495E6B;
    margin-left: 12rpx;
}

.bar_bkg {
    display: flex;
    flex-direction: column;
    margin-left: 120rpx;
    padding: 40rpx 30rpx 30rpx 0;
}

.bar_item_view {
    display: flex;
    align-items: center;
}

.bar_text {
    font-size: 24rpx;
    color: #666666;
    text-align: right;
    width: 104rpx;
    min-height: 24rpx;
    word-wrap: break-word;
    margin: 0 8rpx;
    margin-left: -120rpx;
}

.bars {
    display: flex;
    flex-direction: column;
    border-left: 1rpx solid #D0D0D0;
    padding: 20rpx 0;
    width: 100%;
}

.bar {
    height: 32rpx;
    background: green;
    border-radius: 4rpx;
    display: flex;
    justify-content: center;
    align-items: center;
    color: white;
    font-size: 22rpx;
}

//wxs 简单处理一下数据
function getPercData(data) {
    if (!data || data.length < 1) return []
    if (data.length === 1) {
        data = [data]
    }
    var dataList = []
    var max = 0
    data.forEach(function(group) {
        group.values.forEach(function(item) {
            var value = parseFloat(item.value)
            value = isNaN(value) ? 0 : value
            if (value > max) {
                max = value
            }
            item.value = value
        })
    })
    data.forEach(function(group) {
        group.values.forEach(function(item, index) {
            var dataItem = dataList[index]
            if (!dataItem) {
                dataItem = {
                    name: item.name,
                    values: [],
                    percs: []
                }
                dataList[index] = dataItem
            }
            dataItem.values.push(item.value)
            dataItem.percs.push(max ? (item.value / max * 100).toFixed(2) : '0')
        })
    })
    return dataList
}

最后

俗话说,铁杵磨成针,最怕有心人,基本功牢固,很多问题就能迎刃而解,一起加油吧😂