Langfuse 图表架构宣言以「数据→预备层→可视化层」单向流水线构建通用图表体系【免费下载链接】langfuse Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse本文以 web/src/features/widgets/chart-library/ARCHITECTURE.md 为核心骨架结合 Langfuse 开源仓库中 chart-library 的实际源码与单元测试系统讲解这套图表体系的设计原则、分层架构、视觉与交互方向以及当前实现进度与演进路线。读完你将理解 Langfuse 仪表盘图表为何能处理「未知形状、任意时间尺度、几十上百条序列」的数据并掌握新增图表类型时应遵循的架构纪律。一、开篇这份架构宣言要解决什么问题Langfuse 的仪表盘Dashboard与 Widget 体系需要渲染形态各异的数据未知形状、任意时间范围或尺度、单条序列或上百条序列、干净或嘈杂、稀疏或过载的数据。如果为每种数据形态单独写一套特殊处理逻辑代码会迅速腐化——同一个值在三处被格式化就可能在三个地方得到三种不同、甚至错误的结果。因此 ARCHITECTURE.md 提出一个明确的目标展示任何数据都要清晰可读并且不针对单个图表做特例化处理而是构建一条统一的、可适配的流水线把一切数据都喂进去。它的口号是data ──▶ PREPARE ──▶ VISUALISE ──▶ pixels (decide) (render)预备层Prepare负责一切「决策」解析、单位、日期/数字/时长的格式化、颜色、序列顺序、图例摘要、Top-N、坐标轴类型与尺度、空值处理。它必须是纯函数、不依赖 React、并且被单元测试覆盖。可视化层Visualise只负责「渲染」把已准备好的、可直接呈现的模型映射到图表库recharts上不再做任何决策。如果某个组件试图回头重新格式化或重新推导数据说明这个决策放错了层。数据流严格单向可视化层永远不重新决定预备层已经解决的事情。这是整个图表体系最重要的「唯一规则」。二、十大原则「show whatever data」信条宣言用十条原则定义了这套体系的行为准则每一条在源码中都有对应的实现支撑。1. 单一归一化模型作为枢纽One normalized model is the pivot每个数据源只在前端一次性归约到一个类型化、与呈现无关的统一形状下游所有环节都只面向这个形状永远不需要知道数据来自哪里。在 chart-props.ts 中这个枢纽形状就是DataPointexport interface DataPoint { time_dimension: string | undefined; dimension: string | undefined; metric: number | null | ArrayArraynumber; }注意metric的三个语义null表示「这里没有测到任何东西」桶存在但指标没有诚实的值例如零事件的均值时间序列图把它渲染为缺口gap绝不强制转换为0。metric为null且没有dimension的点是纯粹的「桶标记」bucket marker它让桶留在时间轴上但不贡献任何序列。2. 呈现决策只做一次且发生在上游单位、小数位、阈值、颜色、标签、时间格式都在预备层解析成「可直接渲染」的值渲染器只读取派生属性绝不重新派生。最典型的例子是时间轴Chart.tsx 中明确注释时间轴格式化不在这里决定原始的time_dimension值直接流向可视化层由prepareTimeAxis这一个预备器统一格式化——一处真相来源让每个图表都以相同方式格式化时间LFE-10549。3. 数据没声明的就推断它假定输入是欠声明、混乱的。每个缺失的部分都要有默认值从数值推断类型、推断时间字段、从间距推断桶粒度bucket granularity、从标签推断序列名称。prepareTimeAxis中的parseChartTimestampprepareTimeAxis.ts是这一原则的集中体现它只在值确实「看起来像」时间戳时才解析为Date——epoch 毫秒数、ISO 字符串、ClickHouse 的YYYY-MM-DD[ T]HH:MM:SS格式可选裸日期。对字符串刻意保持保守分类 x 轴会喂入任意标签包括裸整数 1、47、20241230 这类 run 名称所以绝不把裸数字强转成 epoch 日期也绝不回退到new Date(任意字符串)。4. 响应式地适应尺度而非预测式不要自己计算「好看的刻度」让尺度scale去放置刻度然后格式化你被给到的间距。按数量级和粒度选择数字/日期/时长格式通过测量标签来给坐标轴定尺寸而不是猜测。useChartTickBudgetuseChartTickBudget.ts用ResizeObserver实测图表盒子的宽高估算能放下多少个刻度标签横向约 64px/标签 56px 轴边距纵向约 28px/标签返回maxTicks与maxYTicks。预备器prepareTimeAxis再把这个预算换算成真正的刻度间隔与标签。这正是「测量标签不要猜测」ARCHITECTURE.md 第 4 条——代码注释里还特意指出数字时间轴预算假设 ~64px/标签这对实体名entity name来说小了 5 倍所以分类轴必须走宽度感知width-aware的等距抽稀路径。5. 混乱与过载是「头等」但「有界」的情况成本在源头处约束约每个像素一个点而不是在渲染器里。对无界序列用显式、可逆的限额cap约束在有意义的地方加一个 others 汇总绝不悄悄截断而不说明。prepareVisibleSeriesprepareVisibleSeries.ts就是「序列上限」预备接缝按加性指标的量级有限指标值之和给维度排序保留前maxSeries默认DEFAULT_MAX_RENDERED_SERIES 25无有限值的序列排最后同名并列时按名称排序以保持跨重载的确定性。高基数 group-by按 id/name/user 分组动辄产生上百条序列全部绘制既不可读图例/提示框里上百个一次性条目又病态地慢——recharts 每次悬停都会为每个图形元素重新解析状态成本随序列数近似平方增长。返回结构PreparedSeries给出visible/total/hidden让 UI 能诚实地标注「top N of M」而不是静默截断对应 LineChartTimeSeries.tsx 中的SeriesOverflowNote。6. 空值有意义——说清楚是哪种区分「这里没有数据」缺口、「跨过去连接」与「零」。这个选择必须是显式的大多数「脏数据看起来不对」的 bug 都藏在隐式的空值处理里。MissingBucketValuechart-props.ts把选择显式化为两种export type MissingBucketValue zero | gap;zero加性指标计数、求和——零事件发生了诚实的值就是真实的0线保持连续gap非加性指标avg/min/max/百分位——不存在诚实的值单元格变成null线断开而不是在缺口上伪造趋势。该选择是指标聚合的属性由知道它的调用方决定而不是图表决定默认gap——绝不发明数字。7. 按意义分组而非按位置共享同一单位的序列共享同一坐标轴单位不匹配的序列自动获得第二根轴——通过派生键自动完成让布局能适配未知数量的序列。8. 失败时给引导而不是空白框当数据无法绘制时要检测为什么缺时间字段没有数值字段数据为空并提供下一步动作。在 Chart.tsx 中EMPTY_STATE_CHART_TYPES专门覆盖了「所有点都是 null/零时 recharts 原始组件只画出空轴/空网格」的三类时间序列图LINE_TIME_SERIES、AREA_TIME_SERIES、BAR_TIME_SERIES。因为时间序列查询可能把一个空范围「稠密化」成充满 null 的桶行recharts 仍会拿到数据只是画出空轴——所以要「fail into guidance」而不是空白框LFE-14333。判定逻辑用 isChartDataEmpty.ts且真实0永远不被视为空加载中跳过检查避免首帧闪现「No data」。调用方还可以传入自定义emptyState用于表格条带这类高度太矮、会裁剪默认卡片文本的场景。9. 交互即可读性密集数据靠悬停、聚焦、跨共享时间轴同步的十字线变得清晰——而不只是靠丢弃数据。「悬停时精确」胜过「默认就稀疏」。这在代码里体现为syncIdchart-props.ts同一仪表盘时间线上、传了相同syncId的图表会显示同步的悬停十字线 提示框——悬停一个其他所有图表上的垂直时间标记一起移动。10. 把「怎么画」与「画什么」分离只在结构变化时才重建渲染配置新的数据 tick 只应交换数组。输入稳定且被 memo 化昂贵的协调reconciliation很少发生。Chart组件整体用React.memo包裹Chart.tsx使父组件重渲染例如仪表盘查询调度器在加载时 bump 版本号不会在图表输入未变时去协调整个 recharts 子树。而各时间序列组件内的groupedData、timeAxis、series等全部用useMemo按依赖缓存。三、视觉与交互方向V1–V8 设计准则架构规则回答的是「决策应该放在哪里」而这一节回答「绘制时应该决定什么」。贯穿一切的立场是数据承载视觉重量画框保持安静——网格、坐标轴、标签被压到低对比度让序列的形状与颜色成为视线焦点始终追求高 contenteditable="false">【免费下载链接】langfuse Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考