mcpField
D
data-story-charts

data-story-charts

github.com/phantomtonyt创意与媒体24164 次安装4 周前更新
代码仓库

关于 data-story-charts

Data Story Charts 是用于把数据讲成短视频故事的图表动画技能,适合制作数据解读内容的创作者。它让数值、图表揭示和注释按叙事顺序出现,并强调数据来源与准确性。

主要功能

  1. 01折线、柱状、堆叠与计数图
  2. 02随图表变化的数值计数器
  3. 03按叙事顺序揭示数据
  4. 04关键数据点注释标记
  5. 05平滑坐标轴缩放动画

适用场景

  1. 01将数据变化制作成短视频
  2. 02突出趋势中的峰值或转折
  3. 03用注释解释关键数字

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/phantomtonyt/motion-design-skills --skill data-story-charts

安装后在 Claude Code 中输入/data-story-charts确认已加载。

详细介绍

数据故事图表

一张图表,按照故事的展开顺序逐步构建和呈现。数字是主角:顶部的大号计数器、随数字变化的折线或柱形,以及说明变化原因的标注图钉。整体应像解释性新闻报道:清晰、沉稳,并有一两个戏剧性时刻。

选项

选项默认值备注
格式竖屏 1080×1920或横屏 1920×1080
时长15–20 s每个数据节拍约 3–5 s
图表折线图line(随时间变化)、bars(比较)、stacked(拆解总量)、counter(一个大数字)
基调新闻编辑风editorial(默认,浅色纸张)、dark(藏青色、发光强调色)、punchy(节奏更快,大事件时切换颜色)

简报(规划前回答)

  1. 观众应该记住的那个数字或变化是什么?
  2. 数据是什么(粘贴数据或提供文件)?单位、时间范围、来源是什么?
  3. 意外之处是什么:激增、骤降、交叉,还是隐藏成本?
  4. 哪 2–4 个时刻值得用标注图钉说明?
  5. 结尾文案是什么?

只使用用户提供的真实数字,或根据其数据计算得出的数字。切勿编造数据。如果某个数字是估算值,请在屏幕上注明。

阅读节拍表(与节拍列表一同编写)

对于每个节拍,写明观众必须理解的一件事(“机器人不会飞”“缓存未命中”)以及内容保持可读的时长。一次只呈现一条信息。文字时长下限:标签至少停留 0.8 s;句子完全显示后,每个词约需 0.3 s。如果文字来不及读完,就删减文字,不要加快节奏。

动效规范

  • 动效应有因果关系:屏幕上的每个动作都有原因(点击、坠落、数据包到达),接下来的动作由此引出。
  • 为整段视频选定一个主要运动方向,并始终保持一致。其他方向用于传达含义:向上表示成功或结论,缩小表示“全貌如下”。
  • 一个场景的退出应为下一个场景的进入做铺垫:沿相同轴线、朝相同方向、以相近速度运动。让一个物体贯穿场景切换,而不是交叉淡化。
  • 图示、图表或文字不要添加无意义的填充动效(漂浮、呼吸、摇晃)。如果没有内容需要移动,就保持静止;一次刻意的镜头漂移就足够。
  • 缓动:进入时使用平滑的缓出(power2.out 到 power4.out),退出时使用更快的缓入(耗时约为进入的 75%)。进入动效最多 0.8 s。同一组动效的总错开时间最多 0.5 s。back.out 仅用于小幅弹出;不要使用弹性或弹跳效果。
  • 关键时刻一次只动一个元素;观众的视线一次只能跟随一个元素。
  • 在重点揭晓前加入 0.3–0.75 s 的静止停顿。

样式规则(始终遵守)

  1. 每个场景只使用一种图表类型,并突出一组数据。其他数据系列显示为灰色。
  2. 按时间或故事顺序逐步呈现图表:折线从左向右绘制,柱形按顺序增长;内容尚未讲到时不要提前显示。
  3. 大号计数器显示当前值,并随图表逐步变化(使用等宽数字)。
  4. 当数据超出坐标轴范围时,在屏幕上重新缩放坐标轴(对比例和标签进行缓动插值)。这通常是视频中最精彩的时刻。
  5. 用标注图钉标出 2–4 个关键点,并配上简短标签。
  6. 每个标签的对比度都要达到 3:1。网格线和刻度标签应使用低调的深灰色,而不是浅灰色。

默认值和覆盖规则

优先级:用户的品牌规范或具体要求,其次是明确请求,最后才是这些默认值。

  • 新闻编辑风:纸张 #f7f4ee、墨色 #16213a、网格 #e3ded4、刻度标签 #5b6270、强调色 #e4572e、正向 #047857、警示 #a16207、负向 #c2410c。
  • 深色:背景 #0f1724、墨色 #f2f5f9、网格 #1f2b3d、标签 #9fb0c8、强调色 #38d6e0。
  • 字体:Inter(计数器使用 900 字重、字号 140–170 px;说明文字使用 800 字重、字号 56–64 px;标签使用 600 字重、字号 26–30 px)。

