可视化业务开发框架解析
为什么做这个框架
- 降低可视化开发的门槛,解决新人上手成本太高的问题
- 提升开发效率
- 提升质量下限
可视化需求和常规前端需求的区别是什么?
可视化组件,会涉及事件、布局、交互、状态等众多内容,不像UI组件,更像一个整合多个UI组件的业务功能模块。
可视化组件中的View,才更像是常规的UI组件库。
代码
business-visualization-template
设计理念
人类设计,代理编码(Human design, Agents code!)
7层人机交互AI Workflow + 软件工程
聚焦问题领域和解决方案领域
这个流程和每个环节要做的事情,必须深入思维的习惯里面去,成为原则性的内容。
自顶向下,逐步求精,逐渐固化
越到后面的环节越具体,直至产生原子函数、方法等;
抽象程度也是从大到小变化的。
- 确定哪些是固定不变的、哪些是会随着需求变动的
- 不变的部分就形成固化的流程、文档和代码(微内核架构的微内核部分,这部分是我们需要专注做的基建部分,就是基础框架和核心机制)
- 让AI和人聚焦于变化的部分,缩小上下文和心智负担(即需要通过具体的插件实现的业务功能部分,比如柱状图)
AI + Template(填空题模式)
尽量让AI去填空
流程
- 需求分析
- 项目分析
- 方案设计
- 模块设计
- 代码实现
- 性能优化
- 可靠性保障
Q&A
这个程序设计怎么来的?
结合业务实践经验 + 参考其他开源可视化项目的设计。
数据相关的处理代码怎么拆分
Data:原始数据的管理,数据预处理、视图元素和原始数据做一个链接(比如getGraphicElement这个方法),这个在3D里面经常用到
Model:数据驱动的核心
Controller里面也有一部分内容
Data的定位
业务无关的数据处理??
需要挂载在Model上
中间层代理???
为什么视图元素和原始数据要做一个链接?
项目中一般存在四类数据:
option:配置项
original data:业务的原始数据
layout data:布局相关的数据
view data:渲染相关的数据
一般是把2、3、4提出来做个链接,其目的是为了解决查询、管理问题,提高可维护性,可以理解为是一个微型数据库,算是个cache。
因为我们经常会遇到这个场景:
改了原始数据,需要找到对应的图形元素,更新视觉效果。这就设计model-view的查询。
反过来也可以查询,即通过view获取model,不过这种场景只能通过get方法获取数据,且只能获取数据的副本,不能修改它,否则就违背了单向数据流的原则,容易产生意外的影响。
view data和layout data为什么不能放一起?
O-M-L-V
计算公式+原始数据,获得layout
layout作用于model
layout放view的问题:视图层直接修改了model对象,强耦合了???不是这个原因吧??
Layout怎么处理
Layout算是视图数据下属的一个子分类
也是放Model下的Data里面管理
如何触发事件,进而更新数据?
遵循:注册-绑定-触发 的流程,通过Controller进行调度。
可以看下bar的hover事件的实现示例:
Controller中注册事件
1
2
3
4
5
6// 悬停状态变更事件
this.on('barHover', (data: BarChartData | null, index: number | null) => {
console.trace('controller触发了barHover');
this.updateBarStyles();
});View中在
bindInteractionEvents()方法中给元素绑定事件1
2
3
4
5
6
7this.barGroup.on('mouseover', (e: ElementEvent) => {
const targetRect = e.target as Rect;
const index = this.bars.indexOf(targetRect);
if (index !== -1) {
this.handleInteraction('barHover', { index, event: e });
}
});统一的机制代理触发Controller事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* 处理交互事件 - 子类可选实现
* @param eventType 事件类型
* @param eventData 事件数据
*/
protected handleInteraction(eventType: string, eventData: any): void {
this.emit('interaction', {
type: eventType,
data: eventData,
target: eventData.target || null
});
// 触发具体的事件
this.controller.emit(eventType, eventData);
}
Controller中处理了很多业务,是否合理?
不合理,目前的代码中,Controller既处理业务事件又处理UI事件。
需要重构下:
- 数据操作移至 model:将数据相关方法移到 BarChartModel
- 样式处理移至 view:将 updateBarStyles() 移到 BarChartView
- 简化 controller:只保留事件协调和渲染调度逻辑
- 添加服务层:复杂的样式计算可抽离为专门的 style service
Element和View的区别是什么?
Element负责图形元素的状态管理
当前的问题
循环依赖
BarChartView 在构造函数中持有了 Controller 的实例,而 Controller 也持有了 View 的实例,这形成了循环依赖 (Controller <-> View)。虽然在很多 MVC 实现中这很常见,但它会增加组件间的耦合度,使得代码更难理解和单独测试。
改进方案: 可以让 View 和 Model 一样,继承自 Eventful,成为一个独立的事件发布者。Controller 则在外部监听 View 的事件,而不是将自身实例注入到 View 中。这样可以形成更单向的数据流:View -> Controller -> Model。
TODOs
- 结合系统架构师的内容,再细化下流程、每个环节的约束
- 针对每个模块,增加特定的约束
- 解决方案库的建设,比如基于/不基于坐标轴的、基于日历的等等,搞成几套固定的方案,然后让AI辅助我们思考和权衡
- 优化Controller,明确其职责边界
- test模块
如何快速学习掌握这个框架
- 用Gemini提问的方式,学习这个框架
- 用这个框架做一个新需求,或者将一个老的项目用这个框架重构