Echarts散点图(火山图)自定义配置

  1. 创建DOM元素

    vue 复制代码
    <div>
        <div ref="chat" :style="{
            width: volcanoDimensions.width,
            height: volcanoDimensions.height,
          }" class="chat"></div>
      </div>
  2. 初始化图表

    需要实现,被筛选出的数据,带有label,labelLayout,labelLine,原定是直接在series的三组数据中,直接给出三者的相关配置:

    js 复制代码
    label: {
        show: true,
        formatter: function (param) {
          // 仅为目标点显示标签
          const specialPoints = ["a", "b"];
          if (specialPoints.includes(param.value[2])) {
            return param.value[2];
          }
          return "";
        },
        position: 'left',
        minMargin: 2
    },
    labelLine:{
      show: true
    },
    labelLayout: function (labelInfo) {
      // 从后端获取的匹配字段,当前给定['a','b']
      const specialPoints = ["a", "b"];
      if (specialPoints.includes(labelInfo.text)) {
        return {
          x: myChart.getWidth() - 500,
          moveOverlap: 'shiftY'
        };
      }
      return ""; // 非特定点不应用布局
    },

    目的是为了匹配后端返回的过滤数据,给出对应label和labelLine,如果当前点符合,才显示对应标签;但是页面显示label的时候:

    未匹配的点周围也会显示label,只是很短。尝试给labelLine加上条件判断,返回bool值。但是并没有起作用。

    js 复制代码
    label : {
      show: function (param) {
        // 仅为目标点显示标签线
        const specialPoints = ["a", "b"];
        return specialPoints.includes(param.value[2]);
      },
    }

    最后只能通过,筛选匹配数据,根据name往series里面push两组新数据作为额外两组数据,单独显示label

    如果是饼图,这种series里面只有一组数据,单独配置的话:

    直接通过遍历里面的数据,单独添加labellabelLine属性会更方便,两种方法均可行

    js 复制代码
    let label = {
        show:true,
        fontSize:14,
        color:'#000'
    }
    let labelLine = {
        show: true,
        lineStyle:{
            color: '#000'
        }
    }
    series.data.forEach((item,index)=>{
        const testArr = ['a','b','c']
    	if(testArr.includes(item.value)){
            item['label'] = label
            item['labelLine'] = labelLine
        }
    })
    js 复制代码
    data() {
        return {
          myChart: this.$refs.chat,
          // 默认初始配置
          volcanoChartOptions:{}
        };
    },
    
    mounted() {
        // 先初始化,之后从接口访问这个series数据
        // 理论上,所有的图表数据都应该是后端返回
        this.initChat();
        // 从接口返回图表配置数据
        this.loadvolcanoChartOptions();
    },
    
    methods:{
        initChat() {
            // 只能写从后端获取数据的时候,往series后面重新push一个新的对象,
            // 然后这个对象的其余配置都和对应name保持一致,但是label的显示隐藏不一致
            if (!this.myChart) {
                this.myChart = this.echarts.init(this.$refs.chat);
            }
        },
        // 加载后端数据
        async loadvolcanoChartOptions(){
            try{
                // 假设从接口模拟
                const response = await this.fetchvolcanoChartOptions()
                // console.log(response.data,"----");
                this.volcanoChartOptions = response.data
                // 将数据更新到vuex中
                this.updateVolcanoOptions(response.data)
                this.volcanoChartOptions && this.myChart.setOption(this.volcanoChartOptions);
            }catch(e){
                console.log("加载图表配置失败",e);
            }
        },
        // 创建接口,模拟从后端返回数据
            async fetchvolcanoChartOptions() {
          // 在这里替换成实际的 API 调用
          return new Promise((resolve) => {
            setTimeout(() => {
              resolve({
                data: {
                  title: {
                    text:'火山图',
                    left: 'center',
                    textStyle:{
                      fontFamily: 'Arial',
                      fontSize: 18,
                      color:'rgba(0,0,0, 1)'
                    }
                  },
                  grid: {
                    left: '3%',
                    right: '7%',
                    bottom: '7%',
                    containLabel: true,
                  },
                  tooltip: {
                    // trigger: 'axis',
                    showDelay: 0,
                    formatter: function (params) {
                      if (params.value.length > 1) {
                        return (
                          'X: ' + params.value[0] +
                          '<br/>' +
                          'Y: ' + params.value[1] +
                          '<br/>' +
                          'geneId: ' + params.value[2]
                        );
                      }
                    },
                    axisPointer: {
                      show: true,
                      type: 'cross',
                      lineStyle: {
                        type: 'dashed',
                        width: 1
                      }
                    }
                  },
                  legend: {
                    data: ['Up', 'Down', 'Nodiff'],
                    right: '10%',
                    top: '20%',
                    orient: 'vertical',
                    // 给图例的原点点加了个边框
                    // itemStyle:{
                    //   borderWidth:1,
                    //   borderColor:'black'
                    // }
                  },
                  xAxis: 
                    {
                      type: 'value',
                      scale: true,
                      axisLabel: {
                        formatter: '{value}',
                        fontSize: 12,
                        fontFamily: 'Arial',
                        color: 'rgba(0,0,0, 1)',
                      },
                      splitLine: {
                        show: false
                      },
                      min:'-25',
                      max:'10',
                      nameLocation:'middle',
                      name:'x轴标题',
                      nameGap: 30,  // 设置标题与轴的间隔
                      nameTextStyle: { 
                        fontFamily: 'Arial', 
                        fontSize: 16,
                        color: 'rgba(0,0,0, 1)',
                      },
                    }
                  ,
                  yAxis: 
                    {
                      type: 'value',
                      scale: false,
                      axisLabel: {
                        formatter: '{value}',
                        fontSize: 12,
                        fontFamily: 'Arial',
                        color: 'rgba(0,0,0, 1)',
                      },
                      // 是否展示横线,每个刻度的横线
                      splitLine: {
                        show: false
                      },
                      min:'-5',
                      max:'35',
                      nameLocation:'middle',
                      name:'y轴标题',
                      nameGap: 30,  // 设置标题与轴的间隔
                      nameTextStyle: { 
                        fontFamily: 'Arial', 
                        fontSize: 16,
                        color: 'rgba(0,0,0, 1)',
                      },
                    }
                  ,
                  series:[
                    {
                      name: 'Nodiff',
                      color: 'rgba(192,192,192, 1)',
                      type: 'scatter',
                      symbolSize:5, // 标记点的大小
                      emphasis: {
                        focus: 'series'
                      },
                      data: [
                        [
                          -0.8,
                          8.2,
                          "ENSMUSG00000030342"
                        ],
                        [
                          -0.7,
                          8.1,
                          "ENSMUSG00000004633"
                        ],
                        [
                          -0.9,
                          7.6,
                          "ENSMUSG00000074802"
                        ],
                      ],
                      markLine: {
                        lineStyle: {
                          type: 'solid'
                        },
                      }
                    },
                    {
                      name: 'Up',
                      color: 'rgba(255, 51, 51,1)',
                      type: 'scatter',
                      symbolSize:5, // 标记点的大小
                      emphasis: {
                        focus: 'series'
                      },
                      // prettier-ignore
                      data: [
                        [
                          1.262,
                          4.1255,
                          "a"
                        ],
                        [
                          3.8047,
                          3.5476,
                          "b"
                        ],
                        [
                          1.2933,
                          3.1948,
                          "c"
                        ],
                        [
                          1.6687,
                          2.7994,
                          "ENSMUSG00000084844"
                        ],
                        [
                          7.1892,
                          2.7479,
                          "ENSMUSG00000000394"
                        ],
                        [
                          1.0471,
                          2.6834,
                          "ENSMUSG00000084904"
                        ],
                        [
                          1.835,
                          2.5167,
                          "ENSMUSG00000028707"
                        ],
                        [
                          1.1921,
                          2.333,
                          "ENSMUSG00000086502"
                        ],
                        [
                          1.1156,
                          2.3094,
                          "ENSMUSG00000028125"
                        ],
                        [
                          4.3545,
                          2.2873,
                          "ENSMUSG00000107313"
                        ],
                      ],
                      markLine: {
                        silent: true,
                        symbol: 'none', // 去掉箭头
                        data: [
                          // label label: {color: '#FF1D00',formatter:'',fontSize:10} formatter里面写echart横线竖线的标注
                          { xAxis: 'min', lineStyle: { color: '#2f2f2f' }, label: { color: '#FF1D00', formatter: '', fontSize: 10 } },
                          { yAxis: 'min', lineStyle: { color: '#2f2f2f' }, label: { color: '#FF7804', formatter: '', fontSize: 10 } }]
                        }
                      },
                    {
                      name: 'Down',
                      color: 'rgba(51, 51, 255, 1)',
                      type: 'scatter',
                      symbolSize:5, // 标记点的大小
                      emphasis: {
                        focus: 'series'
                      },
                      // prettier-ignore
                      data: [
                        [
                          -3.445,
                          29.9859,
                          "a"
                        ],
                        [
                          -1.418,
                          25.7115,
                          "b"
                        ],
                        [
                          -1.489,
                          25.1274,
                          "c"
                        ],
                        [
                          -3.445,
                          29.9859,
                          "a"
                        ],
                        [
                          -1.418,
                          25.7115,
                          "b"
                        ],
                        [
                          -1.489,
                          25.1274,
                          "c"
                        ],
                        [
                          -3.28,
                          23.0754,
                          "ENSMUSG00000056071"
                        ],
                        [
                          -1.999,
                          19.1505,
                          "ENSMUSG00000001741"
                        ],
                        [
                          -3.1,
                          18.2834,
                          "ENSMUSG00000056054"
                        ],
                        [
                          -1.649,
                          16.959,
                          "ENSMUSG00000025154"
                        ],
                        [
                          -2.92,
                          16.3029,
                          "ENSMUSG00000020264"
                        ],
                        [
                          -2.652,
                          11.7498,
                          "ENSMUSG00000037868"
                        ],
                        [
                          -4.807,
                          10.8801,
                          "ENSMUSG00000040380"
                        ],
                        
                      ],
                      markLine: {
                        silent: true,
                        symbol: 'none', // 去掉箭头
                        data: [
                          // label label: {color: '#FF1D00',formatter:'',fontSize:10} formatter里面写echart横线竖线的标注
                          { xAxis: 'max', lineStyle: { color: '#2f2f2f' }, label: { color: '#FF1D00', formatter: '', fontSize: 10 } }]
                      }
                    },
    
                    // push的数组,加上光圈的两组数据,即为筛选后的数据
                    {
                      name: 'Down',
                      color: 'rgba(51, 51, 255, 1)',
                      type: 'effectScatter',  //加上光圈效果,如果做切换,scatter就是纯点
                      symbolSize:6,  //光圈大小
                      emphasis: {
                        focus: 'series'
                      },
                      label: {
                        show: true,
                        formatter: function (param) {
                          return param.value[2]; // 显示 geneId
                        },
                        position: 'left',
                        minMargin: 2,
                        fontSize: 12,
                        fontFamily: 'Arial',
                        fontStyle: 'normal',  // 正常样式normal;斜体样式italic
                        color:'rgba(6, 6, 6, 1)', //标签字体颜色
                      },
                      // labelline不受return的控制,不受funtion的控制
                      labelLine: {
                        show: true,
                        lineStyle: {
                          color: 'rgba(48, 49, 51, 1)',
                          type: 'solid', //线条形状
                        }
                      },
                      labelLayout: {
                        x:this.myChart.getWidth() - 400,
                        y:this.myChart.getHeight()/3,
                        moveOverlap: 'shiftY'
                      },
                      // prettier-ignore
                      data: [[
                        -3.445,
                        29.9859,
                        "a"
                      ],
                      [
                        -1.418,
                        25.7115,
                        "b"
                      ],
                      [
                        -1.489,
                        25.1274,
                        "c"
                      ]],
                    },
                    {
                      name: 'Up',
                      color: 'rgba(255, 51, 51,1)',
                      type: 'effectScatter',
                      symbolSize:6,  //光圈大小
                      emphasis: {
                        focus: 'series'
                      },
                      label: {
                        show: true,
                        formatter: function (param) {
                          return param.value[2]; // 显示 geneId
                        },
                        position: 'right',
                        minMargin: 2,
                        fontSize: 12,
                        fontFamily: 'Arial',
                        fontStyle: 'normal',  // 正常样式normal;斜体样式italic
                        color:'rgba(6, 6, 6, 1)', //标签字体颜色
                      },
                      // labelline不受return的控制,不受funtion的控制
                      labelLine: {
                        show: true,
                        lineStyle: {
                          color: 'rgba(48, 49, 51, 1)',
                          type: 'solid',
                          // type: [5, 10], // 长短虚线间隔 [长度, 间隔]
                        }
                      },
                      labelLayout: {
                        x: this.myChart.getWidth() - 100,
                        // y: this.myChart.getHeight()/1.7 - (Math.random() * 40 - 60),
                        moveOverlap: 'shiftY'
                      },
                      // prettier-ignore
                      data: [[
                        1.262,
                        4.1255,
                        "a"
                      ],
                      [
                        3.8047,
                        3.5476,
                        "b"
                      ],
                      [
                        1.2933,
                        3.1948,
                        "c"
                      ]],
                    }
                  ],
                },
              });
            }, 1000); // 模拟网络延迟
          });
        },
    }
  3. 数据监听

    为了动态配置修改echarts的数据,采取VueX来实现数据的更新(频繁更新数据不调取接口,只有初始渲染和保存参数时才调用接口)

    js 复制代码
    watch: {
        // 改变颜色,监听的整个options
        volcanoOptionsState: {
          deep:true,
          handler(newOptions) {
            // console.log(newOptions,"newOptions");
            if (this.myChart) {
              // 动态刷新
              this.myChart.setOption(newOptions,true);
            }
          }
        },
        // 宽高
        volcanoDimensions:{
          deep:true,
          handler(newDimensions) {
            if (this.myChart) {
              const newWidth = this.convertRemToPx(newDimensions.width);
              const newHeight = this.convertRemToPx(newDimensions.height);
              this.myChart.resize({ width: newWidth, height: newHeight });
            }
          }
        },
    },
    computed: {
    	...mapState('echarts', ['volcanoOptionsState','volcanoDimensions','volcanoStyle']),
    },
    methods:{
        // 转换 rem 为 px,宽高前期没用百分比,所以涉及到单位转换
        convertRemToPx(rem) {
          const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize); // 获取 1rem 等于多少px
          if (rem.endsWith('rem')) {
            return parseFloat(rem) * remToPx;
          }
          return rem; // 如果传入的不是 rem,直接返回原值
        },
    }
  4. 数据修改

    以其中一个配置项为例

    vue 复制代码
    <div class="itemList">
      <div class="item">
        <span>宽</span>
        <el-input-number v-model="volcanoWidth" controls-position="right" :min="1"></el-input-number>
      </div>
      <div class="item">
        <span>高</span>
        <el-input-number v-model="volcanoHeight" controls-position="right" :min="1"></el-input-number>
      </div>
      <div class="item">
        <span>Down</span>
        <el-color-picker v-model="volcanoColorsDown" show-alpha></el-color-picker>
      </div>
      <div class="item">
        <span>Up</span>
        <el-color-picker v-model="volcanoColorsUp" show-alpha></el-color-picker>
      </div>
      <div class="item">
        <span>Nodiff</span>
        <el-color-picker v-model="volcanoColorsNodiff" show-alpha></el-color-picker>
      </div>
      <div class="item">
        <span>图表样式</span>
        <el-checkbox-group @change="changeVolcanoStyle" v-model="volcanoCheckbox">
          <el-checkbox label="显示边框"></el-checkbox>
          <el-checkbox label="散点光圈"></el-checkbox>
        </el-checkbox-group>
      </div>
      <div class="item">
        <span>散点大小</span>
        <el-input-number v-model="volcanoScatterSizeNum" controls-position="right"></el-input-number>
      </div>
      <div class="item">
        <span>x/y轴偏移</span>
        <div class="buttonGroup">
          <el-button @click="moveAxis('yAxis', 'decrease', 'min')">↑</el-button>
          <div>
            <el-button @click="moveAxis('xAxis', 'increase', 'max')">←</el-button>
            <el-button @click="moveAxis('xAxis', 'decrease', 'min')">→</el-button>
          </div>
          <el-button @click="moveAxis('yAxis', 'increase', 'max')">↓</el-button>
        </div>
      </div>
    </div>
    js 复制代码
    volcanoWidth:{
      get(){
        return parseFloat(this.volcanoDimensions.width)
      },
      set(newWidth){
        this.updateVolcanoWidth(newWidth);
      }
    },
    volcanoHeight:{
      get(){
        return parseFloat(this.volcanoDimensions.height)
      },
      set(newHeight){
        this.updateVolcanoHeight(newHeight);
      }
    },
    volcanoColorsDown:{
      get(){
        return this.volcanoColors.down
      },
      set(newColor){
        this.updateVolcanoColorDown({name: 'Down', newColor});
      }
    },
    volcanoColorsUp:{
      get(){
        return this.volcanoColors.up
      },
      set(newColor){
        this.updateVolcanoColorUp({name: 'Up', newColor});
      }
    },

    就是基础的通过Vuex去监听数据的变化,通过set响应式修改,更新options刷新echarts图表。

  5. 代码封装

    动态生成getter和setter,以此类推

    js 复制代码
    // 宽高配置
    ...['width', 'height'].reduce((acc, dimension) => {
      acc[`volcano${dimension.charAt(0).toUpperCase() + dimension.slice(1)}`] = {
        get() {
          return parseFloat(this.volcanoDimensions[dimension]);
        },
        set(newValue) {
          const currentValue = parseFloat(this.volcanoDimensions[dimension]);
          if (currentValue !== newValue) {
            this[`updateVolcano${dimension.charAt(0).toUpperCase() + dimension.slice(1)}`](newValue);
          }
        }
      };
      return acc;
    }, {}),
    
    // 对于 x 轴和 y 轴的处理
    ...['X', 'Y'].reduce((acc, axis) => {
        ['Title', 'TitleFontSize', 'TitleColor', 'LabelFontSize', 'LabelColor'].forEach(property => {
          const propName = `volcano${axis}${property}Value`;
          acc[propName] = {
            get() {
              return this[`volcano${axis}Axis${property}`];
            },
            set(newValue) {
              this[`updateVolcano${axis}${property}`](newValue); // 更新相应的 x/y 轴属性
            }
          };
        });
        return acc;
    }, {}),
  6. Echarts相关配置项对应

    • 标题
    js 复制代码
    title: {
        text:'火山图',    //名称
        left: 'center',     //居中/居左/居右
        textStyle:{
          fontFamily: 'Arial',
          fontSize: 18,
          color:'rgba(0,0,0, 1)'
        }
    },
    • 图表整体偏移
    js 复制代码
    grid: {
        left: '3%',    //相对偏移百分比
        right: '7%',
        bottom: '7%',
        containLabel: true,
      },
    • 鼠标移入显示信息
    js 复制代码
    tooltip: {
        // 鼠标移入显示参数,parmas为当前点的信息['x','y','自定义数据']
        formatter: function (params) {
          if (params.value.length > 1) {
            return (
              'X: ' + params.value[0] +
              '<br/>' +
              'Y: ' + params.value[1] +
              '<br/>' +
              'geneId: ' + params.value[2]
            );
          }
        },
      },
    • 图例
    js 复制代码
    legend: {
        data: ['Up', 'Down', 'Nodiff'],
        right: '10%',
        top: '20%',
        orient: 'vertical',
        // 给图例的原点点加了个边框
        // itemStyle:{
        //   borderWidth:1,
        //   borderColor:'black'
        // }
      },
    • x/y轴配置
    js 复制代码
    xAxis: 
    {
      type: 'value',
      scale: true,
      axisLabel: {
        formatter: '{value}',
        fontSize: 12,
        fontFamily: 'Arial',
        color: 'rgba(0,0,0, 1)',
      },
      min:'-25',
      max:'10',
      nameLocation:'middle',  //位置
      name:'x轴标题',
      nameGap: 30,  // 设置标题与轴的间隔
      nameTextStyle: { 
        fontFamily: 'Arial', 
        fontSize: 16,
        color: 'rgba(0,0,0, 1)',
      },
    },
    • series(以里面的一组数据为例)
    js 复制代码
    {
      name: 'Nodiff',
      color: 'rgba(192,192,192, 1)',
      type: 'scatter',   //否显示散点光圈,为effectScatter就是有,scatter就是纯点
      symbolSize:6,   //散点光圈大小,type为effectScatter时生效
      symbolSize:5, // 标记点的大小
      data:[
      	[1,2,'自定义数据',{'a','b'}]
      ],
      label: {
        show: true,
        formatter: function (param) {
          return param.value[2]; // 显示 '自定义数据'
        },
        position: 'left',
        minMargin: 2,
        fontSize: 12,
        fontFamily: 'Arial',
        fontStyle: 'normal',  // 正常样式normal;斜体样式italic
        color:'rgba(6, 6, 6, 1)', //标签字体颜色
      },
      
      labelLine: {
        show: true,
        lineStyle: {
          color: 'rgba(48, 49, 51, 1)',
          type: 'solid', //线条形状dashed/solid/dotted
        }
      },
      labelLayout: {
        x:this.myChart.getWidth() - 400,
        y:this.myChart.getHeight()/3,
        moveOverlap: 'shiftY'
      },
    }
相关推荐
低调函数29 分钟前
TypeScript和JavaScript的区别
javascript·ubuntu·typescript
中东大鹅1 小时前
【JavaScript】下拉框的实现
前端·javascript·css·html
Domain-zhuo1 小时前
什么是前端构建工具?比如(Vue2的webpack,Vue3的Vite)
前端·javascript·vue.js·webpack·node.js·vue·es6
yanmengying1 小时前
VUE脚手架练习
前端·javascript·vue.js
突然暴富的我1 小时前
html button 按钮单选且 高亮
前端·javascript·html
午后书香2 小时前
看两道关于异步的字节面试题...
前端·javascript·面试
心肝到爆2 小时前
vue3项目最新eslint9+prettier+husky+stylelint+vscode配置
前端·javascript·vue.js·git·前端框架·node.js
爱吃羊的老虎3 小时前
【WEB开发.js】addEventListener事件监听器的绑定和执行次数的问题(小心踩坑)
开发语言·前端·javascript
工业互联网专业4 小时前
Python毕业设计选题:基于Flask的医疗预约与诊断系统
python·flask·vue·毕业设计·源码·课程设计
LogicFlow4 小时前
👋一起来给流程图加便签吧,超级简单!
javascript·前端框架·开源