---
title: Claude3.5 打造漂亮的动态竞速图工具
date: '2024-09-28 19:00:00'
tags: ['claude']
keywords: ['Claude3.5 辅助前端', '竞速图工具', '动态图表生成工具']
description: 本文用 Claude3.5 实现一个竞速图工具，支持上传数据生成动态竞速图，支持自定义列名，支持预览数据。
---

时不时在网上看到一些漂亮的竞速图页面，展示数据随时间的变化情况，看着很酷。然后查了下有现成的 js 库可以生成这种图表，不过没找到一个合适的工具，可以将数据上传后，自动生成竞速图。

于是想着用 Claude3.5 实现一个，不过这个过程并不顺利，中间踩了不少坑。从 7 月份都开始着手，中间断断续续尝试了多次，直到最近才算实现了一个简单的版本。本文记录下用 Calude3.5 实现这个工具的过程，记录其中踩过的坑。最终的效果可以在[这里](https://gallery.selfboot.cn/zh/tools/chartrace/)体验，欢迎大家留言讨论。

## 失败尝试

最开始的时候，想着比较简单，就先去找了下目前哪些库支持动态竞速图。


决定用 于是就直接提示：

> 帮我用 react 实现一个网页工具，可以根据用户传的 json 数据，生成动态竞速表。
    

生成了一个粗糙的代码，需要特定的数据才能跑起来。并且效果也很差，就暂时搁置了。

![粗糙的竞速图](https://slefboot-1251736664.file.myqcloud.com/20240926_ai_gallery_chartrace_ugly_version.png)

## 继续尝试

最近用 cursor 比较舒服，就想着再来试试。原来的代码能解析 json，然后也能跑起来，这次想着一次性支持多点，能够支持传入 excel 或者 csv 文件，然后数据格式也可以自定义这样子。

打开原来的代码，选定代码后，直接提示：

> 我想支持传入 excel 或者 csv 文件，然后生成竞速直方图。
> 用 ReactECharts 实现，完善下面的代码。

然后 claude3.5 让我导入 excel 库，这里踩了坑，先让我从 xlsx 导入，结果安装的是 xlsx-js-style，货不对版。重新提示修好这些小问题后，让它生成了一份示例数据，然后想着来拿示例数据测试下。示例数据虽然短，不过内容倒是不错：

| 年份 | 类别 | 数值 |
|------|------|------|
| 2015 | 苹果 | 100 |
| 2015 | 香蕉 | 80 |
| 2015 | 橙子 | 60 |
| 2015 | 葡萄 | 40 |
| 2015 | 芒果 | 20 |
| 2016 | 苹果 | 120 |
| 2016 | 香蕉 | 90 |
| 2016 | 橙子 | 70 |
| 2016 | 葡萄 | 50 |
| 2016 | 芒果 | 30 |
| 2017 | 苹果 | 130 |
| 2017 | 香蕉 | 110 |
| 2017 | 橙子 | 90 |
| 2017 | 葡萄 | 70 |
| 2017 | 芒果 | 50 |

这里第一列是时间，第二列是类别，第三列是值，每列名字可以自定义，传入文件后希望能根据时间，生成不同类别的数值动态竞速图。搞好一个版本后，发现传入csv 和 excel 还是不行，必须先传入 excel，再传一个 json 才能生成一个简单竞速图。先不说这个过程有多奇怪，这里竞速图的纵坐标也没有类别的名字。

为了让它能在纵坐标支持名字，花了好长时间，提示了几次也不太行。中间改动还导致连动态图也出不来了，然后加了日志，发现导入的数据解析有问题。让 Claude3.5 反复分析了好几轮，始终没有找到解决方法，看来 Claude3.5 还是**不太适合做这种复杂的分析**。

## 动态竞速图样例

算了，不再继续尝试，不如看看[官方示例](https://echarts.apache.org/examples/en/editor.html?c=bar-race-country)是怎么做的。**毕竟只有自己懂怎么写，才能指导 AI 写出来**。官方示例代码足够简单，样式也很好看，如下：

![官方的竞速直方图示例](https://slefboot-1251736664.file.myqcloud.com/20240926_ai_gallery_chartrace_demo.png)

代码看起来也不是很复杂，导入数据后，指定下动态竞速图的配置，然后很简单就可以生成一个动态竞速图。把这部分提供给 Claude3.5 解读，让它作为参考代码，它肯定能理解。

既然生成动态竞速图的逻辑已经知道，那么剩下的就是处理前端页面，让用户可以上传文件，然后解析文件，生成动态竞速图。这里**让 AI 直接生成这么多，还是有复杂度，人工拆分任务，让 Claude3.5 一点点完成呢**？

## 拆分问题！

这里的任务其实挺好拆分的，先是上传和解析 Json 文件相关部分，接着可以加一个文件预览，这样方面使用者理解数据。有数据的情况下，再结合前面的官方示例代码，就可以生成动态竞速图了。这些都搞定后，可以在一个能跑的版本上慢慢优化迭代，比如增加导出 Gif 功能，或者支持不同颜色，支持添加标题等。后面如果想支持 Excel 或者 csv 也比较简单，只用写处理文件部分就好，其他都能复用。

### 文件上传、预览

这里先看第一部分，直接让 AI 生成一个上传组件，可以传 json 文件。同时为了不限制死文件格式，考虑让使用者选择某列来作为图表的数据源。其实这里需要的数据主要有 3 列，时间、类别和值。再传入 Json 文件后，直接解析里面的列名，然后返回选择框就行。

直接描述有点费劲，为了让 Claude3.5 更好理解，我直接给它画了个草图，如下：

![上传组件草图](https://slefboot-1251736664.file.myqcloud.com/20240926_ai_gallery_chartrace_upload.png)

让它参考这个实现。不过后来想了下，还是让页面整体布局和站点其他的保持一直，于是把设置相关的放到最右边去。Claude 实现的版本并不理想，有一些小问题，不过可以接着微调提示：

> 不对，你这里没有读上传的 json 文件啊。
> 要读文件内容，判断是否合法的 json，如果是合法的 json 则输出预览。
> 否则输出一个弹窗，告诉 json 解析错误。
>
> 这里的弹窗新建一个基础组建，有关闭按钮，在屏幕中央输出，上面有遮罩

这次添加了一个 Modal 遮罩组件，并且不止文件解析错误会输出。在没有选择数据源的时候，也会输出一个弹窗，这就是 Claude 带来的意外惊喜了。预览部分上面提示词没有说很详细，Claude 用一个表格来实现，有点小问题，不过很容易就能改对了。

### 竞速图生成部分

上传部分搞定了，也能预览数据了，接着就是生成竞速图了。提示词也很简单：

> 继续完善这里的代码，现在选择好列之后，点击生成图形，就要根据传入的 json，生成竞速图。
> 
> 竞速图在预览的下方部分。
> 
> 竞速图可以参考这里的：
> const updateFrequency = 2000;
> ...

这里从官方复制了完整的竞速图代码，上面不列出了。这次 Claude3.5 生成的代码很不错，终于能复现官网的例子了。不过还有些小问题，直接提出来给 Claude3.5：

> 这里两个问题：
> 1. 第一次点击生成图形的时候，先出来了一个竖条，卡在那里，等了下才出现动态库；
> 2. 后续点击生成图形的时候，之前的没完成的还在继续，导致会两个叠加在一起

第一个问题一次性完美解决，在设置初始 chartOption 时就包含第一年的数据，这样图表一开始就会显示有意义的数据。第二个问题，开始的提示词可能不太清晰，导致第二次点击后数据是慢慢回到开始，接着补充了详细提示，问题也很快解决。

至此，一个能跑的版本出来啦！

### 导出 GIF 的难点

为了方便导出 GIF，接着先简单提示：

> 这里可以支持导出竞速动态图吗？整个竞速完整过程生成 gif 或者 webp 动态图，支持下载

给出的第一个方案是使用 html2canvas 库来捕获图表的每一帧，然后使用 gif.js 库将这些帧合成为 GIF 动画。第一个版本不行，点了生成 gif 后一直卡在正在生成 GIF..。于是接着完善提示词：

> 我想要的是：
> 1. 生成 gif 的时候，不影响现在站点上的竞速图；现在会有两份竞速图不断覆盖，这样不对
> 2. 生成 gif 在后台，不要等真的每秒这样的过，要支持快速生成完整的竞速图。
> 生成完整后就弹出保存窗口，可以保存 gif

中间反复生成了几个版本，都没能解决。于是尝试让 AI 分析下这里的原因，然后提供日志来排查下。接着 Claude 在代码中添加了不少日志，生成的时候如下：

![竞速图生成完成](https://slefboot-1251736664.file.myqcloud.com/20240926_ai_gallery_chartrace_claude_log.png)

这里一直卡在 Gif 渲染了。把日志也给 Claude3.5 看了下，但是还是没定位到问题。这里让 Claude3.5 **反思下有哪些可能导致这里的问题**，给出了一堆：

1. 图像加载：gif.js 需要完全加载的图像才能正确渲染。
2. canvas 大小：确保 canvas 的尺寸正确设置。
3. 帧的添加方式：确保正确地将帧添加到 GIF 对象中。
4. 内存使用：大量帧可能导致内存问题。
5. 异步操作：确保所有帧都已添加后才调用 render() 方法。

逐个排除了下，还是有问题。去看了下 [gif.js 的官方文档](https://jnordberg.github.io/gif.js/)，依旧没什么头绪。**又耐着心去看下 AI 生成的代码**，发现在创建 jif 对象的时候，是这样的：

```js
const gif = new GIF({
    workers: 4,
    quality: 10,
    width: canvasWidth,
    height: canvasHeight,
    workerScript: '/path/to/gif.worker.js' // 确保正确设置 worker 脚本路径
});
```

问了下 **Claude3.5 这个文件的作用，以及如何设置**。gif.worker.js 文件包含了 GIF 编码的核心逻辑。它在 Web Worker 中运行，允许 GIF 编码过程在后台进行，不会阻塞主线程。在 nextjs 项目中安装 gif.js 库后，可以在 node_modules 中找到 gif.worker.js 文件，将它复制到 public 目录下，然后设置为 `/gif.worker.js`。

看起来很对，但跑了还是不行！又尝试了几轮对话，始终卡在渲染 GIF 那里。**Claude3.5 似乎钻进了牛角尖，接着重新开一个对话，换 GPTo1 来试试**。提示词也很少：

> 已生成并添加第 80 帧
> content.js:241 已生成并添加第 81 帧  
> content.js:224 所有帧生成完毕，开始渲染 GIF  
> content.js:226 调用 gif.render()  
> content.js:250 GIF 渲染开始  
> content.js:252 GIF 渲染进度: 0.00%  
> 
> 这里渲染 gjf 一直卡在渲染进度 0 ，似乎没有开始。帮我解决

哈哈，这里甚至拼错了 gif 为 gjf，但 GPTo1 还是很快理解了，并给出了方向：

> 看起来您的 GIF 渲染过程卡在了进度 0.00%，这通常与 gif.worker.js 的加载问题有关。以下是一些可能的原因和解决方法：
> 1. 确认 gif.worker.js 的路径  
> 确保 gif.worker.js 文件位于正确的位置，并且路径 /gif.worker.js 是可以访问的。如果您将 gif.worker.js 放在 public 文件夹中，确保它可以通过浏览器访问。例如，访问 `http://localhost:3000/gif.worker.js` 应该能看到该文件内容。
> 2. 检查浏览器控制台错误  
> 打开浏览器的开发者工具，查看是否有关于 gif.worker.js 加载的错误。如果有 404 错误，说明路径不正确；如果有跨域问题，可能需要调整服务器配置。

试了下，发现访问的时候路径被重定向了。忽然想起来，当时为了支持旧的没有带语言版本的链接时，在 middleware.js 中加了自动重定向，会在链接中增加语言。比如访问 https://gallery.selfboot.cn/tools/chartrace 会自动重定向到 https://gallery.selfboot.cn/en/tools/chartrace/ 。这里修复比较简单，去掉 gif.worker.js 的自动重定向，再次尝试导出就可以了。

## 其他细节优化

大的功能点都完成了，下面就是用 cursor 来优化下细节。不得不说，**单纯完成小的细节任务，claude 3.5 正确率还是很高的**。比如：

1. 修改生成动态 GIF 图的背景颜色；
2. 下载文件的名字改为上传文件名，后缀是 gif；
3. 把导出 gif 按钮放到右边设置页，导出的时候支持进度条展示进度；
4. 支持自定义输入标题；
5. 每条数据的颜色可以随机生成，然后保证颜色搭配合理；
6. 修改代码支持多语言，生成翻译文件。

这些任务要是自己来写的话，虽然也能实现，但还是要花不少时间的。用 Claude3.5 完成这些细节任务，就轻松很多了。

## Cursor Claude3.5 缺陷

在使用的过程中，发现 cursor 还是有不少问题的。生成代码后，点击 Apply，**发现变更的 diff 中，有时候会把已经写好的代码删掉，用注释来替换了**。所以对于变更，还是不能无脑 Apply，需要人工检查下才行。

此外写的过程中，比如你随手改了一个地方。**后续 AI 生成的时候，可能又给你改回去**。比如下图中，我已经把这里的时间改成了 500ms，后续每次 Apply 的时候，都会改回去 2000：

![AI 错误修改改动的代码](https://slefboot-1251736664.file.myqcloud.com/20240926_ai_gallery_chartrace_Claude_error.png)

猜测这里可能是 cursor 选择代码版本的时候，并没有用当前编辑器中的版本。而是自己在上下文维护了一个代码版本，然后基于维护的版本生成新的代码。之后在 diff 的时候，用了老的版本来覆盖编辑器改动过的版本。

还有一个很不爽的地方就是，**Claude3.5 在功能改动的时候，经常忘记删掉多余的代码**。比如一个功能已经改变了实现，经常只是增加代码，没有删除掉不用的代码。比如下图，这里渲染的时候没有用到一些状态，但是还在。只能提示它去删掉，好在稍微提示下，它就立马知道怎么删除了。

![AI 生成代码的时候没有删除不用的](https://slefboot-1251736664.file.myqcloud.com/20240926_ai_gallery_chartrace_claude_delete.png)

## 使用过程反思

找到和 Claude3.5 的**正确沟通方法**后，从零写一个工具还是挺快的。只用专注于整体的实现思路，然后尽量把任务拆分，每一步 AI 基本完成的还不错。遇到有 Bug 的地方，稍微看看代码，然后也基本也能解决。

**有时候单个模型可能钻牛角尖，可以试着换个模型**。这次生成 GIF 的时候，用 Claude3.5 排查了半天也没能解决，后来切换到 GPTo1，重新开了个会话，直接就给了很好的排查方向，一下子也就解决问题了。

再分享一个个人小技巧，有的对话长了后，**AI 每次提问会带很多上下文，回答的准确度会下降不少**。这时候重新开一个新的对话，效果可能会好很多。

你想要什么工具吗？试试用 cursor 来实现吧。