乐趣区

关于前端:数据可视化探索之-SpreadJS

这是第 98 篇不掺水的原创,想获取更多原创好文,请搜寻公众号关注咱们吧~ 本文首发于政采云前端博客:数据可视化摸索之 SpreadJS

一、前言

数据可视化蕴含三个分支:迷信可视化、信息可视化、可视剖析。

1、迷信可视化次要关注的是三维景象的可视化,如建筑学、气象学、医学或生物学方面的各种零碎。重点在于对体、面以及光源等等的真切渲染,或者甚至还包含某种动静成分。

2、信息可视化是一种将数据与设计联合起来的图片,有利于集体或组织简短无效地向受众流传信息的数据表现形式。

3、可视剖析学被定义为由可视交互界面为根底的剖析推理迷信,将图形学、数据挖掘、人机交互等技术交融在一起,造成人脑智能和机器智能优势互补和互相晋升。

可视化剖析中可视化报表是重中之重,把大量的数据疾速的展现进去,并且灵便的进行数据操作,其中操作包含数据的筛选、关联、联动、钻取,文案的查问,替换、款式设置,条件格局的注入实现多色阶、图标集、数据条、反复值,以及公式的插入,跨表联动等。SpreadJS 在解决可视化剖析报表中最为突出,上面咱们只针对可视化剖析中 SpreadJS 所表演色做探讨。

二、报表可视化难点

互联网电商服务业行业,平时会解决大量商业信息和用户信息,客服和数据分析师,是报表次要用户人员。

客服平时每天都会解决大量的工单填报、客诉注销、第三方平台原始数据的导入、统计汇总、审核审批、电签、散发等工作。平时大部分工作信息的载体都是 Excel,每天服务器须要解决海量的文档,因为 Excel 文档自身数据难以提取入库,模板更新时也不不便第一工夫散发到操作员处,难以整合到 Web 页面里等问题。

数据分析师须要拿到数据进行汇总,算出各个商品品牌的销售额,最大值、最小值、平均值等,标识出有价值的数据。抓取无效数据,制作成报表给 boss。

针对以上的场景,报表可视化能够总结出以下几个难点:

1. 并发

公司客服人数泛滥,几千人同时在线重度操作,业务流转周期短、数据量大,所以对服务端并发性能耗费是很大的。能够在后盾用 Apache POI 来提取和批改 Excel 数据、并执行其中的公式计算等。这样会遇到两个性能瓶颈:

1)须要频繁地上传、下载文档,服务器带宽接受了很大的压力;

2)所有 Excel 解析、提取的操作都在服务器端,频繁的 IO 操作让服务器不堪重负。

以上两个性能点,在目前的架构下很难冲破,这也是重构我的项目时最具挑战性的需要点之一。当然硬堆服务器配置也是一个解决方案,但无奈解决其它的一些问题,并且也会带来运维的压力。

2. 对 Excel 操作和 兼容性要求较高

新零碎如果不能让大家疾速上手应用,以这个我的项目用户的体量,培训老本将无奈接受。而且要可能间接导入已有的 Excel 报表模板,否则再次开发或设计所有 Excel 报表也是难以承受的。

3. 报表格局灵便多变

针对不同的业务场景,报表的模版也是变幻无穷。因而不须要研发的染指,操作员的设计和填报都能够在页面上实现显得尤为重要。

4. 反对公式计算

因为波及到商品、订单、成本核算、财务统计等模块,对计算公式的品种和性能要求较高。

5. 工作流中的数据文档

以前零碎的工作流,波及到 Excel 报表时,要么数据会先在服务端和 Excel 模板进行拼装,要么零碎依据门路找到文件服务器的 Excel 文件,而后流转到对应环节。一些新的业务模块,甚至还只能用邮件进行文件传输。

这个过程会产生大量的文件,对文件服务器的带来了很大压力,后盾也不得不定期做批量的数据拆分和保护。这次降级零碎须要解决这个问题。

三、思考如何选型

首先,选型的第一步就是搞清楚市面上具体有哪些产品供咱们抉择,对于目前市面上能集成到零碎中,反对这种在线表格文档编辑的产品有不少,大体我把他们分了两类。

1. 云文档类型产品

这种产品有很多,相似 WPS、石墨文档、office online。它们自身具备较高的完成度,曾经帮用户实现了包含在线协同内的简直所有性能,甚至也反对肯定水平的二次开发而且能够私有化部署。但问题在于通常这类产品封闭性比拟强,二次定制开发还是绝对比拟艰难,且不够轻量。受权形式也多以按工夫、按并发量、用户数量等形式受权,价格昂贵,不是很适宜咱们的须要。

