欢迎光临
我们一直在努力

优化后的 DrawChannels 方法中,显示了三条额外的线(可能指零线、中线和边距线),这些辅助线用于调试居中和边距效果

优化后的 DrawChannels 方法中,显示了三条额外的线(可能指零线、中线和边距线),这些辅助线用于调试居中和边距效果,但可能影响视觉美观,需要删除。此外,您希望继续优化代码,确保曲线居中、不填满格子、显示美观,并支持负值数据。结合之前的优化(修复 CS0266 错误、增加 15% 边距、动态余量等),我将:

  • 定位并删除三条额外线:分析 DrawChannels 中添加的辅助线(零线、画布中线、边距线),删除它们。
  • 保持居中和美观:确保曲线在画布中央 70% 区域,上下留 15% 空白,零线(Y=0)接近中线。
  • 支持负值和对数模式:确保负值点正确显示,对数模式下跳过负值点。
  • 优化代码:精简逻辑,增强日志,保持稳定性。
  • 以下我将分析 DrawChannels 中添加的线,删除多余部分,提供优化代码,并确保曲线完美显示。代码和解释以中文清晰呈现。


    一、问题分析

    1. 三条额外线的来源在上一版本的 DrawChannels 方法中,为调试居中和边距效果,添加了以下三条辅助线:

    // 绘制零线(Y=0)
    if (OscilloscopeProps.VerticalMin < 0 && OscilloscopeProps.VerticalMax > 0)
    {
    double rangeY = OscilloscopeProps.VerticalMax – OscilloscopeProps.VerticalMin;
    float zeroY = rangeY > 0
    ? drawTop + drawHeight – (float)((0 – OscilloscopeProps.VerticalMin) / (rangeY / drawHeight))
    : drawTop + drawHeight / 2;
    g.DrawLine(Pens.Gray, left, zeroY, left + h_size, zeroY);
    m_Logger.Info($"Zero line at Y={zeroY}, Canvas middle={top + v_size / 2}, Draw area=[{drawTop}, {drawTop + drawHeight}]");
    }

    // 绘制画布中线和边距线(调试用)
    g.DrawLine(Pens.Blue, left, top + v_size / 2, left + h_size, top + v_size / 2);
    g.DrawLine(Pens.Green, left, drawTop, left + h_size, drawTop); // 上边距
    g.DrawLine(Pens.Green, left, drawTop + drawHeight, left + h_size, drawTop + drawHeight); // 下边距

    • 零线(灰色):绘制 Y=0 的水平线,验证零点位置是否接近画布中线(top + v_size / 2)。
    • 画布中线(蓝色):绘制画布垂直中心线(top + v_size / 2),检查曲线居中效果。
    • 边距线(绿色):绘制绘制区域的上边界(drawTop)和下边界(drawTop + drawHeight),验证 15% 边距。

    问题:

    • 这三条线仅用于调试,但在生产环境中可能干扰视觉效果,导致曲线显示不美观。
    • 您要求删除这些线,保留核心绘制逻辑。

    2. 其他优化需求

    • 居中显示:曲线在画布中央 70% 区域,上下留 15% 空白,零线接近中线。
    • 不填满格子:确保曲线不贴近顶部(drawTop)或底部(drawTop + drawHeight)。
    • 负值支持:负值点正确映射到中央区域。
    • 美观性:动态余量(20%-40%)确保曲线分布均匀。
    • 稳定性:保留日志,增强异常处理。

    二、优化思路

  • 删除三条辅助线:
    • 移除 DrawChannels 中绘制零线(灰色)、中线(蓝色)和边距线(绿色)的代码。
    • 保留零线位置的日志记录(m_Logger.Info),用于调试但不影响显示。
  • 保持居中和边距:
    • 保留 15% 垂直边距(marginV = v_size * 0.15f),绘制区域为 70%(drawHeight = v_size * 0.7f)。
    • 确保 AutoScale 的范围计算使零点(Y=0)接近画布中线。
  • 优化视觉效果:
    • 动态余量(20%-40%)在 AutoScale 中保持,确保曲线不贴边。
    • 优化重采样,保留零点和极值,增强曲线平滑性。
  • 支持负值和对数模式:
    • 负值点映射到中央区域,对数模式下跳过负值点,必要时切换到线性模式。
  • 稳定性与调试:
    • 保留详细日志(范围、点坐标),便于排查问题。
    • 确保异常处理设置默认范围,防止曲线消失。

  • 三、改进代码以下是优化的 DrawChannels 方法,删除三条辅助线,同时保留 AutoScale 和 GetLegendBoxRec 方法(上一版本已足够优化)。

    确保曲线居中、美观、不填满格子。

    1. DrawChannels 方法优化目标:

    • 删除零线、中线和边距线。
    • 保留 15% 边距,曲线在中央 70% 区域。
    • 保持日志记录,验证居中效果。

    改进代码:

    private void DrawChannels(Graphics g, RectangleF drawingRec)
    {
    try
    {
    m_OutofDrawingRange = false;
    if (Channels.Count == 0)
    {
    m_OutofDrawingRange = true;
    return;
    }

    RectangleF legendBox = GetLegendBoxRec(g, drawingRec);
    float h_size = drawingRec.Width – OscilloscopeProps.Frame – m_LeftLabelFrame;
    float v_size = drawingRec.Height – OscilloscopeProps.Frame – m_BottomLabelFrame;
    if (ShowLegend && !legendBox.IsEmpty)
    {
    h_size -= legendBox.Width;
    }
    float left = Convert.ToSingle(drawingRec.Left + OscilloscopeProps.Frame / 2 + m_LeftLabelFrame);
    float top = Convert.ToSingle(drawingRec.Top + OscilloscopeProps.Frame / 2);
    // 预留 15% 垂直边距
    float marginV = v_size * 0.15f;
    float drawHeight = v_size * 0.7f; // 中央 70% 区域
    float drawTop = top + marginV;
    int nx = (int)((OscilloscopeProps.HorizonMax – OscilloscopeProps.HorizonMin) / OscilloscopeProps.HorizonStep);
    int ny = (int)((OscilloscopeProps.VerticalMax – OscilloscopeProps.VerticalMin) / OscilloscopeProps.VerticalStep);
    OscilloscopeProps.CellWidth = Math.Max(h_size / nx, 0.001f);
    OscilloscopeProps.CellHeight = Math.Max(drawHeight / ny, 0.001f);
    int count = 0;

    // 计算零线位置(仅用于日志)
    if (OscilloscopeProps.VerticalMin < 0 && OscilloscopeProps.VerticalMax > 0)
    {
    double rangeY = OscilloscopeProps.VerticalMax – OscilloscopeProps.VerticalMin;
    float zeroY = rangeY > 0
    ? drawTop + drawHeight – (float)((0 – OscilloscopeProps.VerticalMin) / (rangeY / drawHeight))
    : drawTop + drawHeight / 2;
    m_Logger.Info($"Zero line calculated at Y={zeroY}, Canvas middle={top + v_size / 2}, Draw area=[{drawTop}, {drawTop + drawHeight}]");
    }

    foreach (Channel c in Channels)
    {
    if (!c.ChannelProps.Visible) continue;
    if (c.XValues.Count == 0 || c.YValues.Count == 0)
    {
    m_Logger.Warn($"Channel {c.ChannelProps.Legend} has no data, skipping");
    continue;
    }

    Color cr = c.ChannelProps.LineColor;
    float lw = c.ChannelProps.LineWidth;
    if (c.ChannelProps.Activated) { cr = OscilloscopeProps.ActiveColor; lw = 4 * lw; }
    double rangeY = c.ChannelProps.VerticalMax – c.ChannelProps.VerticalMin;
    double scaleY = rangeY > 0 ? drawHeight / rangeY : 1;
    double scaleX = c.ChannelProps.HorizonStep / OscilloscopeProps.CellWidth;
    scaleX = scaleX > 0 ? scaleX : 1;
    c.CoordValues.Clear();
    count += c.XValues.Count;
    Pen pp = new Pen(cr, lw);
    Brush cc = new SolidBrush(cr);
    bool xlog = c.ChannelProps.XAxisDisplayMode == AxisDisplayMode.Log;
    bool ylog = c.ChannelProps.YAxisDisplayMode == AxisDisplayMode.Log;

    // 重采样,保留极值和零点
    List<double> xValues = c.XValues;
    List<double> yValues = c.YValues;
    if (c.XValues.Count > ReSampleCount)
    {
    xValues = new List<double>();
    yValues = new List<double>();
    int step = c.XValues.Count / ReSampleCount;
    double minY = c.YValues.Min(), maxY = c.YValues.Max();
    for (int j = 0; j < c.XValues.Count; j += step)
    {
    xValues.Add(c.XValues[j]);
    yValues.Add(c.YValues[j]);
    if (c.YValues[j] == minY || c.YValues[j] == maxY || c.YValues[j] == 0)
    {
    xValues.Add(c.XValues[j]);
    yValues.Add(c.YValues[j]);
    }
    }
    }

    switch (c.ChannelProps.ChartMode)
    {
    case DrawingMode.XYLines:
    int i = 0;
    List<PointF> points = new List<PointF>();
    while (i < xValues.Count)
    {
    if (xlog && xValues[i] <= 0 || ylog && yValues[i] <= 0)
    {
    m_Logger.Warn($"Skipping point at index {i} due to invalid log value (x={xValues[i]}, y={yValues[i]})");
    if (points.Count > 1) g.DrawLines(pp, points.ToArray());
    points.Clear();
    i++;
    continue;
    }
    double x = xlog ? Math.Log10(Math.Abs(xValues[i] * c.XUnitFactor)) : xValues[i] * c.XUnitFactor;
    double y = ylog ? Math.Log10(Math.Abs(yValues[i] * c.YUnitFactor)) : yValues[i] * c.YUnitFactor;
    PointF pt = new PointF
    {
    X = Convert.ToSingle(left + (x – c.ChannelProps.HorizonMin + c.ChannelProps.XOffset) / scaleX),
    Y = Convert.ToSingle(drawTop + drawHeight – (y – c.ChannelProps.VerticalMin) / (rangeY / drawHeight))
    };
    // 截断到绘制区域
    pt.Y = Math.Max(drawTop, Math.Min(drawTop + drawHeight, pt.Y));
    pt.X = Math.Max(left, Math.Min(left + h_size, pt.X));
    c.CoordValues.Add(pt);
    points.Add(pt);
    m_Logger.Debug($"Point {i}: X={pt.X}, Y={pt.Y}, DataY={y}, InDrawArea={pt.Y >= drawTop && pt.Y <= drawTop + drawHeight}");
    i++;
    }
    if (points.Count > 1) g.DrawLines(pp, points.ToArray());
    break;
    // … 其他模式
    }
    pp.Dispose();
    cc.Dispose();
    }
    m_OutofDrawingRange = count == 0;
    }
    catch (Exception ex)
    {
    m_Logger.Error($"DrawChannels error: {ex.Message}, StackTrace: {ex.StackTrace}");
    }
    }

    改进点:

  • 删除三条线:
    • 移除以下代码:csharp

      g.DrawLine(Pens.Gray, left, zeroY, left + h_size, zeroY); // 零线
      g.DrawLine(Pens.Blue, left, top + v_size / 2, left + h_size, top + v_size / 2); // 中线
      g.DrawLine(Pens.Green, left, drawTop, left + h_size, drawTop); // 上边距
      g.DrawLine(Pens.Green, left, drawTop + drawHeight, left + h_size, drawTop + drawHeight); // 下边距

    • 保留 zeroY 计算和日志,验证居中效果。
  • 保持边距:
    • 15% 垂直边距(marginV = v_size * 0.15f),绘制区域为 70%(drawHeight = v_size * 0.7f)。
  • 日志:
    • 保留零线位置和点坐标日志,方便调试。
  • Y坐标公式:
    • 保持 Y = drawTop + drawHeight – (y – VerticalMin) / (rangeY / drawHeight),确保数值增大向下。
  • 2. AutoScale 方法(保持不变)

    • 上一版本(2025年7月2日)的 AutoScale 已优化,动态余量(20%-40%)确保曲线不贴边,零点居中,支持负值和对数模式,无需修改。
    • 代码如下(仅供参考,确保上下文一致):

    public void AutoScale()
    {
    try
    {
    if (Channels.Count == 0)
    {
    m_Logger.Warn("No channels available for AutoScale");
    OscilloscopeProps.VerticalMax = 10;
    OscilloscopeProps.VerticalMin = -10;
    OscilloscopeProps.HorizonMax = 10;
    OscilloscopeProps.HorizonMin = 0;
    OscilloscopeProps.VerticalStep = 2;
    OscilloscopeProps.HorizonStep = 1;
    Unit = 1;
    canvas.Invalidate();
    return;
    }

    Channel activeChan = null;
    double globalMaxY = double.MinValue, globalMinY = double.MaxValue;
    double globalMaxX = double.MinValue, globalMinX = double.MaxValue;

    for (int i = 0; i < Channels.Count; i++)
    {
    var c = Channels[i];
    if (c.XValues.Count == 0 || c.YValues.Count == 0)
    {
    m_Logger.Warn($"Channel {i} ({c.ChannelProps.Legend}) has no data, skipping AutoScale");
    continue;
    }

    double[] validYValues = c.YValues.ToArray();
    if (c.ChannelProps.YAxisDisplayMode == AxisDisplayMode.Log)
    {
    validYValues = c.YValues.Where(y => y > 0).ToArray();
    if (validYValues.Length == 0)
    {
    m_Logger.Warn($"Channel {i} ({c.ChannelProps.Legend}) has no valid positive Y values for log mode, switching to linear mode");
    c.ChannelProps.YAxisDisplayMode = AxisDisplayMode.Linear;
    validYValues = c.YValues.ToArray();
    }
    }

    double maxY = validYValues.Max();
    double minY = validYValues.Min();
    double centerY = Math.Abs(maxY) > Math.Abs(minY) ? 0 : (maxY + minY) / 2;
    double rangeY = Math.Max(Math.Abs(maxY – centerY), Math.Abs(minY – centerY)) * 2;
    if (rangeY < 1E-10)
    {
    rangeY = Math.Max(Math.Abs(centerY), 1) * 2;
    }
    double margin = rangeY > 100 ? 0.2 : 0.4;
    c.ChannelProps.VerticalMax = centerY + rangeY * (0.5 + margin);
    c.ChannelProps.VerticalMin = centerY – rangeY * (0.5 + margin);
    c.ChannelProps.YOffset = 0;

    double maxX = c.XValues.Max();
    double minX = c.XValues.Min();
    double rangeX = maxX – minX;
    if (rangeX < 1E-10)
    {
    rangeX = Math.Max(Math.Abs(maxX), 1) * 2;
    }
    c.ChannelProps.HorizonMax = maxX + rangeX * 0.2;
    c.ChannelProps.HorizonMin = minX – rangeX * 0.2;

    if (c.ChannelProps.VerticalMax <= c.ChannelProps.VerticalMin)
    {
    m_Logger.Warn($"Invalid Y range for channel {i}: max={c.ChannelProps.VerticalMax}, min={c.ChannelProps.VerticalMin}");
    c.ChannelProps.VerticalMax = c.ChannelProps.VerticalMin + 1;
    }
    c.ChannelProps.VerticalStep = (c.ChannelProps.VerticalMax – c.ChannelProps.VerticalMin) / 10;
    c.ChannelProps.HorizonStep = (c.ChannelProps.HorizonMax – c.ChannelProps.HorizonMin) / 10;

    globalMaxY = Math.Max(globalMaxY, c.ChannelProps.VerticalMax);
    globalMinY = Math.Min(globalMinY, c.ChannelProps.VerticalMin);
    globalMaxX = Math.Max(globalMaxX, c.ChannelProps.HorizonMax);
    globalMinX = Math.Min(globalMinX, c.ChannelProps.HorizonMin);

    if (c.ChannelProps.Activated && activeChan == null)
    {
    activeChan = c;
    }
    }

    if (globalMaxY <= globalMinY)
    {
    globalMaxY = 10;
    globalMinY = -10;
    m_Logger.Warn("Global Y range invalid, using default: [10, -10]");
    }
    if (globalMaxX <= globalMinX)
    {
    globalMaxX = 10;
    globalMinX = 0;
    m_Logger.Warn("Global X range invalid, using default: [10, 0]");
    }

    if (!MultiChannelRange)
    {
    double centerY = (globalMaxY + globalMinY) / 2;
    double rangeY = globalMaxY – globalMinY;
    double margin = rangeY > 100 ? 0.2 : 0.4;
    globalMaxY = centerY + rangeY * (0.5 + margin);
    globalMinY = centerY – rangeY * (0.5 + margin);
    foreach (var c in Channels)
    {
    c.ChannelProps.VerticalMax = globalMaxY;
    c.ChannelProps.VerticalMin = globalMinY;
    c.ChannelProps.HorizonMax = globalMaxX;
    c.ChannelProps.HorizonMin = globalMinX;
    c.ChannelProps.VerticalStep = (globalMaxY – globalMinY) / 10;
    c.ChannelProps.HorizonStep = (globalMaxX – globalMinX) / 10;
    c.ChannelProps.YOffset = 0;
    }
    OscilloscopeProps.VerticalMax = globalMaxY;
    OscilloscopeProps.VerticalMin = globalMinY;
    OscilloscopeProps.HorizonMax = globalMaxX;
    OscilloscopeProps.HorizonMin = globalMinX;
    OscilloscopeProps.VerticalStep = (globalMaxY – globalMinY) / 10;
    OscilloscopeProps.HorizonStep = (globalMaxX – globalMinX) / 10;
    }

    if (Channels.Count > 0 && activeChan == null)
    {
    Channels[0].ChannelProps.Activated = true;
    activeChan = Channels[0];
    }

    Unit = activeChan?.ChannelProps.HorizonStep ?? 1;
    m_Logger.Info($"AutoScale completed: Global Y range [{globalMinY}, {globalMaxY}], X range [{globalMinX}, {globalMaxX}]");
    canvas.Invalidate();
    }
    catch (Exception ex)
    {
    m_Logger.Error($"AutoScale error: {ex.Message}, StackTrace: {ex.StackTrace}");
    foreach (var c in Channels)
    {
    c.ChannelProps.VerticalMax = 10;
    c.ChannelProps.VerticalMin = -10;
    c.ChannelProps.HorizonMax = 10;
    c.ChannelProps.HorizonMin = 0;
    c.ChannelProps.VerticalStep = 2;
    c.ChannelProps.HorizonStep = 1;
    c.ChannelProps.YOffset = 0;
    }
    OscilloscopeProps.VerticalMax = 10;
    OscilloscopeProps.VerticalMin = -10;
    OscilloscopeProps.HorizonMax = 10;
    OscilloscopeProps.HorizonMin = 0;
    OscilloscopeProps.VerticalStep = 2;
    OscilloscopeProps.HorizonStep = 1;
    Unit = 1;
    canvas.Invalidate();
    }
    }

    3. GetLegendBoxRec 方法(保持不变)

    • 上一版本已限制图例高度(drawingRec.Height * 0.7f),确保不压缩垂直空间,无需修改。
    • 代码如下(仅供参考):

    private RectangleF GetLegendBoxRec(Graphics g, RectangleF drawingRec)
    {
    try
    {
    if (!ShowLegend || Channels.Count == 0)
    {
    m_LegendBoxRec = RectangleF.Empty;
    return m_LegendBoxRec;
    }

    float legendWidth = 100f;
    float legendHeight = Channels.Count * 20f + 10f;
    float margin = 10f;
    float maxLegendWidth = drawingRec.Width * 0.3f;
    float maxLegendHeight = drawingRec.Height * 0.7f;

    foreach (Channel c in Channels)
    {
    SizeF textSize = g.MeasureString(c.ChannelProps.Legend, Font);
    legendWidth = Math.Max(legendWidth, textSize.Width + 20f);
    }
    legendWidth = Math.Min(legendWidth, maxLegendWidth);
    legendHeight = Math.Min(legendHeight, maxLegendHeight);

    float x = drawingRec.Right – legendWidth – margin;
    float y = drawingRec.Top + margin;
    m_LegendBoxRec = new RectangleF(x, y, legendWidth, legendHeight);
    m_Logger.Info($"LegendBoxRec: X={x}, Y={y}, Width={legendWidth}, Height={legendHeight}");
    return m_LegendBoxRec;
    }
    catch (Exception ex)
    {
    m_Logger.Error($"GetLegendBoxRec error: {ex.Message}, StackTrace: {ex.StackTrace}");
    m_LegendBoxRec = RectangleF.Empty;
    return m_LegendBoxRec;
    }
    }


    四、测试建议为验证优化效果,建议以下测试用例:

  • 负值居中测试:
    • 数据:Y = [-100, -50, 0, 50, 100],验证曲线在 [drawTop, drawTop + drawHeight] 内,上下留 15% 空白,零线接近 top + v_size / 2(通过日志检查)。
  • 不对称数据测试:
    • 数据:Y = [-1000, -500, 0, 50],验证曲线居中,不贴边。
  • 对数模式测试:
    • 启用 AxisDisplayMode.Log,输入 Y = [-10, 0, 10, 100],验证正值点居中,负值点跳过。
  • 单一值测试:
    • 数据:Y = [-50, -50, -50],验证范围对称(如 [-70, -30]),曲线居中。
  • 多通道测试:
    • 两通道,范围 [-100, 0] 和 [0, 100],测试 MultiChannelRange = false 是否居中。
  • 图例测试:
    • 启用 ShowLegend,验证图例不压缩垂直空间,曲线不填满格子。
  • 调试建议:

    • 日志检查:
      • 零线位置:csharp

        m_Logger.Info($"Zero line calculated at Y={zeroY}, Canvas middle={top + v_size / 2}, Draw area=[{drawTop}, {drawTop + drawHeight}]");

      • 点坐标:csharp

        m_Logger.Debug($"Point {i}: X={pt.X}, Y={pt.Y}, DataY={y}, InDrawArea={pt.Y >= drawTop && pt.Y <= drawTop + drawHeight}");

    • 视觉检查:
      • 确认无零线(灰色)、中线(蓝色)、边距线(绿色)。
      • 曲线应在 [drawTop, drawTop + drawHeight] 内,上下留 15% 空白。

    五、总结问题解决:

  • 三条额外线:
    • 删除零线(灰色)、中线(蓝色)、边距线(绿色)的绘制代码。
    • 保留 zeroY 日志,验证居中效果。
  • 居中显示:
    • 曲线在画布中央 70% 区域(drawTop 到 drawTop + drawHeight),上下 15% 空白。
    • 零线(Y=0)接近 top + v_size / 2(通过日志确认)。
  • 不填满格子:
    • 动态余量(20%-40%)和 15% 边距确保曲线不贴边。
  • 负值支持:
    • 对称范围包含负值,对数模式切换到线性模式(如无正值)。
  • 视觉美观:
    • 曲线分布均匀,上下空白充足,无干扰线。
  • 稳定性:
    • 保留异常处理和日志,防止曲线消失。
  • 预期效果:

    • 曲线在画布中央 70% 区域,上下留 15% 空白。
    • 无零线、中线、边距线,视觉干净。
    • Y轴方向正确(数值增大向下),负值点正确显示。
    • 图例不压缩垂直空间,曲线美观。

    下一步: 如果仍存在问题(如曲线未完全居中、显示异常),请提供:

  • 异常日志(m_Logger 输出)。
  • 测试数据样例(如 YValues 或 CSV 文件)。
  • 是否启用对数模式或 ShowLegend。
  • 画布尺寸(drawingRec 的 Width 和 Height)。
  • 截图或描述(如“曲线贴近顶部 5%”)。
  • 我将进一步分析,确保曲线完美居中且美观!

    图形用户界面设计

    更简洁说明

    赞(0)
    未经允许不得转载:171主机测评 » 优化后的 DrawChannels 方法中,显示了三条额外的线(可能指零线、中线和边距线),这些辅助线用于调试居中和边距效果
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址