微信小程序实现手动生成想要的折线图

本文介绍了如何利用Echarts在Web上创建可自定义的折线图,通过JSON配置和用户输入动态调整数据,实现图表的实时更新,重点讲述了数据结构、组件初始化、事件处理和图表更新的逻辑设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有很多时候,看到一些柱状图/折线图的数据,总是跟自己想要的有出入,又无能为力,只有把整张图截下来,再用ps进行修改,这种比较常见。

不过随着echarts的广泛应用,在web上手动生成自己想要的折线图,似乎不再是件难事。

实现步骤

首先,需要在json文件中引入echarts组件

 "ec-canvas": "/components/ec-canvas/ec-canvas"

然后写好图形的骨架以及样式

<view class="container-area">
     <!-- 折线图 -->
     <view class='echart_wrap'>
    <ec-canvas id="mychart" canvas-id="mychart-line" ec="{{ ec }}"></ec-canvas>
     </view>
       <!-- 输入框 -->
       <view   style="display:flex;align-items: center;justify-content: space-between;margin-left:0rpx;padding-left:64rpx;padding-right:64rpx;margin-top:-136rpx;">
          <view class="input-area" wx:for="{{items}}" wx:key="name">
            <view class="title">
            <view class="title-name">{{item.name}}</view>
			 	    <view class="title-unit" style="margin-left: 0rpx;line-height:48rpx;color: #AAAAAA;font-size: 24rpx;">{{item.unit}}</view>
            </view>
            <view class="input-text">
            <input class="vue-input" value="{{item.default}}" type="text"  data-name='{{item.name}}' bindtap="toEntryData"/>
            </view>
          </view>
       </view>
       <!-- 确认按钮 -->
       <view class="btn-row">
           <view bindtap="add" class="btn-add">添加数据</view>
        </view>
        <!-- 动态表单 -->
        <view class="active-form" wx:for="{{items2}}" wx:key="name">
             <view style="display:flex;align-items: center;margin-left:0rpx;padding-left:42rpx;padding-right:0rpx;margin-top:15rpx;margin-bottom:0rpx;">
                 <view class="title">
                <view class="title-name">{{item.name1}}</view>
			 	        <view class="title-unit" style="margin-left: 0rpx;line-height:48rpx;color: #AAAAAA;font-size: 24rpx;">{{item.unit1}}</view>
                 </view>
                <view class="input-text">
                  <input class="vue-input" value="{{item.default1}}" type="text"  data-id='{{item.id}}' data-name='{{item.name1}}' bindtap="toEntryValue"/>
                </view>
                <view class="title">
                <view class="title-name">{{item.name2}}</view>
			 	        <view class="title-unit" style="margin-left: 0rpx;line-height:48rpx;color: #AAAAAA;font-size: 24rpx;">{{item.unit2}}</view>
                 </view>
                <view class="input-text">
                  <input class="vue-input" value="{{item.default2}}" type="text"  data-id='{{item.id}}' data-name='{{item.name2}}' bindtap="toEntryValue"/>
                </view>
                <view data-id='{{item.id}}' class="btn-delete" bindtap="remove">
                    -
                </view>
             </view>
        </view>
