Echats实现可交互拖拽分类式雷达图

不多说直接先上结果。

最终效果图(死亡配色哈哈哈,改,全都可以改,点位置、颜色、文字、大小什么都可以改)

(本文内容涵括:Echats多图表生成、图表隐藏、雷达图图表层颜色调整、图表点拖拽实现、雷达图的拖拽样式;用JavaScript读取excel数据、把字符串数据转成数字类型、再两两组合成组)

  闲着也是闲着,还不如琢磨提升效率的事情。

前景提要:如何处理一表需要八项分类、4个层级的数据,用可视化图表展示分布情况?用什么图?如何实现?

当前难题与解决思路:

  • 没有相关图表——自己定制
  • 图表制作当前靠手工制作,一个一个在PPT贴好,费时费力——靠程序脚本生成
  • 相关程序生成的分布不美观——对不合适的点拖拽到合适地方,点要能交互
  • 同事设计出身,不懂程序操作——程序要简单易用

出于对标签可拖拽的事,一直心心念,也觉得之前那图实在是太太太丑了,还好,偶然发现Echats中有符合心意的功能。自己这边情况的电脑不能安装任何软件,所以HTML与JavaScript这种可以靠在线网站实现的最适合我现在用了。

参考链接

很好就是这种。可惜没有现成的可用,都是直角坐标系,极坐标系得自己整一个,也没学过JavaScript。不过看起来不难,就直接啃吧。

从官方参考资料知道主要是使用  convertToPixel 这个 API,进行了从数据到“像素坐标”的转换,所以我换一个坐标图无所谓,只要把转换坐标改就行,官方也有考虑到,留着“polar”极坐标系。

改成这样 换成”polar“(我不喜欢dataitem,所以换成item名字)

position: myChart.convertToPixel('polar', item),

拖拽功能有现成的,下一步是生成想要的图,我需要的图标签位置和一般雷达图不一样。得亏Echats功能众多,想了个办法——表层图表+隐藏图表。

先准备一个表层的。

标签再建立一个16分层的,隐藏掉轴线与线段,隔段打入标签名字。

 Echats实现多图表方法——Examples - Apache ECharts

实现轴线等隐藏,在配置手册也有。

        radar: [
         { //表层雷达图 
          indicator: [
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            
          ],
           //调整大小
           radius: ["0%", "95%"],
           //调整层数,需要4层
           splitNumber : 4,
           //是否显示坐标轴轴线
           axisLine: {
           show: true
           },
           //分层颜色
           splitArea: {
           areaStyle: {
              color: ['rgba(252, 211, 40, 0.68)', 'rgba(250, 178, 30, 0.67)', 'rgba(245, 138, 9, 0.67)', 'rgba(255, 80, 0, 0.66)']
            }
          }           
           
          },          
           
          { //通过标签的雷达图
           indicator: [
            
            { name: '', max: 4 },
            { name: '系统反馈', max: 4 },
            { name: '', max: 4 },
            { name: '内容需求', max: 4 },
            { name: '', max: 4 },
            { name: '功能需求', max: 4 },
            { name: '', max: 4 },
            { name: '信息架构', max: 4 },
            { name: '', max: 4 },
            { name: '交互设计', max: 4 },
            { name: '', max: 4 },
            { name: '信息设计', max: 4 },
            { name: '', max: 4 },
            { name: '界面设计', max: 4 },
            { name: '', max: 4 },
            { name: '视觉呈现', max: 4 },
          ],
            //调整大小
            radius: ["0%", "90%"],
            //隐藏轴线
            axisLine: {
              show: false
            },
            //隐藏分层
             splitArea: {
             show: false,
             },
            //线设置为透明
             splitLine: {
              lineStyle: {
                 color: "rgba(255, 255, 255, 0)"//不透明度为0
              }
             },
            //标签字体设置
            axisName: {
              fontWeight: "bold",
              fontSize: 15,
              color: "rgba(0, 0, 0, 1)"
            }

          },
        ],

表面功夫做好了,准备用一个雷达图只显示点也隐藏掉图。为什么?因为原本以外两个就够了,但是要移动点,雷达图轴必须是”value“类型——radiusAxis的type和angleAxis。否则用类别形式数据点不能自由移动,或者图表会随着点位置变化而变化(所以要加上max和min限制)。用radar自由度不高也难实现,所以用polar生成。

效果:

 代码段:

var data = [];
var symbolSize = 20;
var ban = [
    'sada', '1', '2', '3', '4', '5', '6','7','8','9','10','11','12','13','14','15','16'
];
var duan = ['1', '2', '3', '4'];
option = {

  polar: {radius: ["0%", "90%"]//大小
  },
  
  angleAxis: [{
    type: 'value',
    data: ban,//分16瓣
    min: 1,//固定1到17瓣,这样value类型时才不会图表随点变化
    max:17,
    boundaryGap: false,//坐标轴两边留白策略
    axisLine: {
      show: false//隐藏线
    }}],
  radiusAxis: [{
    type: 'value',
    data: duan,//分4层
    max: 4,
    min: 0,
    axisLine: {
      show: false
    },
    axisLabel: {
      rotate: 45//长度坐标文字倾斜角度
    }
  }],
  
  series: [
    {
      //name: 'Punch Cardbbb',
      id: 'a',
      type: 'scatter',
      coordinateSystem: 'polar',
      symbolSize: symbolSize,//点大小
      data: data//你所需要的数据
    }]};

把它们合在一起,我再显示出第三层和第一层图表,整个图的底层实现逻辑就是这样的关系。用这种方法实现数据按要求归类,也容易确定相应的坐标数据应该是多少。

 雷达图实现。JavaScript也就学了一点基础就直接开敲,可能有些多余的或者格式错误。

代码段 

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <script src="" rel="external nofollow" ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.2.2/echarts.min.js"></script>
</head>
<body>
    <div id="main" style="width: 600px;height:400px;"></div>
    <p id="demo"></p>
    <script type="text/javascript">

    var symbolSize = 10;
    var data = [[2,4],[1,2],[2,1],[1,1]];
    var ban = ['1', '2','3', '4', '5', '6','7','8','9','10','11','12','13','14','15','16'];
    var duan = ['1', '2', '3', '4'];  
    var myChart = echarts.init(document.getElementById('main'));
    myChart.setOption({       
        radar: [
         { //表层雷达图 
          indicator: [
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            { name: '', max: 4 },
            
          ],
           //调整大小
           radius: ["0%", "95%"],
           //调整层数,需要4层
           splitNumber : 4,
           //是否显示坐标轴轴线
           axisLine: {
           show: true
           },
           //分层颜色
           splitArea: {
           areaStyle: {
              color: ['rgba(252, 211, 40, 0.68)', 'rgba(250, 178, 30, 0.67)', 'rgba(245, 138, 9, 0.67)', 'rgba(255, 80, 0, 0.66)']
            }
          }           
           
          },          
           
          { //通过标签的雷达图
           indicator: [
            
            { name: '', max: 4 },
            { name: '系统反馈', max: 4 },
            { name: '', max: 4 },
            { name: '内容需求', max: 4 },
            { name: '', max: 4 },
            { name: '功能需求', max: 4 },
            { name: '', max: 4 },
            { name: '信息架构', max: 4 },
            { name: '', max: 4 },
            { name: '交互设计', max: 4 },
            { name: '', max: 4 },
            { name: '信息设计', max: 4 },
            { name: '', max: 4 },
            { name: '界面设计', max: 4 },
            { name: '', max: 4 },
            { name: '视觉呈现', max: 4 },
          ],
            //调整大小
            radius: ["0%", "90%"],
            //隐藏轴线
            axisLine: {
              show: false
            },
            //隐藏分层
             splitArea: {
             show: false,
             },
            //线设置为透明
             splitLine: {
              lineStyle: {
                 color: "rgba(255, 255, 255, 0)"//不透明度为0
              }
             },
            //标签字体设置
            axisName: {
              fontWeight: "bold",
              fontSize: 15,
              color: "rgba(0, 0, 0, 1)"
            }

          },
        ],

        series: [
          {
            type: 'radar',
            symbolSize : symbolSize,
            data: data
          }
        ],
      //隐藏的雷达图
        polar: {radius: ["0%", "95%"]},
        angleAxis: [{
          type: 'value',
          data: ban,
          max: 17,
          min: 1,
          show:false,
          
          boundaryGap: false,
          splitLine: {
            show: true
          }}
        ],
        radiusAxis: [{
          type: 'value',
          data: duan,
          max: 4,
          min: 0,
          show:false,
          axisLine: {
            show: false
          },
          axisLabel: {
            rotate: 45
          }
        }],
        series: [
          {
            //name: 'Punch Cardbbb',
            id: 'a',
            type: 'scatter',
            show: true,
            color:"rgba(254, 253, 253, 0.66)",
            coordinateSystem: 'polar',
            symbolSize: symbolSize,
            data: data
          }]
      
    });
        myChart.setOption({
        graphic: echarts.util.map(data, function (item, dataIndex) {
            return {
                type: 'circle',
                position: myChart.convertToPixel('polar', item),
                shape: {
                    r: symbolSize / 3
                },
                invisible: true,
                draggable: true,
                ondrag: echarts.util.curry(onPointDragging, dataIndex),
                onmousemove: echarts.util.curry(showTooltip, dataIndex),
                onmouseout: echarts.util.curry(hideTooltip, dataIndex),
                z: 100
            };
        })
    });

    window.addEventListener('resize', function () {
        myChart.setOption({
            graphic: echarts.util.map(data, function (item, dataIndex) {
                return {
                    position: myChart.convertToPixel('polar', item)
                };
            })
        });
    });

    function showTooltip(dataIndex) {
        myChart.dispatchAction({
            type: 'showTip',
            seriesIndex: 0,
            dataIndex: dataIndex
        });
    }

    function hideTooltip(dataIndex) {
        myChart.dispatchAction({
            type: 'hideTip'
        });
    }

    function onPointDragging(dataIndex, dx, dy) {
        data[dataIndex] = myChart.convertFromPixel('polar', this.position);
        myChart.setOption({
            series: [{
                id: 'a',
                data: data
            }]
        });
    }
