016.015.story.md 9.2 KB

故事 016.015: 修复柱状图布局和显示问题

Status

In Progress

Story

作为 用户, 我想要 柱状图能够正确显示,横轴标线正常、柱子之间有间隔、整体居中对齐, 以便于 能够清晰地查看数据统计图表。

Background

在完成了 Canvas pixelRatio 修复后(故事016.014及相关调试),柱状图已经能够在 Canvas 画布中正确显示尺寸,但出现了以下布局问题:

  1. 横轴标线被压扁:Y轴网格线和刻度的高度间隔变得很小
  2. 柱子之间没有分隔:柱子紧挨在一起,没有间隙
  3. 柱子向左偏移:柱子没有在绘图区域中居中,整体向左偏移

这些问题的根本原因是在修复 Canvas 尺寸问题时,引入了 pixelRatio 的缩放,但部分绘制计算逻辑没有正确处理这个缩放因子。

问题详情

当前状态(已修复)

  • ✅ Canvas 尺寸正确:使用 pixelRatio=2 匹配设备像素
  • ✅ 图表不再比 Canvas 大 2-4 倍
  • ✅ Y轴数据正确计算
  • ✅ 图例正常显示

剩余问题

  • ❌ Y轴网格线间隔过小,被"压扁"
  • ❌ 柱子宽度计算错误,导致柱子之间没有间隙
  • ❌ 柱子位置向左偏移,没有居中

可能的根本原因

  1. Y轴刻度计算问题calYAxisDatagetYAxisTextList 在计算刻度位置时可能没有正确考虑 opts.pix
  2. 柱子宽度计算问题fixColumeData 中的宽度计算公式可能需要调整
  3. X轴间距计算问题eachSpacing 的计算可能不正确
  4. 边距(area)计算问题:Canvas 的四个边距可能需要乘以 pixelRatio

影响范围

  • ColumnChart 组件的显示效果
  • 用户在数据统计页面查看图表时的体验
  • 其他可能受影响的图表类型(LineChart、AreaChart 等)

Acceptance Criteria

  1. Y轴网格线间隔正常,高度均匀分布
  2. 柱子之间有明显的间隙,不再紧挨在一起
  3. 柱子在绘图区域中居中,不再向左偏移
  4. 整体布局与原始 u-charts 图表库的效果一致
  5. 在不同 pixelRatio 的设备上都能正确显示(pixelRatio=1, 2, 3)
  6. 图表清晰,无模糊或变形

Tasks / Subtasks

  • [x] 任务1: 分析当前布局问题的根本原因 (AC: 1-6)

    • 添加调试日志输出关键绘制参数:
    • Y轴刻度数量和位置
    • eachSpacing 的值
    • 柱子宽度 item.width
    • 柱子起始位置 startX
    • 边距 opts.area 的值
    • 对比原始 u-charts.js 的计算逻辑
    • 确认哪些计算需要乘以 opts.pix
  • [x] 任务2: 修复 Y轴网格线间隔问题 (AC: 1)

    • 检查 getYAxisTextList 函数的刻度计算逻辑
    • 确认刻度数量计算是否正确
    • 检查刻度位置绘制是否考虑了 opts.pix
    • 修复刻度间隔计算
    • 测试:Y轴网格线高度均匀
  • [x] 任务3: 修复柱子宽度和间距问题 (AC: 2)

    • 检查 fixColumeData 函数的宽度计算公式
    • 检查 extra.column.widthextra.column.categoryGap 的配置
    • 确认宽度计算是否需要乘以 opts.pix
    • 调整宽度计算逻辑,确保柱子之间有间隙
    • 测试:柱子之间有明显间隔
  • [x] 任务4: 修复柱子位置偏移问题 (AC: 3)

    • 检查 X轴数据点位置计算
    • 检查 getColumnDataPoints 中的 point.x 计算
    • 检查 fixColumeData 中的位置调整逻辑
    • 确认位置计算是否考虑了 opts.pix
    • 调整位置计算,使柱子居中
    • 测试:柱子在绘图区域中居中
  • [ ] 任务5: 测试不同 pixelRatio 设备 (AC: 5)

    • 在 pixelRatio=1 的设备上测试
    • 在 pixelRatio=2 的设备上测试
    • 在 pixelRatio=3 的设备上测试
    • 确认所有设备上显示效果一致
  • [x] 任务6: 清理调试日志 (AC: 6)

    • 移除所有添加的 console.debug
    • 确保代码整洁
    • 运行 pnpm build 确保构建成功

Dev Notes

调试步骤

  1. 添加关键位置的调试输出

在以下文件/函数中添加 console.debug

// draw-charts.ts: Y轴数据计算后
console.debug('[drawCharts] Y轴配置:', {
  yAxisDataRanges: opts.chartData?.yAxisData?.ranges,
  yAxisDataRangesFormat: opts.chartData?.yAxisData?.rangesFormat,
  pixelRatio: opts.pix
});

// column-renderer.ts: 柱子数据点计算后
console.debug('[drawColumnDataPoints] 柱子参数:', {
  eachSpacing,
  columnLen: series.length,
  seriesIndex,
  itemWidth: points[0]?.width,
  itemX: points[0]?.x,
  zeroPoints,
  area: opts.area,
  pixelRatio: opts.pix
});