设置(一次性)

node -v && ffmpeg -version
npx hyperframes init my-video --non-interactive --resolution portrait && cd my-video   # landscape for 16:9

路线选择

需求HyperFrames 中的 SVG + GSAP(默认)RemotionHyperFrames 中的 D3
线条、柱形、计数器、标记点是是是
数千个点或地图投影开销大开销大是(根据时间 t 绘制)
仅使用 Node 和 FFmpeg 运行是是是

布局

  • **9:16:**提示标签位于 y 285,计数器位于 y 350(150 px),图表区域为 x 120–960、y 520–1000,坐标轴标签位于下方,说明文字位于 y 1110–1250。
  • **16:9:**计数器位于左上方,图表区域为 x 200–1720、y 260–880,说明文字位于左下方或图表右侧。
  • 标记点位于对应数据点上方,并由虚线连接。确保它们不遮挡计数器和说明文字。

如何制作动画(支持安全跳转)

将数据写成函数或数组,并根据一个时钟值 t 绘制整张图表。用一个补间动画驱动时钟,并在其 onUpdate 中重绘:

const clock = { t: 0 };
tl.to(clock, { t: DURATION, duration: DURATION, ease: "none", onUpdate: () => draw(clock.t) }, 0);
draw(0);

draw(t) 会根据 t 重建截至当前时刻的折线路径,移动标记点和计数器,并根据当前坐标轴最大值(其本身也是 t 的函数)定位标记点和坐标轴标签。由于每个像素都由 t 决定,因此 HyperFrames 跳转到任意帧时,渲染结果都相同。

动效方式
折线显现根据截至 t 的样本绘制路径(每秒数据约 30 个样本)
折线下方的区域将同一路径闭合至基线,使用不透明度为 12% 的强调色
柱形增长高度从 0 开始补间,间隔 0.08 s,使用 power3.out
计数器显示当前值;小于 10 时保留 1 位小数,大于等于 10 时显示整数
坐标轴缩放yMax(t) 在 1–1.5 s 内从旧最大值平滑过渡到新最大值,同时更新刻度标签
标记点弹出在对应时刻使用 back.out(2.5),缩放比例从 0.5 变为 1
强调关键数字出现时,计数器以 1.12 的比例缩放并往返

增添吸引力

  • 首帧就显示计数器、坐标轴和问题。
  • 在半秒内同时触发惊喜时刻的坐标轴缩放、标记点和说明文字变化。
  • 用一个小标记(圆点、图标或角色)沿着折线前端移动,让观众知道该看哪里。
  • 区域填充和前端数据点上的柔和强调光晕能增加纵深感,又不会显得杂乱。
  • 说明文字用简短句子,并将数字设为强调色(“随后:120 m。”)。
  • 每个定格帧都能独立传达一个事实。

入门模板

<div id="root" data-composition-id="main" data-start="0" data-duration="10" data-width="1080" data-height="1920" style="background:#f7f4ee;font-family:Inter,sans-serif">
<svg viewBox="0 0 1080 1920" width="1080" height="1920" style="position:absolute;inset:0">
  <g id="grid" font-size="26" fill="#5b6270"></g>
  <line x1="120" y1="1000" x2="960" y2="1000" stroke="#16213a" stroke-width="4"/>
  <path id="area" fill="#e4572e" opacity=".12"/><path id="line" fill="none" stroke="#e4572e" stroke-width="9" stroke-linejoin="round" stroke-linecap="round"/>
  <circle id="dot" r="14" fill="#e4572e"/>
</svg>
<div style="position:absolute;left:60px;top:350px;font:900 150px/1 Inter,sans-serif;color:#16213a;font-variant-numeric:tabular-nums"><span id="num">0</span></div>
</div>
<script>
const D = 10, X0 = 120, X1 = 960, Y0 = 1000, Y1 = 520;
const data = (t) => 2 * t + (t > 6 ? 40 * ((t - 6) / 4) ** 2 : 0);          // replace with the user's data
const yMax = (t) => 20 + 60 * Math.min(1, Math.max(0, (t - 6.5) / 1.2));    // axis rescales at the surprise
const px = (t) => X0 + (X1 - X0) * (t / D), py = (v, m) => Y0 - (Y0 - Y1) * (v / m);
const NS = "http://www.w3.org/2000/svg", grid = document.getElementById("grid");
const ticks = [0, 0.5, 1].map((f) => { const l = document.createElementNS(NS, "line"), x = document.createElementNS(NS, "text");
  l.setAttribute("x1", X0); l.setAttribute("x2", X1); l.setAttribute("stroke", "#e3ded4"); x.setAttribute("x", X1); x.setAttribute("text-anchor", "end"); grid.append(l, x); return { f, l, x }; });