2. 控件类型产品

像 LuckySheet、Handsontable、SpreadJS 这种就是规范的控件了,它们都是纯前端表格控件,都反对 Excel 的性能个性和 json 数据绑定。

LuckySheet 是国内的 MIT 开源软件,能够拿来商用。但在我调研时它才刚上线 1、2 个月,并且不像 React 这种有某个大厂来背书,所以不可能拿来用到咱们的正式我的项目里。截止目前曾经过来了 1 年,陆续推出了 QQ 群、论坛等交流平台,但仍显单薄。

Handsontable 是国外的一个商业表格控件,据说二次开发坑较多,但对咱们来说最大的问题是它没有中文反对团队。

SpreadJS 是葡萄城公司的商业 Excel 表格控件,乏味的是我发现在 V2EX 的 LuckySheet 下方评论区中,LuckySheet 的作者也说 SpreadJS 是行业标杆。它反对导入包含公式、图表、款式、条件格局在内的绝大部分 Excel 个性(不反对宏)。并且最惊喜的是,它的操作界面是一个残缺的 Excel 界面,齐全纯 JS 开发的,用 json 进行模板和数据交互。同时 SpreadJS 也有对应的售后反对团队,技术问题能够工作日期间随时电话、论坛交换,相干的材料包含视频、文档、示例、API 手册也都十分丰盛,甚至还能够请他们的技术顾问来公司培训。对于像咱们这种工期短、开发工作比拟沉重的项目组,的确能节约大量的精力,升高了危险。

<img src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/33ceddebf1e345d0951cc98229a20262~tplv-k3u1fbpfcp-zoom-1.image” style=”zoom: 50%;” />

图片起源:SpreadJS 在线 Excel 编辑器

那么什么是控件?为什么要用控件?

援用维基百科
在计算机编程当中,控件 (或 部件,widget 或 control)是一种图形用户界面元素,其显示的信息排列可由用户扭转,例如视窗或文本框。控件定义的特点是为给定数据的间接操作(direct manipulation)提供独自的互动点。控件是一种根本的可视构件块,蕴含在应用程序中,管制着该程序处理的所有数据以及对于这些数据的交互操作。

依照我本人了解,控件就是只提供了基本功能,反对二次开发的功能模块。控件绝对 依赖更轻 可塑性 更好,并且也有对应的 开发文档和 API,是面向开发者的根底性能包,便于按需要来定制性能。

四、SpreadJS 需要解决方案和劣势

1. 并发

因为 SpreadJS 是数据和模板拆散的设计,填报人员只须要在页面上实现填报。提交时能够只提交填报好的数据 json 即可,服务器再也不必集中解析所有 Excel 文件了。带宽耗费也间接节约了一半。

2. 对 Excel 操作和兼容性要求较高

在外部试用时,财务和客服的小姐姐们反馈,应用体验跟 Excel 简直齐全一样 ,不须要再特意培训。而且咱们本人的大量 Excel 报表能够间接导入 进去(二次开发后也能够实现批量和近程导入),包含图表、公式、表格款式等等一系列元素都能够间接导入线上操作。

3. 报表格局灵便多变

设计人员能够间接在线设计,或者把 Excel 设计好的报表,拿到 Web 端,做好 数据绑定,提交保留成 json 格局即可(Spread JS 的 ssjson 格局包含 Excel 文档的所有信息)

4. 反对公式计算

反对了 450 多种(Excel 一共 480 多种)公式 ,还能够本人开发扩大自定义公式,对财务也够用了。同时还 反对所有 Excel 的援用操作,比方跨 sheet 援用、相对援用、函数命名信息之类。

5. 工作流中的数据文档

根本脱离了对文件的依赖,所有流程状态和依赖的数据都能够在数据库中记录,文件服务器只须要保留大量的模板文档即可(其实模板数量不大时能够间接放到数据库里,不过咱们有现成的文件服务器)。这里节约了咱们 90% 文件服务器的空间开销,运维的小伙伴中午都要笑醒。

五、深刻 SpreadJS

重点来了,其实最让我这个前端开发者感兴趣的就是 SpreadJS 的一些底层设计、以及对内存、性能平衡性的优化。对此我做了很多调研和学习,好在这方面材料不难找,经常能够在葡萄城官方论坛的公开课版块遇到一些相干的技术分享。下边是本人理解学习到的内容,做个简略总结:

1. 渲染性能

