首页 前端知识 在vue中利用echarts colormap canvas实现频谱瀑布图

在vue中利用echarts colormap canvas实现频谱瀑布图

2024-05-24 08:05:38 前端知识 前端哥 835 161 我要收藏

宁外一篇文章基础上进行封装优化,在vue中利用highcharts+colormap+canvas实现频谱瀑布图

介绍一下基础引用 colormap+echarts

封装频谱图

<!--
 * @FilePath: \systemmonitor\view\src\components\common\Spectrum.vue
 * @Description: 频谱图
-->
<template>
        <div id="Frequency" class=" h100 w100"></div>  
</template>
<script>
import { ToFixedVAl, findLabel, findValue, sizereverse, freqReverse, kHzReverse, HzReverse, customvalidateErrorStr } from '@/utils/util.js'
export default {
    data() {
        return {
            ToFixedVAl:ToFixedVAl,
            myChart: null, //频谱图
            //频谱变化Echart实例的数据项
            spectrumOption: {
                animation:false,
                title: {
                    show: false,
                    text: '频谱',
                    right: 10,
                    top: 10,
                    textStyle: {
                        color: '#BCBCBC',
                        fontSize: 12
                    }
                },
                tooltip: {
                    show: true,
                    trigger: 'axis',
                    padding: [4, 6, 4, 6],
                    textStyle: {
                        fontSize: 14
                    },
                    formatter: params => {
                        return '功率:' + ToFixedVAl(params[0].data[1], 4) + ' dBm' + '<br/>' + '频率:' + params[0].data[0] + ' MHz'
                    }
                },
                dataZoom: [
                    {
                        show: false,
                        type: 'slider',
                        filterMode: 'weakFilter',
                        showDataShadow: true,
                        bottom: 16,
                        height: 8,
                        startValue: 950,
                        endValue: 2150,
                        borderColor: 'transparent',
                        backgroundColor: '#e2e2e2',
                        handleIcon:
                            'path://M10.7,11.9H9.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7v-1.2h6.6z M13.3,22H6.7v-1.2h6.6z M13.3,19.6H6.7v-1.2h6.6z', // jshint ignore:line
                        handleSize: 20,
                        handleStyle: {
                            shadowBlur: 6,
                            shadowOffsetX: 1,
                            shadowOffsetY: 2,
                            shadowColor: '#aaa'
                        },
                        labelFormatter: ''
                    },
                    {
                        type: 'inside',
                        filterMode: 'weakFilter'
                    }
                ],
                grid: {
                    left: 20,
                    right: 30,
                    bottom: 6,
                    top: 15,
                    containLabel: true
                },
                xAxis: {
                    show: true,
                    // boundaryGap: true,
                    onZero: false,
                    // name: 'MHz',
                    nameTextStyle: {
                        fontSize: 10,
                        color: '#c1c2c6'
                    },
                    min: 950,
                    max: 2150,
                    minorTick: {
                        show: false,
                        lineStyle: {
                            color: '#797979'

                        }
                    },
                    minorSplitLine: {
                        show: false,
                        lineStyle: {
                            color: '#797979'

                        }
                    },
                    splitLine: {
                        show: true,
                        lineStyle: {
                            type: 'dashed',
                            color: '#797979'

                        }
                    },
                    axisTick: {
                        show: false
                    },
                    axisLabel: {
                        show: true,
                        textStyle: {
                            color: '#c1c2c6'
                        }
                    },
                    axisLine: {
                        show: true,
                        onZero: false,
                        lineStyle: {
                            type: 'solid',
                            color: '#797979'

                        }
                        // symbol: ['none', 'arrow'],
                        // symbolOffset: 10,
                        // symbolSize: [10, 10]
                    }
                },
                yAxis: {
                    // name: 'dBm',
                    nameTextStyle: {
                        fontSize: 10,
                        color: '#c1c2c6'
                    },
                    min: -120,
                    max: 0,
                    show: true,
                    splitLine: {
                        show: true,
                        lineStyle: {
                            type: 'dashed',
                            color: '#797979'

                        }
                    },
                    axisTick: {
                        show: false
                    },
                    axisLine: {
                        show: true,
                        lineStyle: {
                            type: 'solid',
                            color: '#797979'

                        }
                    },
                    axisLabel: {
                        show: true,
                        lineStyle: {
                            type: 'solid',
                            color: '#797979'
                        },
                        textStyle: {
                            color: this.$store.state.theme == 'lightTheme' ? '#606266' : '#c1c2c6',
                            fontSize: '12'
                        }
                    }

                    // axisLine: {
                    //     show:true,
                    //       lineStyle: {
                    //         type: 'solid',
                    //         color: '#797979'
                    //     },
                    //     // symbol: ['none', 'arrow'],
                    //     // symbolOffset: 10,
                    //     // symbolSize: [10, 10]
                    // }
                },
                series: [
                    {
                        type: 'line',
                        smooth: true,
                        showSymbol: false,
                        // tooltip: {
                        //     show: true,
                        //     trigger:'item',
                        //     formatter:(e)=>{
                        //      console.log(e)
                        //     }
                        // },
                        // clip: false,
                        // itemStyle: {
                        //     normal: {
                                lineStyle: {
                                    width: '1',
                                    color: '#56a641',
                                    type: 'solid' // dotted虚线  solid:实线
                                },
                        //     }
                        // },
                        emphasis: {
                            lineStyle: {
                                width: '1'
                            }
                        },
                        data: [
                            // [950, 0], [1000, 300], [1050, 0]
                        ],
                        markArea: {
                            tooltip: {
                                show: false
                            },
                            itemStyle: {
                                color: 'rgba(64,158,225,0.4)'
                            },
                            data: [
                                // [{ xAxis: 980 }, { xAxis: 1020 }], [{ xAxis: 990 }, { xAxis: 1010 }]
                            ]
                        },
                        markLine: {
                            symbol: 'none',
                            label: {
                                fontSize: 10,
                                color: '#ff8000',
                                position: 'insideMiddleTop',
                                // formatter: 'EBEM:{c}MHz'
                                formatter: params => {
                                    if (params.data.type == 'signal') {
                                        return  params.data.xAxis + ' MHz'
                                    } else {
                                        return ''
                                    }
                                }
                            },
                            lineStyle: {
                                color: '#ff8000',
                                width: 2,
                                type: 'dashed'
                            },
                            data: []
                        }
                    }
                ]
            },  
        }
    },
    mounted() {
        // 初始化echarts
        this.initCharts();
        //画布收缩同步监听
        this.resizedom(); 
    },
    methods: {
        // 更新series中data数据
        setSeries(data){
            this.spectrumOption.series[0].data = data;
            this.myChart.setOption({series:this.spectrumOption.series})
        },
        // 更新标记线和标记区域
        SetMark(areaData,lineData){
            this.spectrumOption.series[0].markArea.data = areaData;
            this.spectrumOption.series[0].markLine.data = lineData;
         // 仅更新series避免dataZoom重置问题
         this.myChart.setOption({series:this.spectrumOption.series})
        },
        // 仅更新线标记
        SetMarkLine(Arr){
            this.spectrumOption.series[0].markLine.data = Arr;
            // this.myChart.setOption(this.spectrumOption)
              // 仅更新series避免dataZoom重置问题
              this.myChart.setOption({series:this.spectrumOption.series})
        },
        // 重新设置频谱范围
        resetSpec(start,end,type){
               // 重新设置频谱范围
               this.spectrumOption.dataZoom[0].startValue = start;
                    this.spectrumOption.dataZoom[0].endValue = end;
                    this.spectrumOption.xAxis.min = start;
                    this.spectrumOption.xAxis.max =end;
                    if(type){
                        this.spectrumOption.series[0].data = [];
                    this.spectrumOption.series[0].markArea.data = [];
                    this.spectrumOption.series[0].markLine.data = [];
                    }
                    this.myChart.setOption(this.spectrumOption);
        },
          // 初始化频谱
          initCharts() {
            this.myChart = this.$echarts.init(document.getElementById('Frequency'))
            this.myChart.setOption(this.spectrumOption)
            let _this = this;
            this.myChart.on('datazoom', res => {
                //res里可获取滚动条当前起始未知start、end,二者皆为百分比
                //通过此方法直接获取到缩放后的横纵坐标最小值
                // _this.$refs.WaterFall.changeRange(_this.myChart.getOption().dataZoom[0].startValue,_this.myChart.getOption().dataZoom[0].endValue)
            _this.$emit('changeWaterRange',_this.myChart.getOption().dataZoom[0].startValue,_this.myChart.getOption().dataZoom[0].endValue )   
            });
           
        },
        // dom自适应
        resizedom() {
            this.resizeEcharts(document.getElementById('Frequency'), this.myChart)
        },
          // 监听dom变化
          resizeEcharts(echart, myChart) {
            // 这里的echart就是我们的dom元素指的是:
            // this.$refs.echart 或者 document.getElementById(“echart”)
            // 这里的myChart指的是初始化的echarts实例:
            // const myChart = echarts.init(this.$refs.echart)
            const elementResizeDetectorMaker = require('element-resize-detector') // 引入监听dom变化的组件
            const erd = elementResizeDetectorMaker()
            // 监听id为echart的元素 大小变化
            erd.listenTo(echart, function (element) {
                // const width = element.offsetWidth
                // const height = element.offsetHeight
                myChart.resize()
            })
        }
    },
}
</script>
<style scoped>
#Frequency {
    background: linear-gradient(180deg, #353535, #000000, #353535);
}
</style>