</view>
.container-area{
  background: #FFF;
  position: relative;
  z-index:999;
  opacity: 1;
  padding: 0;
 }
 .echart_wrap{
   width:100%;
   height:600rpx;
 }
 .input-area{
  display:flex;
  margin-left: 8rpx;
  margin-right:19rpx;
  margin-top:33rpx;
  margin-bottom:30rpx;
}
.title{
  height: 48rpx;
  display: flex;
}
.title .title-name{
  font-size: 28rpx;
  margin-right: 10rpx;
  font-weight: 600;
}
.title .title-unit{
  font-size: 20rpx;
  font-weight: 600;
  color: #AAAAAA;
  position: relative;
  bottom: 0rpx;
  line-height: 50rpx;
  margin-left:2rpx;
}
.input-text{
  margin-left:15rpx;
}
.vue-input{
  width: 136rpx;
  border: 2rpx solid #D9D9D9;
  border-radius: 8px;
  background: #F3F3F3;
  height: 48rpx;
  padding: 0 10rpx 0 4rpx;
  font-weight: bold;
  font-size: 30rpx;
  line-height: 42rpx;
  display: flex;
  align-items: center;
  text-align: right;
  color: #000000;
}
.btn-row{
    display: flex;
    justify-content: center;
}
.btn-add{
    height: 70rpx;
    line-height: 70rpx;
    padding-left: 30rpx;
    padding-right: 30rpx;
    color: #FFF;
    background-color: #D0021B;
    border-radius: 10rpx;
    text-align: center;
}
.active-form{
  margin-left:-11rpx;
}
.active-form .input-text{
  margin-right:30rpx;
}
.btn-delete{
  width: 64rpx;
  height: 48rpx;
  text-align: center;
  font-weight: bold;
  font-size: 30rpx;
  line-height: 48rpx;
  box-shadow: 0px 8rpx 10rpx rgba(0, 0, 0, 0.06);
  background: #F3F3F3;
  border-radius: 8rpx;
}

从设计之初,认识到折线图是由数据决定的,而数据,肯定不能写死,它是根据用户手动添加/修改/删除的一套逻辑决定最终的展示数据,这需要在代码设计的时候就要考虑到。

逻辑部分,首先需要引入echarts并配置折线图的初始数据

import * as echarts from '../../components/ec-canvas/echarts';
var Chart=null;
// 图表配置项
var option = {
  title: {//标题
    text: '速度变化折线图',
    left: 'center',
    top:15
  },
  renderAsImage: true, //支持渲染为图片模式
  color: ["#FFC34F", "#FF6D60", "#44B2FB"],//图例图标颜色

  legend: {
    show: true,
    itemGap: 25,//每个图例间的间隔
    top: 30,
    x: 30,//水平安放位置,离容器左侧的距离  'left'
    z: 100,
    textStyle: {
      color: '#383838'
    },
  },
  grid: {//网格
    left: 10,
    top:80,
    containLabel: true,//grid 区域是否包含坐标轴的刻度标签
  },
  xAxis: { //横坐标
    type: 'category',
    name: '时间', //横坐标名称
    nameTextStyle: { //在name值存在下,设置name的样式
    color: 'red',
    fontStyle: 'normal',
    padding:-10
    },
    nameLocation: 'end',
    splitLine: { //坐标轴在 grid 区域中的分隔线。
    show: true,
    lineStyle: {
    type: 'dashed'
    }
    },
    boundaryGap: false,
    data: [],
    axisLabel: {
    textStyle: {
    fontSize: 13,
    color: '#5D5D5D'
     }
    }
  },
  yAxis: {//纵坐标
    type: 'value',
    position:'left',
    name: '速度',//纵坐标名称
    nameTextStyle:{//在name值存在下,设置name的样式
      color:'#D0021B',
      fontStyle:'normal',
    },
    splitNumber: 5,//坐标轴的分割段数
    splitLine: {//坐标轴在 grid 区域中的分隔线。
      show: true,
      lineStyle: {
        type: 'dashed'
      }
    },
    axisLabel: {//坐标轴刻度标签
      formatter: function (value) {
        console.log(value)
        var xLable = [];
        xLable.push(value)
        return xLable
      },
      textStyle: {
        fontSize: 13,
        color: '#5D5D5D',
      }
    },
    min: 0,
    max: 100,
  },
  series: [
  {
    name: '速度',
    type: 'line',
    smooth:true,
    data:[],
    symbol: 'none',
    itemStyle: {
      normal: {
        lineStyle: {
          color: '#D0021B'
        }
      }
    }
  }, 
 ],
}

