关于 lightweight-charts-plugin-authoring
lightweight-charts-plugin-authoring 是用于开发 Lightweight Charts™ 插件的 skill,面向插件开发者。它涵盖插件类型选择、本地 API 查阅、脚手架生成、参考实现及验证流程。
主要功能
- 01指导选择插件类型
- 02依据本地类型定义查找 API
- 03通过向导生成插件脚手架
- 04提供工具包与参考实现指引
- 05按步骤构建和测试
适用场景
- 01规划自定义序列或图表原语
- 02实现前核对可用 API
- 03生成插件项目并进行验证
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tradingview/ lightweight-charts --skill lightweight-charts-plugin-authoring安装后在 Claude Code 中输入/lightweight-charts-plugin-authoring确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/official-plugins.mdpackage-contract.mdtoolkit.md详细介绍
编写 Lightweight Charts™ 插件
本技能介绍如何编写插件。关于如何使用图表本身——系列、刻度、标记、数据、封装器——请使用配套的 lightweight-charts 技能;这两项技能专为搭配安装而设计。
当前所在位置及源文件所在位置
本技能适用于两种场景,同一个问题在不同场景下的答案也不同。查找任何内容前,请先判断当前属于哪种场景:
# upstream checkout of lightweight-charts itself?
test -f pnpm-workspace.yaml && test -d packages/lwc-toolkit && echo upstream || echo "a plugin project or other repo"
在插件项目或其他任何仓库中(最常见的情况):
| 查找内容 | 位置 |
|---|---|
| 库的 API | node_modules/lightweight-charts/dist/typings.d.ts;如果尚未安装,可在 https://tradingview.github.io/lightweight-charts/lightweight-charts.d.ts 获取已发布的类型定义 |
| 工具包的 API | node_modules/@tradingview/lwc-toolkit/dist/<module>.d.ts——.d.ts 文件包含文档注释,因此可将其作为文档阅读 |
| 参考插件的源代码 | 不在 node_modules 中(软件包只包含 dist/)。请从 GitHub 获取:https://raw.githubusercontent.com/tradingview/lightweight-charts/master/packages/lwc-plugin-<name>/src/<file>,或者克隆一次后在本地阅读:git clone --depth 1 https://github.com/tradingview/lightweight-charts /tmp/lightweight-charts |
| 文档 | 每个文档页面都提供 Markdown 格式,只需在 URL 后追加 .md 即可,例如 https://tradingview.github.io/lightweight-charts/docs/plugins/custom_series.md。https://tradingview.github.io/lightweight-charts/llms.txt 列出了所有页面;其旁边的 docs_map.md 还列出了各页面的标题 |
**在上游代码库中:**库的类型定义位于 dist/typings.d.ts(或 src/),工具包位于 packages/lwc-toolkit/src/,插件位于 packages/lwc-plugin-*/src/,文档位于 website/docs/plugins/*.md,概念验证示例位于 plugin-examples/。
# what version is installed, and does the hook I am about to rely on exist in it?
node -p "require('lightweight-charts/package.json').version"
rg -n "hitTest|conflationReducer|isHovered|ICustomSeriesPaneView|IPrimitivePaneRenderer" node_modules/lightweight-charts/dist/typings.d.ts
# the toolkit's custom-series surface
rg -n "export (declare )?(function|class|interface|type)" node_modules/@tradingview/lwc-toolkit/dist/custom-series/*.d.ts
# a reference implementation, without a checkout
curl -fsSL https://raw.githubusercontent.com/tradingview/lightweight-charts/master/packages/lwc-plugin-stacked-bars-series/src/renderer.ts
如果本地类型定义与文档不一致,请以本地类型定义为准:文档描述的是当前版本,而类型定义描述的是用户实际安装的版本。如果缺少文件,请说明哪些内容无法验证。切勿臆造选项、钩子或工具包导出项——无法通过臆想的 API 编译的插件会导致用户的类型检查失败,而通过图表会忽略的选项编译的插件则会在运行时静默失效。
从想法到插件类型
这是影响最大、也应该最先做出的决定。思考这个想法是什么,而不是它看起来怎样:
| 想法是…… | 类型 | 原因 |
|---|---|---|
| 以新方式绘制系列自身的数据(柱、带、堆叠、蜡烛) | 自定义系列 | 它需要自己的数据、自动缩放、价格线、最新值和时间刻度集成 |
| 与某个系列关联的装饰(标签、标记、围绕它的带、基于其数据的绘图工具) | 系列基元 | 它会读取该系列的坐标,并被限制在该系列范围内 |
| 与图格关联的装饰(水印、图例、标题、覆盖所有系列的叠加层) | 图格基元 | 它覆盖整个图格,且其生命周期不受任何单个系列影响 |
| 基于 DOM 的无障碍、工具提示或控件层 | 带有 DOM 叠加层的图格基元 | 画布并非合适的媒介;请将其附加到 paneContentElement(pane) |
默认选择基元。自定义系列是更复杂的构造——它必须实现 priceValueBuilder、isWhitespace、renderer 和 update,并且其渲染器会接收系列的绘图数据。只有当这个想法本身就是一个系列时才选择它。需要处理指针事件的绘图工具仍然属于基元:它在基元中绘图,并通过 subscribeClick、subscribeCrosshairMove 和坐标转换 API 响应事件。
选择时还要考虑悬停和交互:基元提供 hitTest,以便图表在十字光标和点击事件中报告它们;自定义系列只有在 Lightweight Charts™ 5.2 及更高版本中,才会在其渲染器上提供 hitTest,这使得 draw 的 isHovered 和 hitTestData 参数能够报告悬停状态。
工作方式
按以下顺序构建。每一步都是发现上一步错误的最低成本时机。
- 先写下数据项和选项,再编写代码。数据项决定
isWhitespace(哪些字段会使一个点可绘制),选项决定defaultOptions。两者分别放在各自的文件中(data.ts、options.ts),因为 README、类型定义和演示都会读取它们。 - 搭建脚手架(见下文)。不要从空白文件开始。
- 先让渲染器使用脚手架生成的示例数据完成绘制,只绘制可见范围,然后再处理选项或交互。绘制所有内容或什么都不绘制的渲染器,在规模还小时更容易修复。
- 通过
applyOptions接入选项;使用requestUpdate()重新渲染。检查每个选项在演示页面中都能生效。 - 仅自定义系列:自动缩放和空白数据。
priceValueBuilder会影响价格刻度和最新值标签;空白数据则决定折线在哪里断开。两者都有容易踩的坑(请查看下方的检查清单)。 - 交互:
hitTest、悬停、拖动。将交互逻辑与绘制逻辑分开。 - 测试,然后是 README,最后是演示和预览页面。软件包约定(
references/package-contract.md)说明了每项必须包含的内容。
使用脚手架,不要从空白开始
npm create lwc-plugin@latest
该向导支持交互操作。它会先询问是否安装此技能(如果已经安装,或用户退出向导并将操作交给你,请选择否),然后询问类型、名称、一句话描述、软件包名称、类名、作者、许可证、最低库版本、标签、是否保留提示注释,以及目标文件夹。在运行向导前,先与用户确定这些答案——上文的类型选择最重要——然后让用户在终端中运行向导;如果你的环境提供了 TTY,也可以自行回答提示。向导会生成一个 Vite 项目,其中包含:
src/<entry>.ts— 插件类(对于自定义系列,还包含一个工厂);src/options.ts、src/data.ts,以及按类型生成的渲染器或视图文件src/example/index.html+example.ts— 开发演示,运行pnpm dev即可提供该页面package.json,包含dev、build、typecheck和check-package脚本、lightweight-chartspeer 范围、作为 devDependency 的@tradingview/lwc-toolkit,以及用于目录的lwcPlugin配置块- README,其结构已符合目录的渲染格式
对于“hint comments”,请选择是:这些 //* 行会解释模板的各个部分;否则向导会将它们剥离。阅读这些注释,然后删除已处理的部分。使用脚手架自带的脚本进行构建(pnpm typecheck、pnpm build、pnpm check-package)——CI 和发布工具会运行这些脚本。
基于工具包构建
官方插件均基于 @tradingview/lwc-toolkit 构建。它从各个示例过去手动复制的辅助函数中提取而来。使用它并不是为了少写几行代码,而是因为这些辅助函数封装了很容易出错的图表行为——给定柱间距时内置蜡烛图的精确宽度、适用于各种设备像素比的像素级精确线条位置、LineStyle 的虚线样式,以及数据合并如何改变柱的逻辑步长。通过子路径分别导入各个辅助函数:
import { CustomSeriesRendererBase } from '@tradingview/lwc-toolkit/custom-series/renderer-base';
import { visibleSegments, extendRange, barCoordinate } from '@tradingview/lwc-toolkit/custom-series/visible-bars';
import { PluginBase } from '@tradingview/lwc-toolkit/plugin-base';
常见情况下,可按问题选择对应模块。references/toolkit.md 介绍了其余内容,包括各个 API 的结构,以及使用它的官方插件。
| 你需要…… | 使用 |
|---|---|
| 编写自定义序列渲染器,而不必重新实现数据 / 范围检查 | custom-series/renderer-base — 扩展 CustomSeriesRendererBase,实现 drawImpl(scope, args) |
创建序列或面板图元,并自动处理图表引用和 requestUpdate() | plugin-base(PluginBase)/ pane-plugin-base(PanePluginBase) |
| 只绘制屏幕上可见的内容,并用线条延伸至面板边缘 | custom-series/visible-bars — forEachVisibleBar、mapVisibleBars、extendRange、barCoordinate |
| 在明确的空白处断开线条,而不是在其他序列的时间戳处断开 | createWhitespaceSeries(custom-series/options-aware-series)+ visibleSegments |
在 priceValueBuilder 中读取选项(使自动缩放跟随选项变化) | 使用会影响绘图值的选项名称调用 createOptionsAwareSeries |
| 处理堆叠值:带状区域边缘和自动缩放极值 | custom-series/stacking — stackLevels、stackedPlotValues |
| 绘制与内置图表宽度相同且像素对齐的柱形、蜡烛图或线条 | dimensions/* — calculateColumnPositions、candlestickWidth、positionsLine、positionsBox |
| 绘制带内嵌边框的圆角矩形 | canvas/round-rect |
| 绘制折线、阶梯线、两条线之间的区域,以及颜色变化的线条 | custom-series/line-paths |
| 在价格轴或时间轴上添加标签 | axis-label-view(AxisLabelView) |
| 在面板内添加 DOM 覆盖层 | dom/pane-element(paneContentElement) |
处理简单事件,或将 Time 转换为时间戳或显示字符串 | delegate、time |
如果脚手架的模板已导入某个工具包辅助函数,请保留它;该模板就是对应类型中最精简且正确的插件。
阅读参考实现
官方包是规范示例:持续维护、经过测试、已发布,并且完全按照本指南所述的方式构建。开始设计之前,先阅读最接近你想法的示例——大多数问题(“如何获取悬停的项目?”“如何自动缩放堆叠图?”)都能在其中一个示例的文件里找到答案。在上游仓库中,它们位于 packages/lwc-plugin-*/src/。在其他仓库中,已发布的 @tradingview/lwc-plugin-* 包提供 README 和 dist/*.d.ts,但不包含源代码——请从 GitHub 获取源代码(使用原始 URL 或浅克隆,见上文);每个插件都只有少量文件。
| 正在构建…… | 阅读 |
|---|---|
| 任意自定义序列 | stacked-bars-series(最简单的完整示例),然后是 pretty-histogram-series |
| 每个数据点包含多个值的序列 | stacked-area-series、stacked-bars-series |
| 必须在间隙处断开或能够应对合并的折线/面积图 | hlc-area-series、brushable-area-series |
| 带有自身指针交互的序列 | brushable-area-series(BrushableAreaInteraction) |
| 替代内置序列的实现 | rounded-candles-series(与烛台选项和宽度匹配) |
| 固定像素高度的覆盖序列 | dual-range-histogram-series(scaleMode、keepPixelSeriesInView) |
| 带有轴标签和拖动功能的序列图元 | vertical-line |
| 位于序列或窗格后方的图像或装饰元素 | image-watermark(同时包含序列和窗格图元) |
| 锚定在窗格边缘或角落的文本标签 | anchored-text(共用一个核心的最精简序列与窗格组合) |
| 窗格上的 DOM 层,多窗格生命周期 | accessibility |
references/official-plugins.md 详细介绍了每个示例的演示内容。
仓库中的 plugin-examples/src/plugins/
(https://github.com/tradingview/lightweight-charts/tree/master/plugin-examples)还包含一组概念验证插件——工具提示、绘图工具、时段高亮、热力图等——适合拓展思路、获取灵感,但它们尚未发布,只是起步示例,不是应当遵循的标准。已正式发布的插件在此处的文件夹会重定向到对应的软件包。
按以下顺序阅读文档
以 Markdown 格式获取页面——在 URL 后附加 .md——而不是抓取 HTML:https://tradingview.github.io/lightweight-charts/docs/plugins/<page>.md。
https://tradingview.github.io/lightweight-charts/llms.txt 是所有页面的索引,docs_map.md 是带标题的同一索引,旁边的 lightweight-charts.d.ts 则是已发布的类型定义。
intro——三种插件类型及其附加方式。series-primitives或pane-primitives——适用于对应类型的视图/渲染器/生命周期约定:attached/detached、updateAllViews、paneViews、priceAxisViews、timeAxisViews、hitTest。custom_series——ICustomSeriesPaneView:priceValueBuilder、isWhitespace、renderer、update、defaultOptions、5.1 版的合并钩子以及 5.2 版的hitTest。canvas-rendering-target——你要在其上绘制的CanvasRenderingTarget2D及其两种坐标空间。pixel-perfect-rendering——为什么宽度和位置要通过dimensions/*辅助函数处理。
对于文档未涵盖的内容,请查阅类型定义,而不是生成的 API 参考——类型定义内容完整,而且你的代码正是根据 .d.ts 编译的。文档描述的是当前版本;如果你的对等依赖范围从更早版本开始,则实际可用的是已安装版本中的类型定义。
正确性检查清单
以下每一点都曾给已发布的插件带来问题。此处说明原因,是为了便于在新的场景中识别问题,而不只是避免旧场景中的问题。
绘图
- 需要清晰显示的内容(线条、柱形、边框)应在
useBitmapCoordinateSpace中绘制,并按horizontalPixelRatio/verticalPixelRatio缩放宽度;文本和布局应使用useMediaCoordinateSpace,单位为 CSS 像素。在 HiDPI 屏幕上混用两种坐标空间会导致模糊。dimensions/*辅助函数正是为此返回位图坐标。 CanvasRenderingTarget2D来自fancy-canvas,而非该库;工具包从custom-series/renderer-base重新导出此类型,因此自定义序列无需依赖它。- 恢复对上下文所做的任何设置(使用
save/restore,或重置globalAlpha、lineCap、虚线设置)。下一个渲染器会使用同一个上下文绘制。 priceToCoordinate返回null表示价格超出刻度范围。跳过该点或在此处断开线条;若绘制到NaN,整条路径都会丢失。
可见范围与坐标
- 只绘制
data.visibleRange;一个序列可能包含百万个点。对于线图和面积图,使用extendRange将范围向两端各扩展一根柱,这样首尾线段会延伸出窗格,而不是停在边缘。 - 可见范围之外的柱可能带有过期或缺失的
x。使用barCoordinate(bar, anchor, barSpacing)从可见锚点重新计算坐标——bar.time是逻辑索引,间距按索引计算。 - 只要另一个序列更早开始,或数据存在间隙,
bar.time(逻辑索引)就会与数组索引i不同。按索引查询选项和画笔范围时,使用逻辑索引。
自定义序列须知
priceValueBuilder返回图表用于自动缩放的绘图值,并将最后一个值作为当前值(价格线、最后值标签)。对于堆叠图,返回[min, max, total](stackedPlotValues);对于带状图,返回极值;对于单个值,返回[value]。- 它会在
setData时运行,早于任何渲染,因此普通视图此时还没有选项——图表尚未调用update——首次导入数据时会忽略percent、base或scaleMode。createOptionsAwareSeries会为构建器提供实时选项 getter,并在命名选项变化时重新导入数据;因此,官方序列将createXSeries工厂作为受支持的入口,并保留导出的类以供组合使用。 - 宿主不会向自定义渲染器提供空白数据:柱形只会跳过间隙,逻辑索引的跳跃也可能只是来自其他序列的时间戳。仅在序列自身的空白数据处断开线条,判断依据是序列接受的输入:
createWhitespaceSeries提供了一个GapCheck;将getConflationFactor(data)作为其minGap传入,这样宽度小于一个合并桶的空白区段会被吸收,而不会将每个桶都拆成单点线段。 conflationReducer和conflationFactor从 5.1 开始提供,渲染器的hitTest从 5.2 开始提供:序列命中测试(通过isHovered/hitTestData报告悬停的自定义序列,draw参数从 5.0 开始提供)也随之加入。将它们实现为可选扩展,除非确实需要,否则保持 peer 范围为^5.0.0,并在 README 中说明。- 当同一项持续处于悬停状态时,
hitTest必须返回同一个hitTestData对象:图表会按引用比较该对象;若每次都返回新对象,指针每移动一次,窗格就会重绘一次。 conflationReducer(a, b)必须返回属于你自己的类型、且能合理表示两项合并结果的数据项:堆叠值求和,OHLC 取最高价/最低价/最后值,汇率则取较晚的值。
基元细节
- 每当图表无法自行察觉更改时(例如选项、时间更新或已解码的图片),都要调用
requestUpdate()。图表不会轮询你的状态。 detached()必须撤销attached()执行的所有操作:监听器、DOM 节点、计时器和待处理的图片加载。附加和分离操作很常见(例如 React effect、窗格重排),泄漏会导致出现幽灵绘图和抛出“已释放”错误。- 窗格基元只会绘制在其所附加的窗格上;传给构造函数的窗格索引只是一个提示,可能不准确。应延迟解析实际窗格(首次绘制时,
context.canvas位于该窗格内),并按对象身份持有它——添加、移除或移动窗格时,索引会发生变化。 - 对于 DOM 覆盖层,请将其附加到
paneContentElement(pane),并确保覆盖层的布局独立于图表自身的表格单元格。
测试
分为三个层次,每个层次都能发现其他层次无法发现的问题。官方包在 tests/ 下都包含这三层;脚手架会搭建第一层。
- 单元测试(
tests/unit/*.spec.ts,node:test+chai):纯函数测试,例如堆叠计算、选项合并、数据填充和间隙检测。运行速度快,可通过仓库的pnpm test运行。 - 图形测试(
tests/graphics/*.js):绘制一种状态的页面;CI 会将截图与前一个构建版本进行比较。每个值得展示的选项都应对应一个用例,此外还应覆盖边缘情况:可见范围为空、内容已滚出视野、一段空白数据、数据合并、反转刻度。 - 交互测试(
tests/interactions/*.js):页面会通过异步的beforeInteractions(container)驱动图表,并在结果不符合预期时抛出错误——包括“是否已绘制”的像素采样、DOM 检查和命中测试。空白数据、更新、pop和指针行为都在此处测试。
在上游运行测试:先运行 pnpm build:prod 和 pnpm --filter "@tradingview/lwc-plugin-*" build,然后运行 pnpm test、pnpm e2e:graphics:plugins 和 GREP="lwc-plugin-<name>/" pnpm e2e:interactions。
发布和目录
在上游通过 pnpm plugins:validate 后,插件即可发布;也可以手动确认以下条件:名称采用 lwc-plugin-* 格式,设置 publishConfig.access: public,包含 lightweight-charts 同级依赖版本范围、lightweight-charts-plugin 关键字、完整的 lwcPlugin 块、含有 ## Installation(包含 ### npm 和 ### CDN 选项卡)及 ## Usage 的 README,以及 README 声明的演示页面。脚手架已满足所有条件。文档网站的插件目录会呈现 README,并为基于软件包构建的预览页面创建框架;references/package-contract.md 列出了相关字段、页面以及首个版本的变更日志规范(简短即可:列出 Added,不写 Fixed——因为目前还没有任何需要修复的问题)。
答复规范
协助开发插件时:
- 用一句话说明插件类型及其原因,或解释用户的想法如果采用另一种类型会失去什么。
- 指出所依据的工具包模块和官方插件。引用其模式前,先阅读该插件的文件。
- 根据本地类型定义核实每个库和工具包的名称;说明哪些名称无法核实。
- 编写脚手架自身脚本能够通过的代码:
pnpm typecheck、pnpm build,并且可通过pnpm dev在演示页面中查看。 - 如果更改涉及自动缩放、空白数据、可见范围或悬停,请指出能够发现回归的测试,并编写该测试。