function draw(t) {
  const m = yMax(t); let d = "";
  ticks.forEach(({ f, l, x }) => { const y = Y0 - (Y0 - Y1) * f; l.setAttribute("y1", y); l.setAttribute("y2", y); x.setAttribute("y", y - 10); x.textContent = Math.round(m * f); });
  const n = Math.max(2, Math.round(t * 30));
  for (let i = 0; i <= n; i++) { const s = (t * i) / n; d += (i ? "L" : "M") + px(s).toFixed(1) + " " + py(data(s), m).toFixed(1); }
  document.getElementById("line").setAttribute("d", d);
  document.getElementById("area").setAttribute("d", d + "L" + px(t) + " " + Y0 + "L" + X0 + " " + Y0 + "Z");
  const dot = document.getElementById("dot"); dot.setAttribute("cx", px(t)); dot.setAttribute("cy", py(data(t), m));
  document.getElementById("num").textContent = Math.round(data(t));
}
const tl = gsap.timeline({ paused: true }), clock = { t: 0 };
tl.to(clock, { t: D, duration: D, ease: "none", onUpdate: () => draw(clock.t) }, 0);
draw(0);
window.__timelines["main"] = tl;
</script>

添加包含 GSAP(https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js)和来自 Google Fonts 的 Inter 字体的 <head>,并添加 html,body{margin:0;width:1080px;height:1920px;overflow:hidden}。在同一时间轴上添加提示标签、说明文字和标记点。

可配置参数

format · duration · chart · tone · data(数组或函数)· units · pins(时间、标签、颜色)· captions(时间、文本)· rescaleAt。

避免

  • 屏幕上的每个数字都必须来自用户数据或注明出处的来源。在构图旁保留一份简短的事实清单(数字、来源)。任何示意性内容都要在屏幕上标明。
  • 将虚构或“示意性”数字当作真实数据呈现。
  • 同时突出多个系列,或使用彩虹色盘。
  • 使用浅灰色标签(无法通过对比度检查,在手机上也难以辨认)。
  • 图表杂项:3D 柱形、粗重网格线,以及有直接标签就不需要的图例。
  • 坐标轴突然跳变而没有平滑过渡。平滑缩放,让观众看清变化过程。
  • 让标记点或说明文字遮挡折线上的关键点。

故障模式

表现原因修复方法
渲染时线条跳动或闪烁根据挂钟时间或逐帧随机值绘制在 onUpdate 中只根据时钟 t 绘制
图钉标签溢出胶囊框预估的胶囊框宽度过小在 28 px 字号下,宽度 ≈ 36 + 字符数 × 17(全大写时 × 23);或者等字体加载后再测量
引导语和计数器重叠两者位置太近引导语基线与计数器顶部之间留出 30+ px
惊喜效果显得平淡没有缩放、图钉或说明文字变化在 0.5 秒内依次加入这三种变化
计数器文字抖动数字为比例宽度font-variant-numeric: tabular-nums
刻度线对比度检查未通过标签颜色太浅浅色背景用 #5b6270,深色背景用 #9fb0c8

验证

npx hyperframes check
npx hyperframes snapshot --at <start, each pin, mid-rescale, end>
npx hyperframes render -o renders/<name>.mp4
  • 屏幕上的数字与对应时刻的数据一致
  • 缩放平滑,缩放过程中标签仍清晰易读
  • 图钉和说明文字不会遮挡线条的关键点
  • 所有文字都在安全区域内;对比度检查通过
  • 最后一帧点明核心结论

最终渲染前的自我检查

在以下方面为快照集打 1–10 分:吸引力(第一秒)、手机屏幕宽度下的可读性、动态效果质量、构图、准确性,以及“静音测试”(观众能否在无声的情况下用一句话概括内容?)。只修改得分为 7 分及以下的项目,重新截取快照并重复检查,直到所有项目都达到 8 分或以上。如果可以,请安排单独的只读审核来评分,避免给自己的作品打分。

不要这样做应当这样做
在两个繁杂的布局之间交叉淡化先让旧内容退出,再引入新内容;或者让一个对象贯穿其中
同时让多个元素移动错开动作,每个时刻只突出一个焦点
用漂浮或摇晃填补停顿保持静止,或者让角色行动
为了追求“活力”而添加粒子、彩虹渐变或 3D 翻转添加更有力的事件:揭示、撞击或镜头移动
为了赶时间而仓促显示文字精简文字

交付

  • 海报:定格后的最终图表帧,并将其作为第 0 帧嵌入:ffmpeg -ss <t> -i renders/<name>.mp4 -frames:v 1 poster.jpg,然后运行 ffmpeg -i renders/<name>.mp4 -i poster.jpg -filter_complex "[0:v][1:v]overlay=enable='eq(n,0)'" -c:a copy renders/<name>-final.mp4。
  • 配上包含关键数字和数据来源的说明文字。
  • 告知用户数据节点,以及修订时需要更改的数字或图钉。

参考案例

评论

支持 Markdown