然后在组件的data里面,初始化数据

  data: {
    ec: {
      onInit: function (canvas, width, height) {
        chart = echarts.init(canvas, null, {
          width: width,
          height: height
        });
        canvas.setChart(chart);
         return chart;
      },
      lazyLoad: true // 延迟加载
    },
    items:{
      type:Array,
      value:[
           {name:'速度',value:'speed',unit:'',default:0},
           {name:'时间',value:'time',unit:'',default:0}
       ],
    },
    items2:{
      type:Array,
      value:[
       ],
    },
    default:0,
    inputArr:[],
    defaultInput:0,
    params:{
       speed:0,
       time:0
    },
    abscissaArr:[],
    ordinateArr:[],
  },

其中,ec表示初始化折线图的chart,items和items2分别表示添加数据前后的表单数组,这表明初始的时候,已经有一个默认的坐标了,那就是(0,0),往后添加/修改删除的都是items2里面的数据,它是一个根据用户需要不断变化的数据,也决定图形的最终走向。

在方法里面,分别具备新增/修改/删除一个输入表单的点击事件,首先是新增

        add(){
          let params=this.data.params
          let res=this.data.abscissaArr.some(item=>
            item>params.time
          )
          if(res){
            wx.showToast({
              title: '输入时间过小!',
              icon:'none'
            })
            return;
          }
          console.log(res)    
          if(!this.data.abscissaArr.includes(params.time)){
            // 横坐标push
            this.data.abscissaArr.push(params.time)
          }
          else{
            wx.showToast({
              title: '请勿输入重复时间',
              icon:'none'
            })
            return;
          }
          //纵坐标push
          this.data.ordinateArr.push(params.speed)
          //动态更新表单
          let dataObject={
            id:'',name1:'速度',value1:'speed',unit1:'',default1:0,name2:'时 
            间',value2:'time',unit2:'',default2:0
          }
          dataObject.id=this.data.items2.length
          dataObject.default1=params.speed
          dataObject.default2=params.time
          let aaa=dataObject
          this.data.items2.push(aaa)
          for(let i=0;i<this.data.items2.length;i++){
              if(this.data.items2.length==1){
                this.setData({[`items2[${0}]`]:aaa})
              }
              if(this.data.items2.length>1){
                this.setData({[`items2[${this.data.items2.length-1}]`]:aaa})
              }
          }
          //赋值给图表
          option.xAxis.data=this.data.abscissaArr
          option.series[0].data=this.data.ordinateArr
          this.init_echarts()
        },

再新增横纵坐标的时候,横坐标我索性直接做了一层限制,输入比当前数据小的,和当前一样的,直接pass掉,只有输入比当前数据大的,才将它push到横坐标所对应的数组里面,纵坐标则没有限制,直接根据输入的数据push,每新增一次,都会有一个动态表单,根据最新输入成功的数据(包括横纵坐标的id,name,value)对items2进行赋值,保证items2里面是最新的数据,同时,还要把最新的横纵坐标赋值给图表,这样更新echarts之后,才能生成最新的图形。

