SOURCE

console 命令行工具 X clear

                    
>
console
/*  
1.点击开始后,不停地生成模拟数据
2.生成后可视化
//*/
var data, tstep = 0.01, ti=0, nxh=3;
var nstep = Math.floor(1/tstep);
const fourier = window.fourier;

function xfft2(array) {
    const N = array.length;

    // 如果数组长度为1,直接返回
    if (N === 1) {
        return array;
    }

    // 如果数组长度为偶数,使用分治FFT
    if (N % 2 === 0) {
        const even = [];
        const odd = [];
        for (let i = 0; i < N / 2; i++) {
            even[i] = array[2 * i];
            odd[i] = array[2 * i + 1];
        }

        const evenFFT = fft(even);
        const oddFFT = fft(odd);

        const t = [];
        for (let k = 0; k < N / 2; k++) {
            const tk = Math.cos(-2 * Math.PI * k / N) * oddFFT[k];
            const tk2 = Math.sin(-2 * Math.PI * k / N) * oddFFT[k];
            t[k] = tk;
            t[k + N / 2] = tk2;
        }

        const result = [];
        for (let k = 0; k < N / 2; k++) {
            result[k] = (evenFFT[k] + t[k]); //Math.abs是我自己加的!!!!!!!!!
            result[k + N / 2] = (evenFFT[k] - t[k]);
        }

        return result;
    }

    // 如果数组长度为奇数,使用混合基数FFT
    // 注意:对于长度为1000的数组,这一步实际上不会执行,因为1000是偶数
    // 但对于其他奇数长度,你需要实现一个适用于奇数的FFT变种

    // 这里省略了混合基数FFT的实现,因为它更复杂且对于长度为1000的数组不适用

    throw new Error('FFT not implemented for odd length arrays.');
}
function col(d, i) {
    //返回d数组中的第i列
    var tpd = []
    for (k in d) {
        tpd.push(d[k][i])
    }
    return tpd
}
function xor0(v) {
    return v > 0 ? v : 0
}
function xsx(d){
    //将实数数组d分成两个数组,实数s,虚数x, 模a,只返回长度N的一半
    var s=[],x=[],a=[];
    for(var i=0,iL=d.length/2;i<iL;i++){
        s.push(d[i].re);
        x.push(d[i].im);
        a.push((d[i].re**2+d[i].im**2)**0.5)
    }
    return [s,x,a]
}
function xser(n){
    var tp = []
    for(var i=0;i<n;i++){
        tp.push(i)
    }
    return tp
}
function getMaxIndices(d, n) {
    //查找一维数组d中最大的n个数字的序号
    // 创建包含元素值和原始索引的对象数组(解决排序后丢失索引的问题)[1,4](@ref)
    const indexedData = d.map((value, index) => ({ value, index }));
    // 降序排序:使用快速排序原理(V8引擎的实际实现)[4,5](@ref)
    indexedData.sort((a, b) => b.value - a.value);
    // 提取前n个元素的原始索引[7,8](@ref)
    return indexedData.slice(0, n).map(item => item.index);
}
function ks(me) {//开始按钮
    if (me.value == "开始") {
        data = [];
        for (var i = 0, iL = 1024*nxh; i < iL; i++) {
            data.push([i * tstep, Math.sin(i/10)*1+Math.sin(i/7)*1+Math.random()*9]);
        }
        myChart.setOption({
            //dataset: { source: data },
            series: {
                id: 0,
                data: data
            }
        })
        me.value = "结束";
        me.tp = setInterval(function(){
            ti+=nstep;
            if(ti>data.length){ ti -= data.length}
            var tiend = ti + 1024, tpdata;
            if( tiend>data.length){
                tiend = 1024-(data.length-ti);
                tpdata = [...data.slice(ti,data.length),...data.slice(0,tiend)]
            }else{
                tpdata = data.slice(ti,tiend)
            }
            /*
            // 创建实数张量
            var realData = tf.tensor1d(tpdata, 'float32');
            // 转换为复数(虚部设为0)
            var complexInput = tf.complex(realData, tf.zerosLike(realData));// 一维 FFT
            const fftResult = tf.signal.fft(complexInput);
            const spectrumArray = fftResult.arraySync(); // 转换为 JavaScript 数组
            //*/

            //tpdata = fourier.fft(tpdata);
            
            ///*
            var sx = xsx(xFFT(col(tpdata,1)))
            sx[0][0]=0; sx[1][0]=0; sx[2][0]=0;
            console.log(getMaxIndices(sx[2].slice(0,512),2))
            //*/
            myChart.setOption({
                series:[
                    {id: 1,data: sx[2].slice(0,112)},
                    //{id:1,data: tpdata}
                ]
            })
        },1000);
    } else {
        //if(debug)
        clearInterval(me.tp);
        me.value = "开始";
    }
}