性能必定是每个深度表格控件用户最放心的问题。咱们的数据量经常达到好几千条,而且 Excel 不不便分页(波及前端的公式计算汇总),所以选型期间很放心。起初发现想多了,SpreadJS 能够轻松加载 50 万条数据加载耗时 200 ms 左右(官网性能演示示例只能加载 5 万,咱们本人扒下来测的 50 万)。起初深刻理解才晓得,解决这个问题,他们的思路是这样的:

  • 实时渲染 + Double buffering (翻译成双层缓存?):

用 Canvas 渲染表格局部,并且只渲染用户看到的局部内容,这就实现了加载 1000 行和加载 100000 行数据速度都很快,性能相差不大的景象。

而 Double buffering 是为了解决间断渲染的连续性体验问题,也能够进一步晋升渲染速度。这个名词预计听过的人少,但应该人人都体验过,Double buffering 在图形学里,个别称作双缓冲,实际上的绘图指令是在一个缓冲区实现,这里的绘图十分的快,在绘图指令实现之后,再通过替换指令把实现的图形立刻显示在屏幕上,这就防止了呈现绘图的不残缺,同时效率很高。在游戏里其实很常见,当咱们主控的人物在地图上奔跑时,游戏引擎会依照人物挪动方向实时加载和渲染地图,这就防止了一次性加载超大地图时那漫长的期待。

图片起源:葡萄城公开课【SpreadJS 性能优化】

  • 稠密数组:

SpreadJS 对表格数据的存储优化采纳了稠密数组的数据结构。稠密数组罕用来优化二维数组(比方棋盘、地图等场景)的内存占用,但它有个天生的缺点,就是拜访性能慢。

所以过后针对这个疑难,我给它做了压力测试,百万级别的遍历耗时 200 多 ms。性能能够满足咱们的需要。

2. 计算引擎

据官网介绍来看,公式引擎其实是蕴含了两大实现的局部,一个是计算逻辑系统、一个是援用零碎。

  • 援用零碎

Excel 中公式的计算都是依赖于某些原始数据的,比方 C1 援用 B1、B1 又援用 A1等等,SpreadJS 把这部分性能封装的曾经十分原生化了,基本不须要开发者操心(除非有援用回溯等非凡需要)。

Excel 中 有间接援用、跨 Sheet 表单援用、绝对 / 相对援用、命名信息的援用、table 行列公式的援用、跨工作簿援用等等(没列举完,感兴趣的同学能够自行搜寻学习)。SpreadJS 的 runtime 是在网页端,因而跨 Workbook 援用就别想了,至多目前必定没反对。

  • 计算逻辑

SUM、IF、MATCH、VLOOKUP 这种能输出到单元格里的计算公式,用起来就像是一个个的小“逻辑包”,目前 SpreadJS 有 460+ 种原生的公式函数,而 Excel 只有 490+ 种,并且 SpreadJS 能自定制公式,应用体验与原生公式一样。

对于底层实现,实际上通过多个版本的迭代,这些公式早已不是一个个独立的“逻辑孤岛”了。公式的实现在底层有大量的形象和复用,据说新版本在性能晋升的同时,代码量比老版本有显著精简,这对前端工程打包也是比拟敌对的。

对于嵌套公式计算的实现,SpreadJS 在底层建设起了 AST 树来解析用户设置公式的计算逻辑,从官网示例的代码来看,公式底层建设了一套 Expression,并且有对应的 public 接口可供调用,如图:

<img src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9438273241b647f2833ceaa91e10ba3c~tplv-k3u1fbpfcp-zoom-1.image” style=”zoom:50%;” />

图片起源:【SpreadJS 公式构造树形展现】

  • 性能

首先,作为一个前端技术,咱们能够先从公式计算的技术要求上来剖析性能可能呈现的瓶颈以及造成的影响。咱们在开发时用到了大量的用户事件、脏数据、联动等性能,所有这些性能确保正确运行的一个重要前提,就是 必须能确保随时能够拿到正确的计算结果,那么最间接的实现思路就是让公式以高优先级、同步的形式来执行完计算

大家都晓得,多线程能够帮忙分担计算压力,然而先抛开设计和实现难度不说,即使反对了 Web Worker,JavaScript 严格来说也只能算是一个单线程语言,因为它的 Web Worker 子线程齐全受主线程管制,并且主线程无奈被阻塞挂起。所以即便引入了 Web Worker,也无奈确保上边提到的同步执行。

通过以上剖析,能够看出公式计算性能的局限性,取决于 JavaScript 的计算能力。我找了一张相干的图片,能够直观反映 Node.js 的计算能力(Node.js 是 V8 引擎,公认最快的 JS 引擎)

<img src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e803b1e04882483db69ef620131c75f2~tplv-k3u1fbpfcp-zoom-1.image” style=”zoom:50%;” />

图片援用自《深入浅出 Node.js》