封装瀑布图

<!--
 * @FilePath: \systemmonitor\view\src\components\common\WaterFall.vue
 * @Description: 瀑布图
-->
<template>
        <div class="h100 w100 flex waterfallBox">
                    <!--图例-->
                    <div class="legend" ref="lengedContent">
                        <canvas ref="spectrogramLeftLegend"></canvas>
                    </div>
                    <!--瀑布图-->
                    <div class="waterFall" ref="waterFallContent">
                        <canvas ref="spectrogramDivInStation"></canvas>
                    </div>
                </div>
</template>
<script>
export default{
    data() {
        return {
            legendMaxNum: 0, //图例最大值
            legendMinNum: -120, //图例最小值
            colormap: [], //颜色库
            pectromgrameTempArray: [],  //瀑布图二维数组(用来显示数据做的临时存储)
            spectromgrameIntervalIndex: 0, //瀑布定时器用到的计数标识
            spectromgrameCtx: null, //瀑布图canvas二维绘图对象
            spectromgrameCanvas: null, //瀑布图
            spectromgrameGroupLength: 150,  //瀑布图纵向显示多少组数据
            curWindowAxis: {      //保存当前窗口可视范围的横坐标轴起始频点
                startFreq: 950,
                endFreq: 2150
            },
        }
    },
    mounted() {
         //创建颜色库
         this.setColormap(); 
        //创建canvas瀑布图画布
        this.isClickedSpectromgrame(); 
    },
    methods: {
        // 设置更改区间范围
        changeRange(start,end){
            this.$set(this.curWindowAxis,'startFreq',start)
            this.$set(this.curWindowAxis,'endFreq',end)        
        },
        // 清空瀑布图内容
        clearContent(){
            this.spectromgrameCtx.clearRect(0,0,this.spectromgrameCanvas.width,this.spectromgrameCanvas.width);
        },
          //创建颜色库
          setColormap() {
            let dx = this;
            let colormap = require('colormap');
            dx.colormap = colormap({
                colormap: 'jet',
                nshades: 150,
                format: 'rba',
                alpha: 1
            })
        },
        //创建瀑布图图例
        createLegendCanvas() {
            let dx = this
            let legendRefs = dx.$refs.spectrogramLeftLegend; //获取图例
            legendRefs.width = this.$refs.lengedContent.offsetWidth;
            legendRefs.height = this.$refs.lengedContent.offsetHeight;
            dx.spectrogramLegendCanvasContent = legendRefs.getContext('2d');
            let legendCanvas = document.createElement('canvas');
            legendCanvas.width = 1;
            let legendCanvasTemporary = legendCanvas.getContext('2d');
            const imageData = legendCanvasTemporary.createImageData(1, dx.colormap.length);
            for (let i = 0; i < dx.colormap.length; i++) {
                const color = dx.colormap[i]
                imageData.data[imageData.data.length - i * 4 + 0] = color[0]
                imageData.data[imageData.data.length - i * 4 + 1] = color[1]
                imageData.data[imageData.data.length - i * 4 + 2] = color[2]
                imageData.data[imageData.data.length - i * 4 + 3] = 255
            }
            legendCanvasTemporary.putImageData(imageData, 0, 0)
            dx.spectrogramLegendCanvasContent.drawImage(legendCanvasTemporary.canvas,
                0, 0, 1, dx.colormap.length, 0, 1, 30, legendRefs.height)
        },

        //创建瀑布图
        createSpectrogrameCanvas() {
            let _this = this;
            this.spectromgrameCanvas = this.$refs.spectrogramDivInStation;
            this.spectromgrameCtx = this.spectromgrameCanvas.getContext("2d");
            this.spectromgrameCanvas.width = this.$refs.waterFallContent.offsetWidth;
            this.spectromgrameCanvas.height = this.$refs.waterFallContent.offsetHeight;  //瀑布图div不包括外边距和边框
        },
        getRad: function (degree) {
            return degree / 180 * Math.PI;
        },
        //操作瀑布图选择框的回调方法
        isClickedSpectromgrame: function () {
            let _this = this;

            this.$nextTick(() => {
                _this.createLegendCanvas();  //绘制图例
                _this.createSpectrogrameCanvas(); //创建瀑布图
            })
        },
        //返回数据对应的Colormap颜色
        colorMapData: function (data, outMin, outMax) {
            let result;
            if (data <= this.legendMinNum) {
                result = outMin;
            } else if (data >= this.legendMaxNum) {
                result = outMax;
            } else {
                //Math.round四舍六入
                result = Math.round(((data - this.legendMinNum) / (this.legendMaxNum - this.legendMinNum)) * (outMax - outMin));
            }
            return result;
        },
        /** 
     * @desc: 绘制单次频谱对应的瀑布图像
     
     * @date: 2022/8/8
     **/
        drawRowToSpectromgrame: function (data) {
            // TODO:后续需要优化根据实际数据创建canvas像素图
            data = data.filter(item => item[0] >= this.curWindowAxis.startFreq && item[0] <= this.curWindowAxis.endFreq);
            let _this = this;
            this.$nextTick(() => {
                let allCanvasHeight = _this.spectromgrameCanvas.height; //绘制瀑布图的区域除开底部坐标轴
                let allCanvasWidth = _this.spectromgrameCanvas.width;
                let canvasHeight = Math.ceil(allCanvasHeight / _this.spectromgrameGroupLength); //瀑布图每行的高度
                let canvasWidth = Math.ceil(allCanvasWidth / data.length); //瀑布图,每个点占用的长度
                /**** 注意:getImageData 的参考坐标始终在左上角和变换坐标轴无任何关系,即时将坐标轴远点修改到左下角 ***/

                //第一块Mod板瀑布图
                if (_this.spectromgrameCtx != null) {
                    let freqXisLeftValue = this.curWindowAxis.startFreq; //开始频点转换为MHz(时频图仅根据当前视图窗口可见范围绘制)
                    let freqXisRightValue = this.curWindowAxis.endFreq; //结束频点转换为MHz(时频图仅根据当前视图窗口可见范围绘制)

                    //获取到最近一次绘制的瀑布图数据图像
                    //指定区域(矩形左顶点从画布左小角(0,00)开始,长度为画布长度获取整个画布区间的图像,每一次将旧的图像上移)
                    let imgOld = _this.spectromgrameCtx.getImageData(0, 0, allCanvasWidth, allCanvasHeight);
                    const imageData = _this.spectromgrameCtx.createImageData(allCanvasWidth, 1); //创建了一组(行)新的瀑布图(单次频谱数据)
                    let pointStartIndex = Math.round((data[0][0] - freqXisLeftValue) / (freqXisRightValue - freqXisLeftValue) * allCanvasWidth);

                    //当上报频谱第一个载波频率点起始位置不等于坐标轴左侧点,需要将空白地方按最低电平值上色
                    if (pointStartIndex > 1) {
                        for (let leftIndex = 0; leftIndex < pointStartIndex; leftIndex++) {
                            let cindex = _this.colorMapData(data[0][1], 0, 130);
                            let leftColor = _this.colormap[cindex];
                            for (let j = 0; j < canvasWidth; j++) {
                                imageData.data[(leftIndex) * 4 + 0 + 4 * j] = leftColor[0];
                                imageData.data[(leftIndex) * 4 + 1 + 4 * j] = leftColor[1];
                                imageData.data[(leftIndex) * 4 + 2 + 4 * j] = leftColor[2];
                                imageData.data[(leftIndex) * 4 + 3 + 4 * j] = 255;
                            }
                        }
                    }

                    //遍历瀑布图数据给当行数据瀑布图上色
                    for (let pointIndex = 0; pointIndex < data.length; pointIndex++) {

                        let imageItemX = Math.round((data[pointIndex][0] - freqXisLeftValue) / (freqXisRightValue - freqXisLeftValue) * allCanvasWidth);
                        let spectrogrumDataIndex = _this.colorMapData(data[pointIndex][1], 0, 130);
                        let color = _this.colormap[spectrogrumDataIndex];
                        if (imageItemX == 0) {
                            imageItemX = imageItemX + 1;
                        }
                        for (let j = 0; j < canvasWidth; j++) {
                            imageData.data[(imageItemX - 1) * 4 + 0 + 4 * j] = color[0];
                            imageData.data[(imageItemX - 1) * 4 + 1 + 4 * j] = color[1];
                            imageData.data[(imageItemX - 1) * 4 + 2 + 4 * j] = color[2];
                            imageData.data[(imageItemX - 1) * 4 + 3 + 4 * j] = 255;
                        }

                    }

                    //当上报FFT最后一个点不等于坐标轴右侧最大值,需要将空白地方按最低电平值上色
                    if(data.slice(-1)[0][0] < this.curWindowAxis.endFreq) {
                        let lastItemX = Math.round((data.slice(-1)[0][0]-freqXisLeftValue) /(freqXisRightValue - freqXisLeftValue) * allCanvasWidth);
                        for(let curPointIndex = lastItemX; curPointIndex*4<imageData.data.length; curPointIndex++) {
                            let colorIndex = _this.colorMapData(data.slice(-1)[0][1],0,130);
                            var lastSegColor = _this.colormap[colorIndex];
                            for (let j = 0; j < canvasWidth; j++) {
                            imageData.data[(curPointIndex)*4 + 0+ 4 * j] = lastSegColor[0];
                            imageData.data[(curPointIndex)*4 + 1+ 4 * j] = lastSegColor[1];
                            imageData.data[(curPointIndex)*4 + 2+ 4 * j] = lastSegColor[2];
                            imageData.data[(curPointIndex)*4 + 3+ 4 * j] =255;
                          }
                        }
                    }

                    //这里需要for循环绘制很多次的原因是因为,imageData单行像素的高度为1像素,而实际我们需要显示的>1,因此每一次的频谱数据要绘制多次
                    for (let i = 0; i < canvasHeight; i++) {
                        /**putImageData(imageData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHight); 
                         * 参数已有依次为:
                         * 1.将要放置的画布对象;
                         * 2.对象左上角的x坐标;
                         * 3.对象左上角的y坐标;
                         * 4.(可选)以像素统计,在画布上放置图像的位置;
                         * 5.(可选)以像素计,画布上放置图像的位置Y;
                         * 6.放置图像的宽度
                         * 7.放置图像的高度
                        */
                        _this.spectromgrameCtx.putImageData(imageData, 0, allCanvasHeight - 1 - i);
                    }
                    _this.spectromgrameCtx.putImageData(imgOld, 0, -canvasHeight);  //将上一次canvas整个画布区域的频谱数据像素上移
                }
            })
        },
    },
}
</script>

