You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForms Chart水平网格单元格着色:将音频频率图表改为钢琴卷帘样式

把WinForms Chart改成钢琴卷帘风格的实现方案

当然可以!把普通的音频频率图表改成钢琴卷帘样式,核心就是贴合钢琴卷帘的视觉逻辑——垂直方向对应音高(频率)、水平方向对应时间帧,用黑白键分区+高亮有数据的音块来提升可读性。下面是具体的实现步骤:

1. 调整Chart基础布局,对齐钢琴卷帘逻辑

首先把Chart的基础配置改成适合钢琴卷帘的模式:

  • 将Series的ChartType设为Column(柱状图),并把PointWidth设为1,让柱子紧密填满每个X轴单元格,形成连续的块:
    chart1.Series[0].ChartType = SeriesChartType.Column;
    chart1.Series[0]["PointWidth"] = "1";
    
  • Y轴用对数刻度(因为钢琴音高是按八度对数分布的),手动设置范围覆盖88键钢琴的频率区间(27.5Hz到4186Hz):
    var chartArea = chart1.ChartAreas[0];
    chartArea.AxisY.IsLogarithmic = true;
    chartArea.AxisY.Minimum = 27.5; // 最低音A0
    chartArea.AxisY.Maximum = 4186; // 最高音C8
    chartArea.AxisY.MajorGrid.Enabled = false; // 隐藏网格线,更像钢琴键
    
  • X轴对应音频帧/时间,隐藏多余的刻度和标签(如果不需要显示具体帧号):
    chartArea.AxisX.LabelStyle.Enabled = false;
    chartArea.AxisX.MajorTickMark.Enabled = false;
    

2. 绘制钢琴黑白键背景(关键步骤)

钢琴卷帘的核心视觉就是黑白键分区,我们可以通过Chart的Paint事件自定义绘制背景:

private void chart1_Paint(object sender, PaintEventArgs e)
{
    var chartArea = chart1.ChartAreas[0];
    // 提前预存88键钢琴的频率数组(可以用公式生成,比如A0=27.5Hz,每个半音乘2^(1/12))
    double[] pianoKeys = GeneratePianoFrequencies();

    foreach (double freq in pianoKeys)
    {
        // 把频率值转换为Chart控件上的Y坐标
        float yPixel = (float)chartArea.AxisY.ValueToPixelPosition(freq);
        // 计算当前键的高度(取下一个键的Y坐标差)
        int keyIndex = Array.IndexOf(pianoKeys, freq);
        float nextY = keyIndex < pianoKeys.Length - 1 
            ? (float)chartArea.AxisY.ValueToPixelPosition(pianoKeys[keyIndex + 1]) 
            : (float)chartArea.Position.Y;
        float keyHeight = yPixel - nextY;

        // 判断当前键是黑键还是白键(根据音名规则:C#/D#/F#/G#/A#是黑键)
        bool isBlackKey = CheckIfBlackKey(freq);

        // 绘制键的背景
        Brush keyBrush = isBlackKey ? Brushes.DimGray : Brushes.LightGray;
        e.Graphics.FillRectangle(keyBrush, 
            chartArea.Position.X, nextY, 
            chartArea.Position.Width, keyHeight);
    }
}

需要两个辅助方法:

  • GeneratePianoFrequencies():用公式生成88键的频率数组(A0=27.5,每个半音频率=前一个×2^(1/12))
  • CheckIfBlackKey():根据频率计算对应的音名,判断是否为黑键

3. 绑定音频频率数据,高亮激活的音块

把音频的每帧频率数据映射到对应的钢琴键,然后添加到Chart的Series中:

// 假设audioFrames是你的音频帧数据,每个帧包含多个检测到的频率
for (int frameIndex = 0; frameIndex < audioFrames.Length; frameIndex++)
{
    var detectedFrequencies = audioFrames[frameIndex];
    foreach (double freq in detectedFrequencies)
    {
        // 找到最接近的钢琴键频率
        double closestKey = FindClosestPianoKey(freq, pianoKeys);
        int keyIndex = Array.IndexOf(pianoKeys, closestKey);

        // 添加数据点,设置高亮颜色
        chart1.Series[0].Points.AddXY(frameIndex, keyIndex);
        chart1.Series[0].Points.Last().Color = Color.DodgerBlue; // 高亮激活的音
        chart1.Series[0].Points.Last().BorderWidth = 0; // 去掉柱子边框
    }
}

FindClosestPianoKey()方法负责把检测到的频率匹配到最接近的钢琴键频率,避免数据点错位。

4. 优化交互和细节

  • 启用缩放和滚动,方便查看长音频:
    chartArea.CursorX.IsUserEnabled = true;
    chartArea.CursorX.IsUserSelectionEnabled = true;
    chartArea.AxisX.ScaleView.Zoomable = true;
    chartArea.AxisX.ScrollBar.IsPositionedInside = true;
    
  • 添加ToolTip,鼠标悬停时显示音名和频率:
    private void chart1_MouseMove(object sender, MouseEventArgs e)
    {
        var hitResult = chart1.HitTest(e.X, e.Y);
        if (hitResult.PointIndex >= 0)
        {
            var point = hitResult.Series.Points[hitResult.PointIndex];
            int keyIndex = (int)point.YValues[0];
            double freq = pianoKeys[keyIndex];
            string noteName = GetNoteName(freq); // 转换频率为音名(比如C4)
            toolTip1.SetToolTip(chart1, $"{noteName} | {freq:F1} Hz");
        }
    }
    

这样调整后,你的Chart就会完全接近钢琴卷帘的样式,可读性会大大提升!

内容的提问来源于stack exchange,提问作者porras

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 14:54:13