-
创建DOM元素
vue"><div><div ref="chat" :style="{width: volcanoDimensions.width,height: volcanoDimensions.height,}" class="chat"></div></div>
-
初始化图表
需要实现,被筛选出的数据,带有
label,labelLayout,labelLine
,原定是直接在series
的三组数据中,直接给出三者的相关配置: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值。但是并没有起作用。label : {show: function (param) {// 仅为目标点显示标签线const specialPoints = ["a", "b"];return specialPoints.includes(param.value[2]);}, }
最后只能通过,筛选匹配数据,根据
name
往series里面push
两组新数据作为额外两组数据,单独显示label
。如果是饼图,这种
series
里面只有一组数据,单独配置的话:直接通过遍历里面的数据,单独添加
label
和labelLine
属性会更方便,两种方法均可行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'] = labelitem['labelLine'] = labelLine} })
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-ignoredata: [[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-ignoredata: [[-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;斜体样式italiccolor:'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-ignoredata: [[-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;斜体样式italiccolor:'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-ignoredata: [[1.262,4.1255,"a"],[3.8047,3.5476,"b"],[1.2933,3.1948,"c"]],}],},});}, 1000); // 模拟网络延迟});}, }
-
数据监听
为了动态配置修改echarts的数据,采取
VueX
来实现数据的更新(频繁更新数据不调取接口,只有初始渲染和保存参数时才调用接口)watch: {// 改变颜色,监听的整个optionsvolcanoOptionsState: {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 等于多少pxif (rem.endsWith('rem')) {return parseFloat(rem) * remToPx;}return rem; // 如果传入的不是 rem,直接返回原值}, }
-
数据修改
以其中一个配置项为例
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>
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图表。 -
代码封装
动态生成getter和setter,以此类推
// 宽高配置 ...['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; }, {}),
-
Echarts相关配置项对应
- 标题
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: {// 鼠标移入显示参数,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]);}},},
- 图例
legend: {data: ['Up', 'Down', 'Nodiff'],right: '10%',top: '20%',orient: 'vertical',// 给图例的原点点加了个边框// itemStyle:{// borderWidth:1,// borderColor:'black'// }},
- x/y轴配置
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(以里面的一组数据为例)
{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;斜体样式italiccolor:'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'}, }