而 SpreadJS 官网给出了一组公式的计算数据,参考如下:

<img src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/868feeb1203c4f37887f125685947cae~tplv-k3u1fbpfcp-zoom-1.image” style=”zoom:50%;” />

据咱们测试,以上计算性能靠近原生 JS 的计算性能,SpreadJS 在这方面的优化曾经非常靠近物理极限了。目前在咱们的利用场景中,这个计算性能曾经足够应用,但不排除当前会呈现海量的数据和公式的计算需要,而在这方面官网也给出了相干解决方案,参考这里。

据说,官网还在进一步开发缓存技术,来实现公式计算的分块缓存:即便援用链上有值发生变化,也不须要计算整个援用链的公式。听起来很弱小,思路也靠谱,但愿早点推出。

3. 款式零碎

Excel 的款式零碎非常复杂,边框、字体、对齐、数据格式、条件格局等等每一个性能点都有非常灵活宏大的实现,刚开始理解 SpreadJS 时,我也被它的 Style 类惊呆了,除了我能设想到的边框、背景、字体、对齐等这些能“看失去”的,居然也有单元格类型、数据格式、表格按钮、下拉、水印这类货色。不由得感叹 Style 太重了,如果定制大量的单元格款式,内存和性能必定都不好。不过理论利用中倒也没发现瓶颈,原来这里采纳了分层构造来设计,如图:

<img src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dbf1f0f0ea5b460e9a6ee14e3883e139~tplv-k3u1fbpfcp-zoom-1.image” style=”zoom:50%;” />

图片起源:葡萄城公开课【SpreadJS 性能优化】

六、SpreadJS 怎么用?

1. 渲染表格

图 6.1-1 绑定数据和公式

首先获取全局 spread 对象,spread 是整个表格的主体,spread 又分成多个 sheet。SpreadJS 初始化完结都会返回一个 spread 对象。

  • vue 版本 spread 对象
 <gc-spread-sheets @workbookInitialized='spreadInitHandle($event)' />
  methods:{spreadInitHandle: function (spread) {this.spread = sprea},
  }
  • 绑定数据, 绑定公式
 tableDataBind() {
    // 数据源,能够从后盾申请拿到
    var dataSource = {
        // 留神这里加了一层 bindPath,用于映射表格的绑定门路
        bindPath_table: [{
            c1: 100,
            c2: 90,
            c3: 30,
            c4: 40
        }, {
            c1: 88,
            c2: 66,
            c3: 55,
            c4:100
        }, {
            c1: 30,
            c2: 89,
            c3: 100,
            c4: 40
        },{
            c1: 40,
            c2: 66,
            c3: 88,
            c4: 40
        }]
    };
    // 表格绑定和单元格绑定数据源,须要用 SpreadJS 的 CellBindingSource 包装一下
    var spreadNS = GC.Spread.Sheets;
    var dataSource1 = new spreadNS.Bindings.CellBindingSource(dataSource);
    var table2 = this.activeSheet.tables.add("tableName", 0, 0, 1, 5, spreadNS.Tables.TableThemes.light6);
    table2.showFooter(true);
    table2.autoGenerateColumns(false);
    var c1 = new spreadNS.Tables.TableColumn(1);
    c1.name("语文");
    c1.dataField("c1");
    var c2 = new spreadNS.Tables.TableColumn(2);
    c2.name("数学");
    c2.dataField("c2");
    var c3 = new spreadNS.Tables.TableColumn(3);
    c3.name("英语");
    c3.dataField("c3");
    var c4 = new spreadNS.Tables.TableColumn(4);
    c4.name("化学");
    c4.dataField("c4");
    var c5 = new spreadNS.Tables.TableColumn(5);
    c5.name("共计");
    table2.bindColumns([c1, c2, c3, c4, c5]);
    table2.bindingPath("bindPath_table");
    // 设置公式
    table2.setColumnDataFormula(4, "=[@语文]+[@数学]+[@英语]+[@化学]");
    table2.setColumnFormula(4, "=SUBTOTAL(109,[共计])");
    // 设置容许单元格的内容超出单元格,与绑定无关
    this.activeSheet.options.allowCellOverflow = true;
    // 绑定 dataSource
    this.activeSheet.setDataSource(dataSource1);
    this.spread.resumePaint();},

<img src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/35b5949707104cf986f6eb2f0251bfff~tplv-k3u1fbpfcp-zoom-1.image” style=”zoom:50%;” />
图 6.1- 2 函数名和函数码映射表

2. 渲染条件格局

