HT for Web简称HT提供了涵盖通用组件、2D拓扑图形组件以及3D引擎的一站式解决方式。正如Hightopo官网所表达的我们希望提供:Everything you need to create cutting-edge 2D and 3D visualization. 这个愿景从功能上是个相当长的战线,从设计架构上也是极具挑战性的,事实上HT团队是很保守的,我们从不贪多图大,仅仅做我们感觉自己能得更好,能给用户综合体验更佳的功能,在这样理念驱动下我们慢慢形成了这种愿景,慢慢实现了几个有意义的里程碑,慢慢积累下了不少图形组件设计上的创新和经验,我不知道这个系列会写多少篇,或许永远也不会结束,也没有系统的提纲规划。想到什么就写什么,仅仅希望文章能启示有兴趣的同学对图形组件设计更深的思考就足够了。
讨论前先设定话题的边界。HT是基于HTML5的图形组件库,因此文章的案例很多其它会涉及HTML和JavaScript语言。但并不局限于Web前端。设计思想上相同适用于不论什么GUI语言平台。
完整的前端设计是须要考虑到后台载入并发等因素。但本系列更側重于纯client图形组件,不涉及网络通讯部分的思考,比如近期阿里无线前端招聘让谈谈:讲讲输入完网址按下回车,到看到网页这个过程中发生了什么。这是个能讨论出非常多方方面面,让你了解面试者的好话题,但这里讨论的话题会与下面keyword更为相关:企业应用、Single Page Application、重client交互、监控、MV*等。
如Linus大神所言:Talk is cheap, show me the code. 因此我选择在话题展开之前,先用HT来扩展定制几个应用案例。以便大家了解HT组件及其扩展设计思路。
熟悉Flex的程序猿应该都了解Tour de Flex这个包罗万象的大杂烩,当中的网络监控拓扑Network Monitor特别其动画切换效果一直给我非常深印象,这里不可能有篇幅实现完整样例。我们仅尝试实现其展示CPU和MEM的界面部分。
实现的终于效果如上图所看到的,模型数据就两个数值,一个代表CPU占用率,一个代表内存占用率,左側通过HT的图形组件GraphView自己定义了矢量图形展示。右上角自己定义了属性页PropertyView的两单元格的Renderer。右下角两个Slider可拖动改变CPU和MEN值。
此样例麻雀虽小五脏俱全,三个部分分别採用三种方式实现了自己定义组件,同一时候不同组件共享同一数据源,在呈现的基础上还支持桌面和移动端的Mouse和Touch的交互。还有不同终端屏幕的组件布局功能。
业务上须要在占用率小于40%时呈现律师。40%-70%时显示黄色,超过70%时呈现红色,因此定义了例如以下getColor的工具函数:
getColor = function(value) {
if (value < 40)
return '#00A406';
if (value < 70)
return '#FFCC00';
return '#A60000';
};
PropertyView上採用的最基础和原始的方式,通过Canvas画笔进行单元格的自己定义绘制,在注冊PropertyView时重载drawPropertyValue函数就可以实现单元格自己定义Renderer的绘制
drawFunc = function(g, value, x, y, w, h){
g.fillStyle = '#A1A1A3';
g.beginPath();
g.rect(x, y, w, h);
g.fill();
g.fillStyle = getColor(value);
g.beginPath();
g.rect(x, y, w * value / 100, h);
g.fill();
ht.Default.drawText(g, value + '%', '12px Arial', 'white', x, y, w, h, 'center');
};
propertyView.addProperties([
{
displayName: 'CPU',
drawPropertyValue: function(g, property, value, rowIndex, x, y, w, h, data, view) {
drawFunc(g, data.a('cpu'), x, y, w, h);
}
},
{
displayName: 'MEM',
drawPropertyValue: function(g, property, value, rowIndex, x, y, w, h, data, view) {
drawFunc(g, data.a('mem'), x, y, w, h);
}
}
]);
Slider拉条部分直接在HT封装的组件之上应用,因而无需接触到最底层的Canvas画笔绘制,仅须要在onValueChanged时更新leftBackgroud拉条左側颜色就可以,事实上也能够通过重载Slider的getLeftBackground函数实现:
formPane.addRow(['CPU', {
slider: {
step: 1,
onValueChanged: function(){
var value = this.getValue();
node.a('cpu', value);
this.setLeftBackground(getColor(value));
},
value: node.a('cpu')
}
}], [50, 0.1]);
formPane.addRow(['MEM', {
slider: {
step: 1,
onValueChanged: function(){
var value = this.getValue();
node.a('mem', value);
this.setLeftBackground(getColor(value));
},
value: node.a('mem')
}
}], [50, 0.1]);
GraphView部分採用了《HT全矢量化的图形组件设计》文章介绍的HT自己定义的矢量方式来实现图形效果。这样的方式介于以上两种扩展方式之间。须要自己定义绘制效果,但通过HT提供的矢量格式,用户可採用较为直观易读的JSON格式来描写叙述图形。并通过数据绑定的方式实现模型数据与界面呈现的关联,避免如第一种自己定义renderer的方式。即须要接触究竟层绘制函数。同一时候业务逻辑代码与绘制代码混杂一起不易维护的问题。
ht.Default.setImage('server_image', {
300,
height: 200,
comps: [
{
type: "roundRect",
rect: [3, 5, 291, 189],
background: "#E3E3E3",
gradient: "linear.northeast",
shadow: true
},
{
type: "text",
text: "CPU",
font: "16px Arial",
rect: [20, 45, 59, 41]
},
{
type: "text",
text: "MEM",
font: "16px Arial",
rect: [20, 108, 59, 41]
},
{
type: "rect",
rect: [82, 55, 145, 22],
background: "#A1A1A3"
},
{
type: "rect",
rect: {
func: function(data) {
return [82, 55, 145 * data.a('cpu') / 100, 22];
}
},
background: {
func: function(data) {
return getColor(data.a('cpu'));
}
}
},
{
type: "rect",
rect: [82, 117, 145, 22],
background: "#A1A1A3"
},
{
type: "rect",
rect: {
func: function(data) {
return [82, 117, 145 * data.a('mem') / 100, 22];
}
},
background: {
func: function(data) {
return getColor(data.a('mem'));
}
}
},
{
type: "text",
font: "16px Arial",
rect: [240, 49, 53, 31],
text: {
func: function(data) {
return data.a('cpu') + '%';
}
},
color: {
func: function(data) {
return getColor(data.a('cpu'));
}
}
},
{
type: "text",
font: "16px Arial",
rect: [240, 108, 47, 39],
text: {
func: function(data) {
return data.a('mem') + '%';
}
},
color: {
func: function(data) {
return getColor(data.a('mem'));
}
}
}
]
});
以上代码注冊了名为server-image的图片,绑定了attr上的mem和cpu的两个属性,因此做完这些手脚架的基础工作后,应用人员仅仅须要构建ht.Node对象。通过node.setImage(‘server-image’)就可以实现该图元在GraphView上呈现’server-image’描写叙述的矢量效果,而且PropertyView、Slider和GraphView三个组件都通过node的attr上的cpu和mem来显示界面,这样当后台获取到採集的实时数据后,仅仅须要更新到node的attr上的cpu和mem属性。则界面上的全部组件就会自定更新显示:
node = new ht.Node();
node.setName('SERVER');
node.setImage('server_image');
node.a({
cpu: 30,
mem: 70
});
dataModel.add(node);
当然实际应用中并不须要拉条改变CPU和MEN值。这些值一般通过后台採集实时自己主动更新仅作为呈现。但有了前端这些组件的一站式支持,我们不须要连接后台也能够非常方便在client进行模拟測试。有了这种机制我们就能够分离模块一步步測试,比如我们如今不须要连接server也能够測试矢量描写叙述定义的是否正确,数值改变后绿黄红的业务颜色更新是否正确,各个组件的数据同步是否正常,Mouse和Touch交互能否正常操作。界面在不同设备屏幕上显示是否正常等等,这些纯client组件的封装工作都做到位后,你就能够安心连接后台数据进行測试了。
见过太多客户出问题时仅仅会说:界面显示不正确。这种问题描写叙述全然无法定位根源,究竟时后台数据库问题。网络通讯问题,解析数据问题,设置模型问题还是组件封装问题?这也是MVC/MVP/MVVM存在的另外一个层面的意义。MV*除了事件派发数据绑定外,能更好的进行呈现、模型和业务逻辑的分工分割进行独立測试的重要意义,即可TCP/IP七层协议的分类,每一个协议层都应该确保正确实现自己的协定约定,而且每一层可进行独立的測试,这才是可维护可扩展的系统,因此对于HT客户遇到问题时,我们一般也是一层层的帮忙梳理找根源,假设矢量描写叙述没问题呈现出错,那是HT组件库的问题,假设模拟到Node上的attr数据显示正确,那就去找找实际执行后台通信解析后的数据是否正确的设置到模型上,这样一步步的分析非常easy就能定位到问题的根源。
以上三种扩展方式各有利弊。我将在下篇中继续展开分析,本篇结尾上一段该样例在移动终端的执行操作视频