<style scoped>
.legend {
    width: 46px;
    height: 100%;
    overflow: hidden;
    text-align: right;
}

.legend canvas {
    width: 30px;
    height: 100%;
    margin-left: 8px;
}

.waterFall {
    width: calc(100% - 55px);
    height: 100%;
    position: relative;
}

.waterFall canvas {
    width: calc(100% - 30px);
    height: 100%;
}
.waterfallBox {
    background: linear-gradient(180deg, #353535, #000000, #1f1f1f);
}
</style>

引用

<template>
 <div class="w100 h100">
   <div class="waterBox w100">
                <!-- 瀑布图 -->
              <WaterFall ref="WaterFall"/>
            </div>
            <div class="freq w100">
                 <!-- 频谱 -->
                 <Spectrum ref="Spectrum"  @changeWaterRange="changeWaterRange" />
            </div>
             </div>
</template>
<script>
import WaterFall from '../../common/WaterFall.vue';
import Spectrum from '../../common/Spectrum.vue';
export default {
   components:{
    WaterFall,
    Spectrum
   },
   mounted(){
   this.initMessage()
   },
   methods:{
    //举例说明传递参数调用方法
    getMessage(data){
       // var seriesData=[[950, -20],[951, -30],[952, -40],...];
     // 仅更新series避免dataZoom重置问题
      this.$refs.Spectrum.setSeries(data)
     //渲染瀑布图
     this.$refs.WaterFall.drawRowToSpectromgrame(data);
    },
      // 频谱缩放时需要更新瀑布图范围  这个地方主要是频谱开启缩放 瀑布图范围需要跟着更新  所以需要借助父元素调用瀑布图中的方法
       changeWaterRange(start,end){
        this.$refs.WaterFall.changeRange(start,end)
        },
        //重新设置瀑布图和频谱范围   
        reserveRange(){
             // 重新设置瀑布图和频谱范围
             this.$refs.WaterFall.changeRange(950,2150)
             this.$refs.Spectrum.resetSpec(950,2150,true)
         // 清空瀑布图内容
          this.$refs.WaterFall.clearContent();
        },
        initMessage(){
       var pin=this.RandomNumBoth(40,60);
        var data=[];
        for(var i =950;i<2150;i+=0.3){
          if(i>=1200&&i<=1230){
             let nums=this.RandomNumBoth(60,70);
            data.push([i,nums])
           }else if(i>=1450&&i<=1500){
             let nums=this.RandomNumBoth(50,60);
            yData.push(nums) 
            data.push([i,nums])
           }else if(i>=1800&&i<=1850){
             let num1=this.RandomNumBoth(40,50);
           
            data.push([i,num1])
           }else{
              let num=this.RandomNumBoth(2,30);
              
                data.push([i,num])
           }
        }
       this.getMessage(data)
      },
      // 生成范围区间的值
        RandomNumBoth(Min, Max) {
            var Range = Max - Min;
            var Rand = Math.random();
            var num = Min + Math.round(Rand * Range); //四舍五入
            return num;
        },
   }
}
   <script>
   <style>
   .freq {
    height: 27.5vh;
}
.waterBox{
    height: 15vh;
}
#Frequency {
    background: linear-gradient(180deg, #353535, #000000, #353535);
}
.w100{
    width:100vw;
}
.h100{
    height:100vh;
}
   </style>
转载请注明出处或者链接地址:https://www.qianduange.cn//article/9331.html
标签
评论
发布的文章

JQuery中的load()、$

2024-05-10 08:05:15

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!