</script>
</body>
</html>

图搞定了,接下来怎么把excel的数据直接用上让它直接生成数据点呢?原表已经处理好了数据,直接引入就行。(注意,实际用时只留数据就行,”y坐标“、”角坐标“要去掉。因为现在读取表只是简单获取)

参考了别人的

jquery获取excel表格数据 - 简书 (jianshu.com)

原本他是生成json类型,但实在是不好用就改成生成csv类型

// 遍历每张表读取
 for (var sheet in workbook.Sheets) {
       if (workbook.Sheets.hasOwnProperty(sheet)) {
            fromTo = workbook.Sheets[sheet]['!ref'];
            console.log(fromTo);
            persons = persons.concat(XLSX.utils.sheet_to_csv(workbook.Sheets[sheet]));
            break;
       }

是为了接下来进行将字符串转换成数字,用parseFloat可以处理浮点数,用parseInt反而把小数点以后去掉了。然后再两个两个打包成一组[1.5,2]这样的。代码实现如下

var shuju =0;
  $('#excel-file').change(function(e) {
      var files = e.target.files;
      var fileReader = new FileReader();
      var s = [];

      fileReader.onload = function(ev) {
          try {
              var data = ev.target.result,
                  workbook = XLSX.read(data, {
                      type: 'binary'
                  }), // 以二进制流方式读取得到整份excel表格对象
                  persons = []; // 存储获取到的数据
          } catch (e) {
              console.log('文件类型不正确');
              return;
          }

          // 表格的表格范围,可用于判断表头是否数量是否正确
          var fromTo = '';


          // 遍历每张表读取
          for (var sheet in workbook.Sheets) {
              if (workbook.Sheets.hasOwnProperty(sheet)) {
                  fromTo = workbook.Sheets[sheet]['!ref'];
                  console.log(fromTo);
                  persons = persons.concat(XLSX.utils.sheet_to_csv(workbook.Sheets[sheet]));
                  break; // 如果只取第一张表,就取消注释这行
              }
          } 
          var w = [];                                   
          var a = [];
          s = persons.toString().split("\n");
          s.pop();//莫名其妙数据没少情况下最后多了一个NaN,删掉
          //console.log(s);//如果有问题可以取消这里注释看生成了什么

          w = s.toString().split(",");
          //console.log(w);//如果有问题可以取消这里注释看生成了什么

          //变成数字
          var turnNum = function(nums){
              for(let i=0;i<nums.length;i++){
                  nums[i] = parseFloat(nums[i])
              }        
              return nums;
          }
          a = turnNum(w)
          //console.log(a);//如果有问题可以取消这里注释看生成了什么



          //变成两个一组
          var b = [];
          var result = [];
          var k = 0;

          for(var i = 0; i<a.length; ++i){
              if(i%2 == 0){
                  b = [];
                  for(var j = 0; j<2; ++j){
                      if(a[i+j] == undefined){
                          continue;
                      } else{
                          b[j] = a[i+j];
                      }
                  }
                  result[k] = b;
                  k++;
                  shuju = result;
              }

          }                   
          //console.log(result); //如果有问题可以取消这里注释看生成了什么

最终生成的result:

 最终代码合并一下就是

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
        <script src="https://cdn.bootcss.com/xlsx/0.11.5/xlsx.core.min.js"></script>
        <script src="" rel="external nofollow" ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.2.2/echarts.min.js"></script>
    </head>
    <body>
        <input type="file" id="excel-file">
        <div id="main" style="width: 600px;height:400px;"></div>
        <script type="text/javascript">
          var shuju =0;
            $('#excel-file').change(function(e) {
                var files = e.target.files;
                var fileReader = new FileReader();
                var s = [];
                
                fileReader.onload = function(ev) {
                    try {
                        var data = ev.target.result,
                            workbook = XLSX.read(data, {
                                type: 'binary'
                            }), // 以二进制流方式读取得到整份excel表格对象
                            persons = []; // 存储获取到的数据
                    } catch (e) {
                        console.log('文件类型不正确');
                        return;
                    }
    
                    // 表格的表格范围,可用于判断表头是否数量是否正确
                    var fromTo = '';
                    

                    // 遍历每张表读取
                    for (var sheet in workbook.Sheets) {
                        if (workbook.Sheets.hasOwnProperty(sheet)) {
                            fromTo = workbook.Sheets[sheet]['!ref'];
                            console.log(fromTo);
                            persons = persons.concat(XLSX.utils.sheet_to_csv(workbook.Sheets[sheet]));
                            break; // 如果只取第一张表,就取消注释这行
                        }
                    } 
                    var w = [];                                   
                    var a = [];
                    s = persons.toString().split("\n");
                    s.pop();//莫名其妙数据没少情况下最后多了一个NaN,删掉
                    //console.log(s);//什么情况可随时取消注释调出来看
                    w = s.toString().split(",");
                    //console.log(w);//如果有问题可以取消这里注释看生成了什么
                    
                    //变成数字
                    var turnNum = function(nums){
                        for(let i=0;i<nums.length;i++){
                            nums[i] = parseFloat(nums[i])
                        }        
                        return nums;
                    }
                    a = turnNum(w)
                    //console.log(a);//如果有问题可以取消这里注释看生成了什么
                    
                    

                    //变成组
                    var b = [];
                    var result = [];
                    var k = 0;

                    for(var i = 0; i<a.length; ++i){
                        if(i%2 == 0){
                            b = [];
                            for(var j = 0; j<2; ++j){
                                if(a[i+j] == undefined){
                                    continue;
                                } else{
                                    b[j] = a[i+j];
                                }
                            }
                            result[k] = b;
                            k++;
                            shuju = result;//结果赋值给shuju变量,给下面用
                        }

                    }                   
                    //console.log(result); //如果有问题可以取消这里注释看生成了什么                
                    

                  //图表生成段
                  var symbolSize = 10;//点大小,虽然没用但是不能没有
                  var data = shuju;//接收到要呈现的数据
                  var data1 = [[1,1],[2,2]];//随便给一个
                  var ban = ['1', '2','3', '4', '5', '6','7','8','9','10','11','12','13','14','15','16'];//隐藏层分16瓣
                  var duan = ['1', '2', '3', '4'];  
                  var myChart = echarts.init(document.getElementById('main'));
                  myChart.setOption({
                    //表面的雷达图
                      radar: [
                       {  
                        indicator: [
                          { name: '', max: 4 },
                          { name: '', max: 4 },
                          { name: '', max: 4 },
                          { name: '', max: 4 },
                          { name: '', max: 4 },
                          { name: '', max: 4 },
                          { name: '', max: 4 },
                          { name: '', max: 4 }
                        ],
                         //调整大小
                         radius: ["0%", "95%"],
                         //调整层数,需要4层
                         splitNumber : 4,
                         //分层颜色
                         splitArea: {
                            areaStyle: {
              color: ['rgba(252, 211, 40, 0.68)', 'rgba(250, 178, 30, 0.67)', 'rgba(245, 138, 9, 0.67)', 'rgba(255, 80, 0, 0.66)']
                          }
                         }
                        },          
                         
                        {//标签的雷达图
                         indicator: [
                          { name: '', max: 4 },
                          { name: '系统反馈', max: 4 },
                          { name: '', max: 4 },
                          { name: '内容需求', max: 4 },
                          { name: '', max: 4 },
                          { name: '功能需求', max: 4 },
                          { name: '', max: 4 },
                          { name: '信息架构', max: 4 },
                          { name: '', max: 4 },
                          { name: '交互设计', max: 4 },
                          { name: '', max: 4 },
                          { name: '信息设计', max: 4 },
                          { name: '', max: 4 },
                          { name: '界面设计', max: 4 },
                          { name: '', max: 4 },
                          { name: '视觉呈现', max: 4 },
                        ],
                         //调整大小
                         radius: ["0%", "90%"],
                         //隐藏轴线
                         axisLine: {
                           show: false
                         },
                         //隐藏分层
                         splitArea: {
                         show: false,
                         },
                         //线设置为透明
                         splitLine: {
                          lineStyle: {
                             color: "rgba(255, 255, 255, 0)"
                           }
                         },
                         //标签字体设置
                         axisName: {
                            fontWeight: "bold",
                            fontSize: 15,
                            color: "rgba(0, 0, 0, 1)"
                          }

                        },
                      ],
                      series: [
                        {
                          type: 'radar',
                          symbolSize : symbolSize,//这些现在没什么用但是留着是为了随时调整
                          data: data1
                        }
                      ],
                    //隐藏的雷达图
                      polar: {radius: ["0%", "95%"],},
                      angleAxis: [{
                        type: 'value',
                        data: ban,//分16瓣
                        max: 17,//固定1到17瓣,这样value类型时才不会图表随点变化
                        min: 1,
                        show:false,//隐藏,需要调整时再看到取消注释就行
                        boundaryGap: false,//坐标轴两边留白策略
                        splitLine: {
                          show: true//虽然现在看不到,但到时候需要调整
                        }}
                      ],
                      radiusAxis: [{
                        type: 'value',
                        data: duan,
                        max: 4,//分4层
                        min: 0,
                        show:false,//隐藏,需要调整时再看到取消注释就行
                        axisLine: {
                          show: false//同上,隐藏线
                        },
                        axisLabel: {
                          rotate: 45//长度坐标文字倾斜角度
                        }
                      }],
                      series: [
                        {
                          id: 'a',//非常重要,id为锁定用交互的点,注意与下面一致
                          type: 'scatter',
                          //show: true,
                          color:"rgba(93, 153, 249, 0.80)",//点颜色调整
                          coordinateSystem: 'polar',
                          symbolSize: symbolSize,//点大小
                          data: data
                        }]

                  });
                      myChart.setOption({
                      graphic: echarts.util.map(data, function (item, dataIndex) {
                          return {
                              type: 'circle',
                              position: myChart.convertToPixel('polar', item),
                              shape: {
                                  r: symbolSize / 2//覆盖在点上的交互点大小
                              },
                              //invisible: false,//让点可见,方便随时调整
                              invisible: true,//交互点是否可见,现在是不可见
                              draggable: true,
                              ondrag: echarts.util.curry(onPointDragging, dataIndex),
                              onmousemove: echarts.util.curry(showTooltip, dataIndex),
                              onmouseout: echarts.util.curry(hideTooltip, dataIndex),
                              z: 100
                          };
                      })
                  });

                  window.addEventListener('resize', function () {
                      myChart.setOption({
                          graphic: echarts.util.map(data, function (item, dataIndex) {
                              return {
                                  position: myChart.convertToPixel('polar', item)
                              };
                          })
                      });
                  });

                  function showTooltip(dataIndex) {
                      myChart.dispatchAction({
                          type: 'showTip',
                          seriesIndex: 0,
                          dataIndex: dataIndex
                      });
                  }

                  function hideTooltip(dataIndex) {
                      myChart.dispatchAction({
                          type: 'hideTip'
                      });
                  }

                  function onPointDragging(dataIndex, dx, dy) {
                      data[dataIndex] = myChart.convertFromPixel('polar', this.position);
                      myChart.setOption({
                          series: [{
                              id: 'a',
                              data: data
                          }]
                      });
                  }
                };   
                // 以二进制方式打开文件
                fileReader.readAsBinaryString(files[0]);
                
            });
        </script>
    </body>

</html>

最终样式,然后就是慢慢一个一个挪了/(ㄒoㄒ)/~~比起之前的python做的图,在HTML用JavaScript的Echats有更多功能和可实现的地方,交互功能比python更容易达到。现在想想之前随机排实在是太丑了。后面再想办法把python的随机加进来和对数据表excel的判断加进来吧。

其实写的时候遇到许多问题,例如为什么点不能交互?数据为什么传递不过来?但现在是完成了才开始写稿,所以有些过程忘记了许多。HTML也就借个框,剩下都是JavaScript完成。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值