echarts在小程序中的调用及PC中没有遇到过的一些坑。。

1,635 阅读8分钟

昨天接到一个需求是要把之前在PC中做的echarts图及功能在小程序中也实现;想着可能只是复制粘贴的事,因为是第一次在小程序中引入使用图表,结果被里面的各种坑磕磕绊绊了一整天,所以决定写下我的第一篇文章;

图片的标注

以下就是本菜ji半懵半懂实现该功能的过程;本篇是给像我这样的萌新朋友准备,或者是打算做类似需求的伙伴们借鉴的,如果有更好的方法或者不足之处,欢迎看过的各位前辈大佬指教。


1.进入官网下载最新的echarts包 echarts.apache.org/zh/builder.…

在这里我的需求是暂时只要饼状图,所以定制下载了饼图

2.在小程序端的话 官方也是有专门的小程序版的包,可以直接上github上下载实例

手机扫描可以体验效果:

图片的标注
下载后里面有很多图表示例,其中的 ec-canvas 文件夹是我们需要的,里面包含了提供的各种组件

3.引入ec-canvas组件

首先我们得把ec-canvas文件夹放进小程序项目中,然后到需要使用到图表的文件中对应index.json中挂载:

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

这一配置的作用是,允许我们在 pages/bar/index.wxml 中使用 <ec-canvas> 组件。注意路径的相对位置要写对,如果目录结构和本例相同,就应该像上面这样配置。

index.wxml 中,我们创建了一个<ec-canvas>组件,内容如下:

<ec-canvas id="activeChart" ec="{{ ec_active }}"></ec-canvas>

注意:2d的ec-canvas在真机调试的时候可能会无法显示,但是上线后应该是没有问题的,如果想真机调试的话,可以在标签中加上以下代码,但这样会导致图表失真,开发完以后别忘了删除呀

force-use-old-canvas="true"

其中 ec_active 是一个我们在 index.js 中定义的对象,本次我一共需要使用到三个ec对象,它使得图表能够在页面加载后被初始化并设置。index.js 的结构如下:

import * as echarts from '../ec-canvas/echarts';
var app = getApp();
Page({
     data: {
    ec_active: {
      // onInit: initChart
    },
    ActivetableData: [], // 活跃会员
    activeVip: [],
  },
})

申明变量后,首先请求接口获取图表信息

 getActiveUserBread() {
    var that = this;
    app.ajax({
      type: 'post',
      url: 'MemberAnalyzeActive',
      data: {
      },
      success: res => {
        if (res.status == 1) {
          console.log(res);
          that.data.activeVip = res.data
          that.setData({
            activeVip: that.data.activeVip
          })
          this.initChart() //初始化图表
        }
      }
    })
  },

然后这个接口的我是在onLoad里调用的

  onLoad: function (options) {
      this.activeComponnet = this.selectComponent('#activeChart');
      this.getActiveUserBread()
},
  //这个selectComponent里的值必须是在wxml里对应ec-canvas的id

获取到信息后我存入了activeVip里 供后续初始化后使用

initChart: function () {
  this.activeComponnet.init((canvas, width, height) => {
    // 初始化图表
    const Chart = echarts.init(canvas, null, {
      width: width,
      height: height
    });
    Chart.setOption(this.getOption());  //注意这里调用了getOption来返回对应的图表
    return Chart;
  });
},

这个initChart事件会执行到setOption的时候执行里面的this.getOption())来获取返回的option值

getOption: function () {
  var option = {
    backgroundColor: "#ffffff",
    color: ["#37A2DA", "#32C5E9", "#67E0E3", "#91F2DE", "#FFDB5C", "#FF9F7F"],
    series: [{
      label: {
        normal: {
          fontSize: 14
        }
      },
      type: 'pie',
      center: ['50%', '50%'],
      radius: '60%',
      label: {
        normal: {
          formatter: '{b}\n({d}%)\n{c}人',
          textStyle: {
            fontWeight: 'normal',
            fontSize: 12
          }
        }
      },
      data: this.data.activeVip,
    }]
  };
  return option
},