渲染条件格局:数据渲染实现只能保证数据能失常显示进去,然而这还不能满足数据分析师的需要,还要显著展现无效数据譬如:最大值,最小值标红,进度条展现一个变动状态,图标展现回升还是降落,双色阶,三色阶,等,具体怎么实现?

  • 图标集:成果如图

  • 实现代码
iconset() {
    var activeSheet = this.activeSheet;
    var iconSetRule = new GC.Spread.Sheets.ConditionalFormatting.IconSetRule();
    // 演示 demo 先写死区域
    iconSetRule.ranges([new GC.Spread.Sheets.Range(0,0, 5, 5)]);
    // IconSetType 图标记的类型:箭头,圆圈和 execl 买通的,excel 有哪些这这边就反对哪些
    iconSetRule.iconSetType(GC.Spread.Sheets.ConditionalFormatting.IconSetType.threeArrowsColored);
    var iconCriteria = iconSetRule.iconCriteria();
    iconCriteria[0] = new GC.Spread.Sheets.ConditionalFormatting.IconCriterion(
      true,
      GC.Spread.Sheets.ConditionalFormatting.IconValueType.number, 
      60
    );//(<60)
    iconCriteria[1] = new GC.Spread.Sheets.ConditionalFormatting.IconCriterion(
      true,
      GC.Spread.Sheets.ConditionalFormatting.IconValueType.number,
      90
    );//(60<= <90)
    iconCriteria[2] = new GC.Spread.Sheets.ConditionalFormatting.IconCriterion(
      true,
      GC.Spread.Sheets.ConditionalFormatting.IconValueType.number,
      90
    );//(>=90)
    iconSetRule.reverseIconOrder(false);
    iconSetRule.showIconOnly(false);
    activeSheet.conditionalFormats.addRule(iconSetRule);
},
  • 进度条:成果如图

  • 实现代码
dataBar(){
    var activeSheet = this.activeSheet;
    activeSheet.conditionalFormats.addDataBarRule(
        GC.Spread.Sheets.ConditionalFormatting.ScaleValueType.number,0,// 最小数
        GC.Spread.Sheets.ConditionalFormatting.ScaleValueType.number, 100,// 最大值
        "orange",// 色彩
        [new GC.Spread.Sheets.Range(0,0, 5, 4)]
    );
},
  • 反复值:成果如图

  • 实现代码
duplicateValue() {
    var activeSheet = this.activeSheet;
    var style = new GC.Spread.Sheets.Style();
    style.backColor = "yellow";
    style.foreColor  = "red";
    var ranges = [new GC.Spread.Sheets.Range(0,0, 5, 4)];
    activeSheet.conditionalFormats.addDuplicateRule(style, ranges);
},
  • 蕴含文本 6 的单元格: 成果如图

  • 实现代码
includeText() {
    var activeSheet = this.activeSheet;
    var style = new GC.Spread.Sheets.Style();
    style.backColor = "red";
    var ranges = [new GC.Spread.Sheets.Range(0,0, 5, 5)];
       activeSheet.conditionalFormats.addSpecificTextRule(GC.Spread.Sheets.ConditionalFormatting.TextComparisonOperators.contains, "6", style, ranges);
},
  • 综合以上实现后果如图

七、写在最初

本文次要介绍了本人在数据可视化方向的一些摸索,针对一些筹备做市场大盘以及邮件订阅报表,线上协同合作,可视化剖析等方向的同学有肯定的帮忙。

因篇幅较长,所波及概念性的货色比拟多,难免会呈现谬误,心愿大家多多斧正,谢谢大家!

举荐浏览

通过自定义 Vue 指令实现前端曝光埋点

H5 页面列表缓存计划

招贤纳士

政采云前端团队(ZooTeam),一个年老富裕激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员形成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端利用、数据分析及可视化等方向进行技术摸索和实战,推动并落地了一系列的外部技术产品,继续摸索前端技术体系的新边界。

如果你想扭转始终被事折腾,心愿开始能折腾事;如果你想扭转始终被告诫须要多些想法,却无从破局;如果你想扭转你有能力去做成那个后果,却不须要你;如果你想扭转你想做成的事须要一个团队去撑持,但没你带人的地位;如果你想扭转既定的节奏,将会是“5 年工作工夫 3 年工作教训”;如果你想扭转原本悟性不错,但总是有那一层窗户纸的含糊… 如果你置信置信的力量,置信平凡人能成就不凡事,置信能遇到更好的本人。如果你心愿参加到随着业务腾飞的过程,亲手推动一个有着深刻的业务了解、欠缺的技术体系、技术发明价值、影响力外溢的前端团队的成长历程,我感觉咱们该聊聊。任何工夫,等着你写点什么,发给 ZooTeam@cai-inc.com

退出移动版