然后是修改

       toEntryValue(e){
          let title="修改"+e.currentTarget.dataset.name;   
          let that=this;
          let type=e.currentTarget.dataset.name;
          let id=e.currentTarget.dataset.id;
          if(type=='时间'&&id==0){
            wx.showToast({
              title: '该条数据不可修改!',
              icon:'none'
            })
            return;
          }
          let selectArray=this.data.items2.filter(function(item){
            return item.id==id
          })
          let value=null
          if(type=='速度'){
            value=selectArray[0].default1;
          }
          if(type=='时间'){
            value=selectArray[0].default2;
          }
          wx.showModal({
            title:title,
            editable:true,
            content:value+'',
            success:function(res){
              if(res.confirm){
                let content=res.content;
                var v_regExpValidNum = new RegExp("^[-]?[0-9]*([.][0-9]{0,5})?$");
                var isNumber = v_regExpValidNum.test(content);
                if(!isNumber){
                  wx.showToast({
                    title: '请输入数字',
                    icon:'none'
                  })
                  return;
                }
                content=Number(content);
                that.changeData(content,e.currentTarget.dataset.name,id)
              }
            }          
          })
        },
        // 改变动态表单并更新图表
        changeData(value,type,id){
          for(let i=0;i<this.data.items2.length;i++){
            if(id==this.data.items2[i].id){
              if(type=='速度'){
                this.setData({[`items2[${i}].default1`]:value})
                this.data.ordinateArr[id]=value
              }
              if(type=='时间'){
                if(i!==0&&i!=this.data.items2.length-1&&value<this.data.items2[i+1].default2&&value>this.data.items2[i-1].default2){
                    this.setData({[`items2[${i}].default2`]:value})
                    this.data.abscissaArr[id]=value
                }
                else if(i==0&&this.data.items2.length==1){
                  this.setData({[`items2[${i}].default2`]:value})
                  this.data.abscissaArr[id]=value
                }
                else if(i==0&&this.data.items2.length>1&&value<this.data.items2[i+1].default2){
                  this.setData({[`items2[${i}].default2`]:value})
                  this.data.abscissaArr[id]=value
                }
                else if(this.data.items2.length>1&&i==this.data.items2.length- 
                  1&&value>this.data.items2[i-1].default2){
                  this.setData({[`items2[${i}].default2`]:value})
                  this.data.abscissaArr[id]=value
                }
                else{
                      wx.showToast({
                      title: '输入时间应该在正确的区间!',
                      icon:'none'
                    })
                  return;
                }
              }
            }
          }
          this.init_echarts()
        },

修改数据的时候,由于每一个表单具有横纵坐标,首先需要判断修改的是横坐标还是纵坐标,根据获取到的type进行判断,同时我对items2第一组数据的横坐标进行了限制,不允许修改。然后根据每组数据的id判断修改的是第几组数据,获取这组数据的坐标值,当弹窗出现的时候会返显这个数据。

弹窗出现,输入横坐标/纵坐标的数据,点击确定会调用一个函数,该函数将输入的value,对应的type,已经对应的id作为参数传入,在这里我又做了一层数据修改的限制,先找到items对应要赋值的数据,然后如果是修改的纵坐标,就直接赋值(包括items2本身,以及折线图数据),如果是横坐标,我需要让其修改的数据依然比它前面的数据大,比它后面的数据小,以及考虑了两端的情况,这样才能成功赋值。

最后是删除

        remove(e){
          let index=e.currentTarget.dataset.id
          if(index==0){
            wx.showToast({
              title: '该条数据不可移除!',
              icon:'none'
            })
            return;
          }
          let newArray=this.data.items2.filter(item=>{
            return item.id!=index
          })
          this.setData({
            items2:newArray
          })
          this.data.abscissaArr.splice(index,1)
          this.data.ordinateArr.splice(index,1)
          this.init_echarts()
          for(let i=0;i<this.data.items2.length;i++){
            this.setData({[`items2[${i}].id`]:i})
          }
        }

 一个items2有多组数据,具体移除哪一种数据,是根据它们唯一的id值来确定的,每一个表单都绑定了一个唯一的id值,除了第一组数据不能删除,其他数据通过id值筛选出最新的数组,将删除的数据排除在外,再用筛选的数组重新给items2赋值,保证items2数据始终是最新的,同时直接通过数组的splice方法,删除图表数据中指定位置的横纵坐标。记住:由于这里删除了一个id,原items2数组中即少了一条数据,如果不重新对id赋值,这样下一次操作会出现数据错乱的情况。所以,在这里需要根据从小到大的顺序,对原items2数组的每一个id值重新赋值。

如此这般,就实现了可以根据需要手动生成横纵坐标的折线图。

效果展示

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值