一系列操作图形的显示优化后,我把option返回到调用的函数中 其实这样已经可以显示图表了,但是我这边需要点击具体的一块,获取信息并渲染到表中,然后经过一顿盘查

  initChart: function () {
      this.activeComponnet.init((canvas, width, height) => {
          // 初始化图表
          const Chart = echarts.init(canvas, null, {
              width: width,
              height: height
          });
          Chart.setOption(this.getOption());
          Chart.on('click', (params) => {
              this.getActiveUser(params.name)
          })

      return Chart;
  });

其实就是在初始化图表的时候加了个事件,但是在小程序中居然可以使用click事件,这也是让我很意外的,这样就跟在PC中的操作类似了,但是其中有一点特别需要注意,这也让我今天花费了不少时间排错,就是里面的params它是这个实例的对象,我们本可以作为参数直接传进点击事件的函数中,但是它报错了,由于现在没有图,不过类似于这样

baocuo
Maximum call stack size exceeded翻译过来就是:超出最大调用堆栈大小,这在网上有各种各样的回答,我这项目中报这个错,看了一圈发现可能是这个params的问题,有可能是这个对象太大了导致传递报错,所以我直接this.getActiveUser(params.name)获取了其中我需要传过去的参数,结果真是这样,以下是点击获取并存储点击后的数据

 getActiveUser: function (params) {
  console.log(params);
  if (params != this.data.paramsList) {
    this.setData({
      paramsList: params,
      ActiveCurrentPage: 0,
    })
  }
  var that = this
  if (that.data.canLoad) {
    that.setData({
      canLoad: false,
      isLoad: true
    })
  }
  var page = parseInt(that.data.ActiveCurrentPage) + 1;
  var canLoad = that.data.canLoad;
  wx.showLoading({
    title: '加载中',
    mask: true,
  })
  app.ajax({
    type: 'post',
    url: 'MemberAnalyzeActiveList',
    data: {
      timestamp: params,
      page: page,
      pageSize: 10
    },
    success: res => {
      wx.hideLoading();
      if (res.status == 1) {
        var ActivetableData = that.data.ActivetableData
        var canLoad = true;
        if (page == 1) {
          ActivetableData = res.data.list
        } else {
          var otherList = res.data.list;
          ActivetableData.push.apply(ActivetableData, otherList);
        }
        if (res.data.list.length < 10) {
          canLoad = false;
        }
        that.setData({
          ActivetableData: ActivetableData,
          canLoad: canLoad,
          ActiveCurrentPage: page,
          isLoad: false
        })
      } else {
        that.setData({
          isLoad: false
        })
        wx.showToast({
          title: "暂无数据",
          icon: 'none',
          duration: 1000,
          mask: true,
        })
      }
    }
  })

},哦
有好多目的之外的代码,只是为了更好的显示效果,这样大致就没问题了

但我需要用到三张表,这倒不是问题,照着如上的方法,修改一些值就可以直接生成了;接着我想把这三张表放进微信官方的swiper里面让它们能滑动展示并可以点击渲染到表格,所以我是如下做的

<view style="height:100%">
    <swiper duration="500" style="height:600rpx" bindchange="swiperChange">
        <swiper-item>
            <view>
                <view class="viphead">活跃会员</view>
                <view class="circle_vip">
                    <view class="container">
                     <!-- force-use-old-canvas="true" -->
                        <ec-canvas id="activeChart" ec="{{ ec_active }}"></ec-canvas>
                    </view>
                </view>
            </view>
        </swiper-item>
        <swiper-item >
            <view>
                <view class="viphead">沉睡会员</view>
                <view class="circle_vip">
                    <view class="container">
                     <!-- force-use-old-canvas="true" -->
                        <ec-canvas id="sleepChart" ec="{{ ec_sleep }}"></ec-canvas>
                    </view>
                </view>
            </view>
        </swiper-item>
        <swiper-item>
            <view>
              <view class="viphead">生日会员</view>
              <view class="circle_vip">
                  <view class="container">
                   <!-- force-use-old-canvas="true" -->
                      <ec-canvas id="sleepChart" ec="{{ ec_sleep }}"></ec-canvas>
                  </view>
              </view>
          </view>
      </swiper-item>
  </swiper>

写完没毛病,也是可以展示的,但是它的点击事件就是触发不了,一开始我以为是在bindtap或者touchstart的小程序中使用onclick本身就不好,但结果发现不是这个原因,思考了挺久想想有可能跟swiper和echarts的执行顺序或者他们之间的渲染机制有关系,所以我用了一个很笨的办法

 swiperChange: function (e) {
  console.log(e);
  this.setData({
    pieIndex: e.detail.current
  })
  if (e.detail.current == 0) {
    this.activeComponnet = this.selectComponent('#activeChart');
    this.getActiveUserBread()
  }
  if (e.detail.current == 1) {
    this.sleepComponnent = this.selectComponent('#sleepChart');
    this.getSleepUserBread()
  } else if (e.detail.current == 2) {
    this.birthComponnent = this.selectComponent('#birthChart');
    this.getBirthUserBread()
  }
  this.setData({
    ActivetableData: []
  })
},

就是根据swiper发生改变时候,在拿到它的index值后再进行初始化图表,结果这样真的解决了。

另外我还尝试了在Page({...})的外面init出饼图,图是可以正常出来,利用promise同步执行获取到值也可以渲染到图表上,但它的事件怎么也触发不了,所以我放弃了,下面附上我放弃的代码

import * as echarts from '../ec-canvas/echarts';
var app = getApp();
Page({
data: {
  ec_active: {
    onInit: initChart
  },
  ActivetableData: [], // 活跃会员
  ActiveCurrentPage: 1,
  pagesize: 10,
  activeVip: [],
  paramList: {}, //饼状图参数 
},
toUserDetail: function (e) {
  wx.navigateTo({
    url: '../vipdetail/vipdetail'
  })
},

//  活跃会员
getActiveUserBread() {
  var store_id = wx.getStorageSync('storeid');
  var merchant_id = wx.getStorageSync('merchantid');
  var that = this;
  app.ajax({
    type: 'post',
    url: 'MemberAnalyzeActive',
    data: {
      store_id: store_id,
      merchant_id: merchant_id
    },
    success: res => {
      if (res.status == 1) {
        console.log(res);
        that.data.activeVip = res.data
        that.setData({
          activeVip: that.data.activeVip
        })
      }
    }
  })
},

//生命周期函数--监听页面加载
onLoad: function (options) {

},
})

function initChart(canvas, width, height, dpr) {
const chart = echarts.init(canvas, null, {
  width: width,
  height: height,
  devicePixelRatio: dpr // new
});
canvas.setChart(chart);
var activeVip = []
let promise = new Promise((resolve, reject) => {
  app.ajax({
    type: 'post',
    url: 'MemberAnalyzeActive',
    data: {},
    success: res => {
      if (res.status == 1) {
        activeVip = res.data
        resolve(res.data)
      } else {
        reject()
      }
    }
  })
})
promise.then(() => {
  var option = {
    backgroundColor: "#ffffff",
    color: ["#37A2DA", "#32C5E9", "#67E0E3", "#91F2DE", "#FFDB5C", "#FF9F7F"],
    series: [{
      label: {
        normal: {
          fontSize: 14
        }
      },
      type: 'pie',
      center: ['50%', '50%'],
      radius: '60%',
      label: {
        normal: {
          formatter: '{b}\n({d}%)\n{c}人',
          textStyle: {
            fontWeight: 'normal',
            fontSize: 12
          }
        }
      },
      data: activeVip,
    }]
  };
  chart.setOption(option);
  return chart;
})
}

哎放弃了,还是我上面的写法看着好


小结

今天是初次在小程序中使用echarts,本以为PC上可以直接复用的代码,结果遇到了好多坑,像我这样的萌新们不怎么会使用echarts小程序版的,可以随意参考一下;此外,若文中有不足的地方,欢迎留言私信呀,第一次写文章,不足之处还请前辈们见谅。

图片的标注