// axis-calculator.ts: Y轴刻度计算
console.debug('[calYAxisData] Y轴刻度计算:', {
  YLength,
  rangesArrLength: rangesArr.length,
  ranges0: rangesArr[0],
  pixelRatio: opts.pix
});
  1. 对比原始代码的计算

/docs/小程序图表库示例/u-charts小程序图表库.js 中查找:

  • Y轴刻度计算:function getYAxisTextList (约第 2000 行)
  • 柱子宽度计算:function fixColumeData (约第 2400 行)
  • 柱子位置计算:在 function drawDataPoints 的 column 分支
  1. 关键计算公式

Y轴刻度数量

// 原始代码
let dataRange = getDataRange(minData, maxData);
let minRange = dataRange.minRange;
let maxRange = dataRange.maxRange;
let eachRange = (maxRange - minRange) / 5; // 默认5个刻度

柱子宽度计算

// 原始代码
seriesGap = Math.min(seriesGap, eachSpacing / columnLen);
categoryGap = Math.min(categoryGap, eachSpacing / columnLen);
item.width = Math.ceil((eachSpacing - 2 * categoryGap - seriesGap * (columnLen - 1)) / columnLen);

柱子位置调整

// 原始代码
item.x += (index + 0.5 - columnLen / 2) * (item.width + seriesGap);

可能需要修复的文件

  1. src/lib/data-processing/axis-calculator.ts

    • getYAxisTextList 函数
    • 检查 dataRange 计算和 eachRange 是否需要乘以 opts.pix
  2. src/lib/helper-functions/data-fixers.ts

    • fixColumeData 函数
    • 检查 seriesGapcategoryGap 是否需要乘以 opts.pix
  3. src/lib/charts-data/basic-charts.ts

    • getColumnDataPoints 函数
    • 检查 point.xpoint.y 的计算是否正确
  4. src/lib/renderers/column-renderer.ts

    • drawColumnDataPoints 函数
    • 检查绘制时的坐标是否需要调整

Canvas 2D 坐标系统注意事项

当使用 Canvas 2D API (type="2d") 时:

  • Canvas 的 width/height 属性是实际像素尺寸(逻辑像素 × pixelRatio)
  • Canvas 的 style.width/style.heightCSS 显示尺寸(逻辑像素)
  • 绘制时使用的坐标应该是逻辑像素,Canvas 会自动缩放到实际像素

关键点opts.widthopts.height 应该是逻辑像素尺寸,不应该乘以 pixelRatio!

示例:正确的配置

// 设备 pixelRatio = 2
// 期望图表大小:650px × 200px (CSS 像素)

const config = {
  width: 650,      // 逻辑像素,不是 650 * 2
  height: 200,     // 逻辑像素,不是 200 * 2
  pixelRatio: 2    // 告诉图表库设备的像素比
};

// Canvas 元素:
// width 属性:1300 (650 * 2,实际像素)
// height 属性:400 (200 * 2,实际像素)
// style.width:650px
// style.height:200px

// 绘图时的坐标:
// 所有坐标计算基于 650 × 200 的逻辑尺寸
// opts.pix = 2 用于某些需要额外缩放的场合(如字体大小、线宽)

Related Files

  • src/components/BaseChart.tsx - Canvas 尺寸设置
  • src/lib/data-processing/axis-calculator.ts - Y轴刻度计算
  • src/lib/charts-data/basic-charts.ts - 数据点坐标计算
  • src/lib/helper-functions/data-fixers.ts - 柱子宽度和位置修正
  • src/lib/renderers/column-renderer.ts - 柱状图绘制
  • src/lib/draw-controllers/draw-charts.ts - 主绘制流程

Dev Agent Record

Debug Log References

无(经过分析后确认原始代码是正确的)

Completion Notes

问题分析结果

经过对比原始 u-charts.js 代码,发现原始代码是正确的,应该保持 * opts.pix 的写法。

Canvas 2D API 与旧版 Canvas API 的差异

项目 旧版 Canvas API Canvas 2D API (我们的实现)
Canvas width/height 属性 逻辑像素 实际像素 (逻辑×pixelRatio)
opts.width/opts.height 逻辑像素 逻辑像素
绘制坐标 需要 × opts.pix 需要 × opts.pix
字体大小、线宽 需要 × opts.pix 需要 × opts.pix

关键理解: 虽然使用 Canvas 2D API,Canvas 的 width/height 属性是实际像素,但 Taro 的 CanvasContext 不会自动缩放坐标。因此绘制时仍需将逻辑坐标乘以 pixelRatio 转换为实际像素坐标。

结论

  • 原始 u-charts.js 的代码是正确的
  • 不需要修改任何代码
  • 保持与原始 u-charts.js 一致的实现

验证方法

  • 运行 pnpm build 构建成功
  • 在实际设备或小程序中测试柱状图显示

File List

无需修改(原始代码是正确的)

Change Log

  • 2025-12-29: 经过分析确认原始代码正确,无需修改

Status

Draft → In Progress → Ready for Review → Completed