T8是蚂蚁集团AntV技术栈下的一款文本可视化解决方案,采用声明式JSON Schema语法来描述数据解读报告的内容。作为一个专门为AI时代设计的可视化库,它在架构设计上有诸多值得深入研究的亮点。
开源源码阅读方法 与项目汇报相似,阅读开源源码应采用自顶向下、逐步求精 的方法:
先看宏观调用流程 - 理解整体架构和入口点
再深入具体实现 - 分析各个模块的细节
架构设计思考 整体Manager封装的缺失 我们项目中缺少统一的Manager封装,可以参考:
PluginManager模式 - 统一管理所有插件的注册和调用
ECharts的extension.ts - 在该文件中封装所有的Register操作
T8的简洁应用 - 核心流程只需要3-4个函数调用
Facade模式的应用 通过Facade模式统一对外接口,隐藏内部复杂性,让外部调用更加简洁。
文本可视化的服务化封装 将文本可视化封装为服务+组件 的组合:
服务层 :负责数据处理、Schema解析、插件管理
组件层 :负责UI渲染和用户交互
目标 :使其能够在各处复用
VISALL的UI组件编排设计 参考T8的Preact实现:
虚拟DOM组件 - 提升渲染性能
组件编排机制 - 支持灵活的界面组合
HIVIS也可以采用类似设计 - 统一的组件架构模式
Schema设计:逻辑抽离与AI友好 Schema+Spec架构模式 1 Schema (逻辑层) + Spec(表现层) = AI友好的架构
这种模式的核心优势:
逻辑抽离到Schema - 组件只负责渲染
AI友好设计 - 便于大模型理解和生成
多模态支持 - 图表+文本+表格的统一描述
本质上这和KAmis类似,都是通过DSL/Schema作为中间层。AI短期内无法直接生成可用的产品界面,但可以:
先生成DSL/Schema - 结构化描述界面需求
再通过解释器渲染 - 将Schema转换为可视化界面
Entity设计 包括验证方式(如zod)和UI表现的定义:
数据验证 - 确保输入数据的正确性
UI映射 - 将数据转换为可视化元素
交互处理 - 定义用户交互行为
组件库作为解释器引擎 与传统架构的对比:
ECharts模式 :绘制逻辑放在组件内部
T8模式 :绘制逻辑放在Schema中
这种模式变更带来的影响:
开发主体变化 :从业务开发人员维护 → AI维护
个性化能力 :能实现千人千面的定制化
对比HIVIS :需要思考如何适配这种新模式
CustomBlock机制 自定义块机制提供了强大的扩展能力:
自定义渲染逻辑 - 支持特定的业务需求
插件化架构 - 便于功能模块的独立开发
动态加载 - 按需加载自定义组件
Plugin机制:业务个性化实现 插件系统是实现业务个性化的关键:
标准化接口 - 统一的插件开发规范
动态扩展 - 运行时加载和卸载插件
隔离机制 - 插件间的解耦和独立
架构设计原则 通用机制提取 将通用的机制、类、框架提取到单独的仓库和包中:
EventEmitter - 事件处理机制
通用工具类 - 可复用的基础组件
框架层 - 标准化的开发框架
这种模块化设计能够:
核心架构:JSON Schema驱动的数据流 T8的核心设计理念是”数据即视图”,整个项目以JSON Schema为数据流主体,通过插件化架构实现功能扩展。
外层Text组件设计 1 2 3 4 5 export class Text extends EE { private spec : NarrativeTextSpec ; private pluginManager : PluginManager ; private parser : T8ClarinetParser; }
Text类作为整个渲染引擎的入口,承担了三个核心职责:
数据管理 :通过schema()方法接收NarrativeTextSpec数据
插件协调 :通过PluginManager管理所有可插拔模块
渲染控制 :支持一次性渲染和流式渲染两种模式
流式JSON支持:面向AI时代的设计 T8最前瞻的设计是对流式JSON输出的支持。通过封装clarinet库实现流式JSON解析器:
1 2 3 4 5 6 7 8 9 10 11 export const streamRender = (newJSONFragment: string ) => { this .parser .append (newJSONFragment); const result = this .parser .getResult (); if (result.error ) { options?.onError ?.(result.error ); } else { options?.onComplete ?.(result); this .schema (result.document as NarrativeTextSpec ); this .render (); } };
这种设计完美契合大模型逐步输出的工作模式,为实时数据可视化提供了基础支撑。
技术选型:Preact代替React 在包体积限制(压缩后75KB,gzip后25KB)的约束下,T8选择了Preact作为UI框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 import { h, render as preactRender } from 'preact' ;render ( ) { preactRender ( h (NarrativeTextVis , { spec, pluginManager : this .pluginManager , themeSeedToken : this .themeSeedToken , onEvent : this .emit .bind (this ), }), container, ); }
Preact保持了React的hooks、render等API,但显著降低了库的体积,这对于组件库开发是理想选择。
插件系统:描述符驱动的扩展架构 T8的插件系统采用了描述符(Descriptor)模式,通过统一的标记系统来管理和识别不同类型的可插拔模块。
描述符系统的设计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export interface PhraseDescriptor <MetaData > { key : string ; isEntity : boolean ; render?: ((value: string , metadata: MetaData ) => HTMLElement ) | HTMLElement ; tooltip?: ...; classNames?: ...; style?: ...; }export interface BlockDescriptor <CustomBlockSpec > { key : string ; isBlock : true ; className?: ...; style?: ...; render?: (spec: CustomBlockSpec ) => HTMLElement ; }
“描述符”这个命名比传统的”type”、”name”更加贴切,准确表达了其在系统中作为识别标记的作用。
PluginManager的统一管理 1 2 3 4 5 6 7 8 9 10 11 export class PluginManager { protected entities : Partial <Record <EntityType , PhraseDescriptor <EntityMetaData >>> = {}; protected customPhrases : Record <string , PhraseDescriptor <any >> = {}; protected customBlocks : Record <string , BlockDescriptor <any >> = {}; register (plugin: PluginType ) { if (isBlockDescriptor (plugin)) this .customBlocks [plugin.key ] = plugin; if (isEntityDescriptor (plugin)) this .entities [plugin.key ] = plugin; if (isCustomPhraseDescriptor (plugin)) this .customPhrases [plugin.key ] = plugin; } }
PluginManager统一管理三种类型的插件:实体短语(Entity)、自定义短语(Custom Phrase)、自定义块(Custom Block),实现了真正的功能解耦。
组件架构:榫卯结构与洋葱模型 T8的内部组件架构体现了经典的”榫卯结构”设计,通过插槽和组件化的方式构建复杂的可视化界面。这种设计也可以看作是一种洋葱模型,逐层封装不同的职责。
ContextProvider的洋葱模型 1 2 3 4 5 6 7 8 9 10 11 export const ContextProvider = ({ plugin, themeSeedToken, events, children } ) => { return ( <PluginProvider plugin ={plugin} > {/* 最外层:插件层 */} <ThemeProvider themeSeedToken ={themeSeedToken} > {/* 中间层:主题层 */} <EventProvider events ={events} > {/* 内层:事件层 */} {children} </EventProvider > </ThemeProvider > </PluginProvider > ); };
这种三层嵌套结构清晰地分离了插件管理、主题配置和事件处理三个关注点。
NarrativeTextVis的容器结构 1 2 3 4 5 6 7 8 9 10 const { headline, sections, styles, className } = spec;return ( <ContextProvider themeSeedToken ={themeSeedToken} plugin ={pluginManager} events ={events} > <Container > {headline ? <Headline spec ={headline} /> : null} {sections?.map((section) => <Section key ={section.key || v4 ()} spec ={section} /> )} </Container > </ContextProvider > );
层级关系 整个组件架构的层级关系非常清晰:
1 2 3 4 5 6 7 8 ContextProvider (全局上下文) └── Container (主容器) ├── Headline (标题 - 朴素短语序列) └── Sections (内容模块) └── Paragraphs (段落) ├── Phrases (短语序列 - normal类型) ├── Bullets (条目列表) └── CustomBlock (自定义块)
数据结构:从NarrativeTextSpec到原子组件 JSON Schema作为数据流主体 T8的JSON Schema定义了完整的数据结构层次:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "NarrativeTextSpec" : { "properties" : { "headline" : { "$ref" : "#/definitions/HeadlineSpec" } , "sections" : { "items" : { "$ref" : "#/definitions/SectionSpec" } , "type" : "array" } } } , "SectionSpec" : { "anyOf" : [ { "properties" : { "paragraphs" : { ...} } } , { "properties" : { "customType" : { ...} } } ] } }
数据流向清晰:NarrativeTextSpec → headline + sections → SectionSpec → paragraphs → PhraseSpec
标准建制:两种基本段落类型 T8定义了两种标准的段落”建制”:
Phrases(短语序列) :normal类型的文本段落
Bullets(条目列表) :有序或无序列表
1 2 3 4 5 6 7 8 9 10 11 export function Bullets ({ spec }: BulletsProps ) { const children = spec.bullets ?.map ((bullet ) => ( <Li key ={spec.key || v4 ()}> <Phrases spec ={bullet.phrases} /> {bullet?.subBullet ? <Bullets spec ={bullet?.subBullet} /> : null} </Li > )); const tag = spec.isOrder ? 'ol' : 'ul' ; return <Comp as ={tag} > {children}</Comp > ; }
原子组件:Phrase、Entity与Chart Phrase:朴素文本短语 Phrase是最基础的文本元素,支持丰富的样式定制:
1 2 3 4 5 6 if (isTextPhrase (phrase)) { if (phrase.bold ) defaultText = <Bold > {defaultText}</Bold > ; if (phrase.italic ) defaultText = <Italic > {defaultText}</Italic > ; if (phrase.underline ) defaultText = <Underline > {defaultText}</Underline > ; if (phrase.url ) defaultText = <a href ={phrase.url} > {defaultText}</a > ; }
Entity:关键信息实体 Entity是展示关键信息的核心元素,预定义了10种类型:
1 2 3 4 5 6 7 "EntityType" : { "enum" : [ "metric_name" , "metric_value" , "other_metric_value" , "contribute_ratio" , "delta_value" , "ratio_value" , "trend_desc" , "dim_value" , "time_desc" , "proportion" ] }
每个实体都包含丰富的元数据:
1 2 3 4 5 6 7 8 9 "EntityMetaData" : { "properties" : { "entityType" : { "description" : "实体类型标记" } , "origin" : { "description" : "原始数据" , "type" : "number" } , "assessment" : { "description" : "衍生指标评估参数" } , "detail" : { "description" : "明细数据,用于弹框展示" } , "sourceId" : { "description" : "变量来源ID" } } }
Chart:轻量级数据可视化 Chart在T8中不是独立的可视化组件,而是作为实体的辅助表现形式,提供”一看就懂”的数据展现。
以Proportion图表为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 export const renderProportionChart = (container: Element, config: ProportionChartConfig ) => { const { data = 0 } = config; const chartSize = getElementFontSize (container); const proportion = Math .max (0 , Math .min (1 , data)); const r = chartSize / 2 ; const svg = createSvg (container, chartSize, chartSize); svg.append ('circle' ).attr ('cx' , r).attr ('cy' , r).attr ('r' , r).attr ('fill' , '#CDDDFD' ); const endAngle = proportion * 2 * Math .PI ; const arcPath = arc (r)(r, r, endAngle); svg.append ('path' ).attr ('d' , arcPath).attr ('fill' , '#3471F9' ); };
通过SVG路径直接渲染,舍弃了复杂的视图对象和交互动画,专注于信息传递的本质。
事件处理:完整的冒泡机制 T8实现了完整的事件冒泡机制,每个层级都支持click、mouseenter、mouseleave事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const handleClick = ( ) => { onClick?.(spec?.value , metadata); onEvent?.('phrase:click' , spec); };const onClick = ( ) => { onEvent?.('paragraph:click' , spec); };const onClick = ( ) => { onEvent?.('narrative:click' , spec); };
这种设计确保了用户交互的精确捕捉和处理。
设计思想:大模型友好的架构 “数据即视图”的理念 T8的JSON Schema设计(442行完整定义)让人联想到SVG的”语言描述系统”。通过声明式的配置而非命令式的编程,降低了维护门槛,特别适合AI时代的自动化需求。
为AI流式输出优化 流式JSON解析器的设计完全考虑了大模型逐步输出的特点:
支持增量解析:通过append()方法接收JSON片段
错误容错:提供完整的错误处理机制
渐进渲染:解析完成后立即渲染,无需等待完整数据
类型安全与自动约束 通过TypeScript类型定义自动生成JSON Schema,传递给LLM作为输出约束:
1 ts-json-schema-generator -f tsconfig.json -p src/index.ts -t NarrativeTextSpec
这种设计确保了大模型输出的格式正确性,实现了”数据驱动的升级版”。
架构优势与启发 设计亮点
高度模块化 :插件系统实现了真正的功能解耦
数据驱动 :JSON Schema作为单一数据源,便于管理和传输
扩展性强 :通过描述符模式支持任意类型的内容扩展
性能优化 :Preact + 流式渲染,适合大数据量场景
AI友好 :流式支持和声明式配置迎合了AI时代的开发需求
对其他项目的启发 对于类似的可视化项目,T8的设计提供了很好的参考:
统一的容器-插槽模式 :ContextProvider + PluginManager + Component
标准化的数据接口 :JSON Schema作为通用语言
灵活的扩展机制 :通过descriptor实现插件化
清晰的层级结构 :从全局上下文到原子组件的逐层细化
这种设计既保证了整体的统一性,又为差异化需求留出了足够的扩展空间。
资料