option = {
    //dataset: { source: data },
    xAxis: [{
        id: 0,
        name: 'time',
        //type: 'scatter',
    }, {
        id: 1,
        gridIndex: 1,
        type:'category',
        name: 'q',
    }],
    yAxis: [{
        name: 'a',
        type: 'value',
        scale: false
    }, {
        gridIndex: 1,
        name: 'log10(f)',
        type: 'value',
        scale: false,
        //max: 3,
        min: 0
    }],
    grid: [
        { bottom: '60%' },
        { top: '60%' },
    ],/*
    tooltip:{
        trigger: 'axis',
    },//*/
    dataZoom: {},
    series: [{
        id: 0,
        xAxisIndex: 0,
        yAxisIndex: 0,
        type: 'line',
        data: null,
        encode: { x: 0, y: 1 },
        symbol: 'none',
    }, {
        id: 1,
        xAxisIndex: 1,
        yAxisIndex: 1,
        type: 'line',
        data: null,
        clip: true,
        //symbol:'none',
    }, {
        id: 2,
        xAxisIndex: 1,
        yAxisIndex: 1,
        type: 'line',
        data: null,
        clip: true,
        //symbol:'none',
    }]
};
var myChart = echarts.init(document.getElementById('main'));
myChart.setOption(option);
<div id="main"></div>
<div id="ctrl">
	<input type="button" onclick="ks(this)" id="bt" value="开始"></input>
	<input type="button" value="导出"></input>
</div>
<script>
class Complex {
    constructor(re = 0, im = 0) {
        this.re = re;
        this.im = im;
    }

    add(other) {
        return new Complex(this.re + other.re, this.im + other.im);
    }

    sub(other) {
        return new Complex(this.re - other.re, this.im - other.im);
    }

    mul(other) {
        return new Complex(
            this.re * other.re - this.im * other.im,
            this.re * other.im + this.im * other.re
        );
    }

    scale(scalar) {
        return new Complex(this.re * scalar, this.im * scalar);
    }

    conjugate() {
        return new Complex(this.re, -this.im);
    }
}

function xFFT(input, isInverse = false) {
    // 处理实数输入自动补虚部
    const complexInput = input.map(n => 
        n instanceof Complex ? n : new Complex(n, 0)
    );
    
    const N = complexInput.length;
    const log2N = Math.log2(N);
    
    // 自动补零到最近的2^N长度
    if (!Number.isInteger(log2N)) {
        const nextPower = Math.ceil(log2N);
        const paddedLength = Math.pow(2, nextPower);
        return FFT([...complexInput, ...Array(paddedLength - N).fill(new Complex())]);
    }

    // 位逆序置换[1,3](@ref)
    const reversed = Array(N).fill().map((_, i) => 
        parseInt(i.toString(2).padStart(log2N, '0').split('').reverse().join(''), 2)
    );
    
    const arr = reversed.map(i => complexInput[i]);
    
    // Cooley-Tukey迭代实现[2,3](@ref)
    for (let s = 1; s <= log2N; s++) {
        const m = 1 << s;
        const angle = (isInverse ? 2 : -2) * Math.PI / m;
        const wm = new Complex(Math.cos(angle), Math.sin(angle));
        
        for (let k = 0; k < N; k += m) {
            let w = new Complex(1, 0);
            for (let j = 0; j < m/2; j++) {
                const t = w.mul(arr[k + j + m/2]);
                const u = arr[k + j];
                arr[k + j] = u.add(t);
                arr[k + j + m/2] = u.sub(t);
                w = w.mul(wm);
            }
        }
    }

    // 逆变换归一化[5](@ref)
    return isInverse ? arr.map(x => x.scale(1/N)) : arr;
}

// 示例用法
//const signal = [0, 1, 0, -1, 0, 1, 0, -1]; // 8点正弦波
//const spectrum = FFT(signal);
//console.log(spectrum);
</script>
html,body {
    width: 100%;
    height: 94%;
    margin: 3% 0 0 0;
    padding: 0;
}

#main {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
}

#ctrl {
    position: absolute;
    z-index: 999;
    left: 10px;
    top: 10px;
}

本项目引用的自定义外部资源