前端进阶七-二维可视化库

数据可视化库概述

https://zhuanlan.zhihu.com/p/149398216

D3--可视化库的jquery

安装

npm install d3

全局导入或者部分导入

//全局导入
import * as d3 from "d3";
//部分导入
import {scaleLinear} from "d3-scale";

D3 API,共八种:

第一章:核心函数:选择、过渡、数组、数学、请求、格式化、本地化

第二章:比例尺:线性比例尺、恒等比例尺、乘方比例尺、对数比例尺、量化比例尺、分位数比例尺、临界值比例尺

第三章:可缩放矢量图形。SVG元素、线生成器、径向线生成器、面积生成器、弧生成器、符号生成器、弦生成器、对角线生成器、轴、刷子

第四章:时间。时间格式、时间间隔、时间计数

第五章:布局。布局有捆绑布局、弦布局、簇布局、力布局、层次布局、直方图布局、包布局、分区布局、饼布局、堆叠布局、树布局、矩形树布局

第六章:地理。地理有地理路径、经纬网生成器、球面数学运算、标准抽象投影、标准投影、流

第七章:几何:有四叉树、凸包、多边形和泰森多边形

第八章:行为:包括拖拽和缩放

初始化布局

this.force = d3.forceSimulation().alphaDecay(0.1)
							 	.force("link",d3.forceLink().distance(100))
								.force('collide',d3.forceCollide().radius(()=>30))
								.force("charge",d3.forceManyBody().strength(-400))
this.force.nodes(nodes)
					.force('link',d3.forceLink(links).distance(150));
					.alpha(1);
					.restart();
force.on('tick',()=>{
  nodes.forEach(node => {
    if (!node.lock){
      d3.select(self.nodes[node.uid].attr)
    }
  })
  links.forEach(link => {
    d3.select(self.links[`${link.source.uid}_${link.target.uid}`])
    	.attr('x1',()=> link.source.x)
    	.attr('y1',()=> link.source.y)
    	.attr('x2',()=> link.target.x)
    	.attr('y2',()=> link.target.y)
  })
})
render(){
  const { width,heigth, nodes, links, scale, translate, selecting, grabbing} = this.props.store;
  return (
  	<svg id="svg" ref="svg" width={width} height={height}
      className={cn({
        grab: !selecting && grabbing,
        grabbing: !selecting && grabbing
      })}
      >
      <g id="outg" ref="outg" transform{`translate(${translate})scale(${scale})`}>
        <g ref="lines" className="lines">
          links.map(link => (
          	<line
              key = {`${link.source.uid}_${link.target.uid}`}
              ref = {child => this.links[`${link.source.uid}_${link.target.uid}`] = child}
              x1 = {links.source.x}
              y1 = {links.source.y}
              x2 = {links.target.x}
              y2 = {links.target.y}
              />          
          ))
        </g>
        <g ref="nodes" className="nodes">
          {
            nodes.map(node => (
            	<Node
                key = {node.uid}
                node = {node}
                store = {this.props.store}
                addRef = {child => this.nodes[node.uid] = child}
                />
            ))
          }
        </g>
      </g>
    </svg>
  )
}
class Node extends Component {
  render() {
    const { node, addRef, store } = this.props;
    const { force } = store;
    return (
    	<g className="node"
        ref={child => {
          this._node = child;
          addRef(child);
        }}
        transform={`translate(${node.x || width / 2}, ${node.y})`}
        >
      	<g id={node.nodeIndex}>
          // 节点图片dom
        </g>
        {
          node.locked && (
          	<Lock
              x = {10}
              y = {10}
              release = {()=>{
                node.fixed = false;
                node.locked = false;
                node.fx = null;
                node.fy = null;
                force.alpha(0.3).restart();
              }}
              />
          )
        }
      </g>
    )
  }
  
  componentDidMount() {
    this._node._data_ = this.props.node;
    d3.select(this._node)
    	.on('click',d=>{
       // code
    	})
  }
}
let startTime = 0;
this.drag = d3.drag()
							.on('start',(d)=>{
  							start = (new Date().getTime());
  							d3.event.sourceEvent.stopPropagation();
  							if (!d3.event.active){
                  this.force.alphaTarget(0.3).restart();
                }
  							d.fx = d.x;
  							d.fy = d.y;
							})
							.on('drag', d => {
  							this.grabbing = true;
  							d.fx = d3.event.x;
  							d.fy = d3.event.y;
							})
							.on('end', d => {
  							const nowTime = (new Date()).getTime();
  							if (!d3.event.active){
                  this.force.alphaTarget(0);
                }
  							if (nowTime - startTime >= 150){
                  
                }
							})
const self = this;
const outg = d3.select('#outg');
this.zoomObj = d3.zoom()
								 	.scaleExtent([0.2,4])
									.on('zoom',()=>{
  									const = transform = d3.event.transform;
  									self.scale = transform.k;
  									self.translate = [transform.x,transform.y];
  									outg.attr('transform',transform);
									})
									.on('end',()=> {
  
                  })

添加元素

<html>
    <head>
       <meta charset="utf-8">
       <title>D3测试</title>
       <script type="text/javascript" src="d3.js"></script>
    </head>
    <body>
    <script type="text/javascript">
        d3.select("body").append("p").text("New paragraph!");     
    </script>
    </body>
</html>

操作svg

var w = 500;
var h = 50;

// 数据
var dataset = [ 5, 10, 15, 20, 25 ];

// 创建SVG容器
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 50);

// 创建圆
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle");

// 根据数据设置每个圆的属性
circles.attr("cx", function(d, i) {
  return (i * 50) + 25;
})
  .attr("cy", h/2)
  .attr("r", function(d) {
  return d;
});

条形图

<body></body>
<style>
  div .bar {
    display: inline-block;
    width: 20px;
    margin-right: 2px;
    background: teal;
  }
</style>
const dataset = [ 5, 10, 15, 20, 25]

d3.select("body").selectAll("div")
		.data(dataset)
		.enter()
		.append("div")
		.attr("class","bar")
		.style("height", function(d) {
  		return (d * 5) + "px";
		})

散点图

const w = 500;
const h = 100;

var dataset = [
  [5,20],[480.90],[250,50],[100,33],[330,95],
  [410,12],[475,44],[25,67],[85,21],[220,88]
]

var svg = d3.select("body")
						.append("svg")
						.attr("width",w)
						.attr("height", h)

svg.selectAll("circle")
		.data(dataset)
		.enter()
		.append("circle")
		.attr("cx",function(d){
  		return d[0]
		})
		.attr("cy",function(d){
  		return d[1]
		})
		.attr("r",function(d){
  		return Math.sqrt(h-d[1])
		})
svg.selectAll("text")
		.data(dataset)
		.enter()
		.append("text")
		.text(function(d) {
  		return d[0] + "," + d[1];
		})
		.attr("x",function(d){
  		return d[0]
		})
		.attr("y",function(d){
  		return d[1]
		})
		.attr("font-family","sans-serif")
		.attr("font-size","11px")
		.attr("fill","red")

力导向图

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>testD3-22-force.html</title>
    <script type="text/javascript" src="d3.js"></script>
    <style type="text/css">
    </style>
</head>
<body>
<script type="text/javascript">
var h=300;
var w=300;
// 颜色函数
var colors=d3.scale.category20()//创建序数比例尺和包括20中颜色的输出范围
 
//(1)定义节点和联系对象数组
var dataset={
    nodes:[//节点
        { name:"Adam"},
        { name:"Bob"},
        { name:"Carride"},
        { name:"Donovan"},
        { name:"Edward"},
        { name:"Felicity"},
        { name:"George"},
        { name:"Hannah"},
        { name:"Iris"},
        { name:"Jerry"} 
    ],
    edges:[//边
        { source:0,target:1,weight:1,color:1},
        { source:0,target:2,weight:3,color:4},
        { source:0,target:3,weight:4,color:6},
        { source:0,target:4,weight:6,color:65},
        { source:1,target:5,weight:3,color:76},
        { source:2,target:5,weight:8,color:879},
        { source:2,target:5,weight:7,color:989},
        { source:3,target:4,weight:9,color:643},
        { source:5,target:8,weight:1,color:54},
        { source:5,target:9,weight:3,color:54}, 
        { source:6,target:7,weight:4,color:45},
        { source:7,target:8,weight:0,color:43},
        { source:2,target:8,weight:8,color:243},
        { source:3,target:8,weight:1,color:43},
        { source:5,target:8,weight:5,color:13},
        { source:6,target:8,weight:3,color:351},
        { source:8,target:9,weight:4,color:1}
    ]
};
 
//(2)转化数据为适合生成力导向图的对象数组
var force=d3.layout.force()
    .nodes(dataset.nodes)//加载节点数据
    .links(dataset.edges)//加载边数据
    .size([w,h])//设置有效空间的大小
    .linkDistance(50)//连线的长度
    .charge(-200)//负电荷量,相互排斥设置的负值越大越排斥
    .start();//设置生效
 
var svg=d3.select("body")
    .append("svg")
    .attr("width",w)
    .attr("height",h);
 
//(3)创建作为连线的svg直线
var edges=svg.selectAll("line")
    .data(dataset.edges)
    .enter()
    .append("line")
    .style("stroke",function(d){//  设置线的颜色
        return colors(d.color);
    })
    .style("stroke-width",function(d,i){//设置线的宽度
        return d.weight;
    });
 
//(4) 创建作为连线的svg圆形
var nodes=svg.selectAll("circle")
    .data(dataset.nodes)
    .enter()
    .append("circle")
    .attr("r",function(d){//设置圆点的半径,圆点的度越大weight属性值越大,可以对其做一点数学变换
        return Math.log(d.weight)*10;
    })
    .style("fill",function(d){
        return colors(d.weight*d.weight*d.weight);
    })
    .call(force.drag);//可以拖动
 
 //(5)打点更新,没有的话就显示不出来了
force.on("tick",function(){
    //边
    edges.attr("x1",function(d){
        return  d.source.x;
    })
    .attr("y1",function(d){
        return  d.source.y;
    })
    .attr("x2",function(d){
        return  d.target.x;
    })
    .attr("y2",function(d){
        return  d.target.y;
    });
 
    //节点
    nodes.attr("cx",function(d){
        return d.x;
    })
    .attr("cy",function(d){
        return d.y;
    });
 
})
</script>
 
</body>
</html>

其他函数:

d3.forceCenter([x,y]):中心力,将所有节点都推向图标的中心,默认坐标是0,0,施加力时,所有节点的相对位置保持不变

d3.forceCollide([radius]):collision,碰撞力,使两个节点接触时像弹簧球一样弹开

d3.forceLink([links]):连接力,拉动节点相互连接,好像节点之间有个弹簧一样

d3.forceManyBody():排斥力,类似带电电子的排斥方式,推动所有节点彼此远离

d3.force,d3.forceY:定位力,将节点推向期望的点,不同于forceCenter,它们会改变节点的相对位置

力导向图带文字

点替换文字、图片、提示

node.append("svg:image")
			.attr("class","circle")
			.attr("xlink:href","http://localhost:8080/spring/imgs/myself.PNG")
			.attr("x","-8px")
			.attr("y","-8px")
			.attr("width","16px")
			.attr("height","16px")

node.append("svg:title")
			.text(function(d) { return d.name})

弧形箭头连线

svg.append("svg:defs").selectAll("marker")
		.data(["suit","licensing","resolved"])
   .enter().append("svg:marker")
		.attr("id",String)
		.attr("viewBox","0 -5 10 10")
		.attr("refX", 15)
		.attr("")

缩放与拖拽

连线link箭头

const defs = this.svg.append('defs')

defs.append('marker')
		.attr("id",'marker')
		.attr("markerUnits",'userSpace')
		.attr("viewBox",'-20 -10 20 20')
		.attr("refX",20)
		.attr("refY",0)
		.attr("orient","auto")
		.attr("markerWidth",20)
		.attr("markerHeight",20)
		.attr("xoverflow",'visible')
		.append("path")
		.attr('d','M-10,-5 L 0, 0 L -10,5')
		.attr('fill','#595959')
		.attr('stroke','#595959')
defs.append('marker')
		.attr("id",'marker-actived')
		.attr("markerUnits",'userSpace')
		.attr("viewBox",'-20 -10 20 20')
		.attr("refX",25.5)
		.attr("refY",0)
		.attr("orient","auto")
		.attr("markerWidth",16)
		.attr("markerHeight",16)
		.attr("xoverflow",'visible')
		.append("path")
		.attr('d','M-16,-8 L 0, 0 L -16,8')
		.attr('fill','#0091FF')
		.attr('stroke','#0091FF')
		.attr('fill-opacity','0.9')
		.attr('stroke-opacity','0.6')

selection方法

selection.each()

selection.sort()

selection.filter()

selection.node():返回选择集中第一个非空元素,如果选择集为空则返回null

selection.nodes():返回选择集中所有被选中元素的元素数组

selection.empty():当选择集中没有任何元素时返回true

Selection.size():返回选择集中包含的DOM元素个数

selection.clone():

Selection.append():

selection.remove():

selection.insert():

selection.raise():

selection.lower():

映射与集合

d3.set([array])

set.has(value):

Set.add(value):添加

setremove移除

d3-graphviz

d3-graphviz可以对d3的点进行操作,各种变化

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v5.min.js"></script>
<script src="https://unpkg.com/@hpcc-js/wasm@0.3.11/dist/index.min.js"></script>
<script src="https://unpkg.com/d3-graphviz@3.0.5/build/d3-graphviz.js"></script>
<div id="graph" style="text-align: center;"></div>
<script>
var dotIndex = 0;
var graphviz = d3.select("#graph").graphviz()
    .transition(function () {
        return d3.transition("main")
            .ease(d3.easeLinear)
            .delay(500)
            .duration(1500);
    })
    .logEvents(true)
    .on("initEnd", render);
  
function render() {
    var dotLines = dots[dotIndex];
    var dot = dotLines.join('');
    graphviz
        .renderDot(dot)
        .on("end", function () {
            dotIndex = (dotIndex + 1) % dots.length;
            render();
        });
}

var dots = [
    [
        'digraph  {',
        '    node [style="filled"]',
        '    a [fillcolor="#d62728"]',
        '    b [fillcolor="#1f77b4"]',
        '    a -> b',
        '}'
    ],
    [
        'digraph  {',
        '    node [style="filled"]',
        '    a [fillcolor="#d62728"]',
        '    c [fillcolor="#2ca02c"]',
        '    b [fillcolor="#1f77b4"]',
        '    a -> b',
        '    a -> c',
        '}'
    ],
    [
        'digraph  {',
        '    node [style="filled"]',
        '    a [fillcolor="#d62728"]',
        '    b [fillcolor="#1f77b4"]',
        '    c [fillcolor="#2ca02c"]',
        '    a -> b',
        '    a -> c',
        '}'
    ],
    [
        'digraph  {',
        '    node [style="filled"]',
        '    a [fillcolor="#d62728", shape="box"]',
        '    b [fillcolor="#1f77b4", shape="parallelogram"]',
        '    c [fillcolor="#2ca02c", shape="pentagon"]',
        '    a -> b',
        '    a -> c',
        '    b -> c',
        '}'
    ],
    [
        'digraph  {',
        '    node [style="filled"]',
        '    a [fillcolor="yellow", shape="star"]',
        '    b [fillcolor="yellow", shape="star"]',
        '    c [fillcolor="yellow", shape="star"]',
        '    a -> b',
        '    a -> c',
        '    b -> c',
        '}'
    ],
    [
        'digraph  {',
        '    node [style="filled"]',
        '    a [fillcolor="#d62728", shape="triangle"]',
        '    b [fillcolor="#1f77b4", shape="diamond"]',
        '    c [fillcolor="#2ca02c", shape="trapezium"]',
        '    a -> b',
        '    a -> c',
        '    b -> c',
        '}'
    ],
    [
        'digraph  {',
        '    node [style="filled"]',
        '    a [fillcolor="#d62728", shape="box"]',
        '    b [fillcolor="#1f77b4", shape="parallelogram"]',
        '    c [fillcolor="#2ca02c", shape="pentagon"]',
        '    a -> b',
        '    a -> c',
        '    b -> c',
        '}'
    ],
    [
        'digraph  {',
        '    node [style="filled"]',
        '    a [fillcolor="#d62728"]',
        '    b [fillcolor="#1f77b4"]',
        '    c [fillcolor="#2ca02c"]',
        '    a -> b',
        '    a -> c',
        '    c -> b',
        '}'
    ],
    [
        'digraph  {',
        '    node [style="filled"]',
        '    b [fillcolor="#1f77b4"]',
        '    c [fillcolor="#2ca02c"]',
        '    c -> b',
        '}'
    ],
    [
        'digraph  {',
        '    node [style="filled"]',
        '    b [fillcolor="#1f77b4"]',
        '}'
    ],
];
</script>

canvas

d3资源

Https://d3js.org.cn

d3案例:https://csacademy.com/app/graph_editor/

https://bl.ocks.org/mohdsanadzakirizvi/6fc325042ce110e1afc1a7124d087130

https://observablehq.com/explore

Vega - 图表库也能低代码

G2

G2 是支付宝前端团队开发的,虽然当时 Echarts 已经很成熟了,但支付宝还是选择了自己做了一个,也就是 G2 的前身 acharts,它的 API 和 Echarts 基本类似,后来作者看了《The Grammar Of Graphics》那本书,就调整了方向,开始基于这本书的思路开发 G2,这个 G2 的命名就是这本书名里的两个 G。

受这本书影响的可视化库有很多,前面说的 Vega-Lite 在作者的论文里就提到过借鉴了不少了,和 G2 一样参考那本书的还有 chart-parts,不过别看它是微软的项目,其实也只有一个人在开发,提交次数非常不稳定,作者同时还在忙另一个更知名的项目 react-dnd,所以大概率以后会弃坑。

《The Grammar Of Graphics》里作者主要是借鉴了面向对象的思想,将图表拆分成了很多组成部分,比如数据源、数据转换、坐标系、图形等,通过将它们组装起来实现各种展现效果,从技术角度倒是不新奇,但这本书通过数学的方式来进行组合和分析,看起来非常高级。

chart.js

我之前对 Chart.js 这个库了解比较少,几年前第一次发现的时候它的 Star 数就很高了,它现在的 Star 数量高达 49.3k,在 npm 上的周下载量达到百万,和 D3 是一个量级的,恐怕是最火的前端图表库了。

既然怎么火,它在技术上情况怎样呢?这个项目的最早提交时间是 2013 年,7 年来提交没怎么中断,然而整个项目代码量只有 1w 多行,它的图表种类少,可配置项也不多,整体来说比 Echarts/Highcharts 都差很远,看了半天实在没搞懂为什么那么火,或许反应出大部分人对图表要求不高,能显示几种场景图表,有点交互和动画就够了。

Highcharts

创始在 2006 的时候只想给自己的主页加个图表,当时虽然有 FusionCharts/AnyChart 这些 Flash 的商业方案,但他用不惯就开始自己基于 SVG/VML 开发,可能他是最早这么做,极有可能是 Highcharts 后来居上的原因,使用 SVG/VML 最大的好处是方便前端使用,而之前基于 Flash 的方案和 JavaScript 通信麻烦,往往导致整个页面都得用 Flash 开发,成本比较高,所以后来 FusionCharts/AnyChart 也都转向了 SVG/VML 方案。

十四年过去了,它的创始人至今还几乎天天提交代码,这个项目的贡献者中不少是同一个小镇的,多半是他手把手培养起来的乡亲。。。

Highcharts 虽然开源,但商用是需要收费的,很多人看到要花钱就不往下看了,但其实专门做这块的商业公司往往更有优势,前面介绍 D3 的时候提到过,在所有基于 D3 开源的图表库中做得最好的是一个商业公司的产品。

AnyChart

如果你想用商业软件又觉得 Highcharts 太丑,AnyChart 就是个不错的候选,和 Highcharts 类似,AnyChart 也是开源但商用必须收费,它的整体设计比 Highcharts 要漂亮,但影响力远比不上 Highcharts,甚至在 GitHub 上的 Star 只有 248,还不如许多个人发布的玩具项目。

然而在国内某卖控件的网站上,AnyChart 的下载量很靠前,远高于 Highcharts,它们之间的差距甚至达到百倍,所以 AnyChart 可能更好卖,其中颜值可能是主要原因。

FusionCharts

FusionCharts 来自印度,它的创始人 Pallav Nadhani 的父亲是做 Web 设计的,所以他很早就接触网络,在 16 岁的时候就会用 Excel 来做高中作业,他用的过程中觉得 Excel 里的图表不好用,于是给 http://ASPToday.com 投稿了一篇文章,提出了 Flash 不仅能用来做小广告,还可以实现交互式图表的想法,这篇文章给他带来了 1500$ 的零花钱,普通高中生拿到零花钱多半会很快就和小伙伴们一起挥霍掉,而 Nadhani 拿着这笔钱在 17 岁(2002 年)时候成立了公司,开始开发基于 Flash 的图表库,然而高中辍学创业并不容易,没有投资人会相信一个还没成年的小孩,Nadhani 独自在家开发了三年后才招到第一个人,早期只在网上卖,因为网上不容易发现你是个小孩,发现了肯定不敢买,好在图表方面的需求很旺盛,所以公司不断做大,图表库不仅卖给了很多公司,还卖给了美国联邦政府,连美国总统都在用

amCharts

amCharts 也来自小国家,它的总部在立陶宛,最开始是只做地图类的图表,后来才有的饼图折线图,它应该是这里面最小的商业公司,整个团队只有 7 个人,其中参与开发的大概 4-5 个,因为人少,所以它的功能相对其它几个商业库要少点,比如不支持 vml(不过现在没人用 IE8 了),可能正是因为觉得做不过,所以它采用了不同的商业策略,是唯一可以免费商用,只有去 logo 才需要付费,这个策略看起来效果不错,它的搜索量是这几个商业库中排名第二的,使用的网站数仅次于 Highcharts。

Google chart

Google Charts 在 2008 年就推出了,做得非常早,虽然国内现在没人用,但国外用得很多,有个数据分析说它是最流行的图表库,在网站中的使用量 Highcharts 的两倍

Google Charts 有两个版本,一个是通过链接来生成图表图片,另一个是外链的 JavaScript 图表库。

通过链接生成图表图片的做法在十几年前非常常见,比如性能监控领域用了好多年的 RRDtool,即便现在你都能在一些股票网站的 k 线图里看到这种做法,使用它的主要原因是当时的前端技术不成熟,浏览器兼容性差,使用这种方式可以轻松在所有浏览器里得到一致的显示效果,下面是一个例子:

G6

G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。旨在让关系变得透明,简单。让用户获得关系数据的 Insight。

安装

npm install @antv/g6

导入

import G6 from '@antv/g6'

创建步骤:1.在html中创建容器

<div id="mountNode"></div>

2.引入 G6 的数据源为 JSON 格式的对象。该对象中需要有节点(nodes)和边(edges)字段,分别用数组表示:

const data = {
  // 点集
  nodes: [
    {
      id: 'node1', // String,该节点存在则必须,节点的唯一标识
      x: 100, // Number,可选,节点位置的 x 值
      y: 200, // Number,可选,节点位置的 y 值
    },
    {
      id: 'node2', // String,该节点存在则必须,节点的唯一标识
      x: 300, // Number,可选,节点位置的 x 值
      y: 200, // Number,可选,节点位置的 y 值
    },
  ],
  // 边集
  edges: [
    {
      source: 'node1', // String,必须,起始点 id
      target: 'node2', // String,必须,目标点 id
    },
  ],
};

3.创建关系图(实例化)时,至少需要为图设置容器、宽和高。

const graph = new G6.Graph({
  container: 'mountNode', // String | HTMLElement,必须,在 Step 1 中创建的容器 id 或容器本身
  width: 800, // Number,必须,图的宽度
  height: 500, // Number,必须,图的高度
});

4.配置数据源,渲染

graph.data(data); // 读取 Step 2 中的数据源到图上
graph.render(); // 渲染图

keylines

cytoscape.js

jointjs

nivo

安装

yarn add @nivo/core @nivo/bar

官网:https://nivo.rocks/

其他开源图表库

开源图表库还有很多 Star 数量比较高的,这里简单汇总一下:

  • TOAST UI Chart 是韩国 NHN 公司搞的,贡献者比较少,感觉快要弃坑了,大公司里的非主业项目经常这样,表示理解。
  • Chartist.js 别看它有 12.2k 的 Star,看提交历史肯定弃坑了,3 年只提交了 20 次。
  • Frappe Charts 代码只有 3.5k 行,最近提交很少且文档很简陋,13.4k Star 说明他们的推广策略很厉害,也是个值得学的案例。
  • Raphael 特别古老的前端图形库,2008 年就有了,不少图表库的底层基于它,它自己也有个 g.Raphaël 图表库,不过只开发了两年就停了,可能是作者在 Adobe 忙别的事情去了,比如折腾过 Snap.svg,但也弃坑了。
  • vis.js 主要是绘制关系图的,曾经有段时间弃坑过,不过最近还有少量更新,类似的还有 dagreGoJSDraw2D
  • TradingView 专注做股票类的,似乎很专业,有不少分析 k 线的小工具。
  • uPlot 以体积小巧作为主要优点,然而体积小只是因为功能少,2k 多行代码做不了啥,所以它的配置项和交互都很少。
  • Chartkick.js 将 Chart.js、Google Charts 和 Highcharts 的接口封装起来,提供一套更简单的 API,一方面是更简单,另一方面是万一其中某个挂掉了可以直接换别的,然而这也意味着它的功能非常有限,毕竟是取交集。
  • 另外还有些 gis 领域的专用可视化库,比如 cesiumdeck.gl,但国内对测绘是有规定的,最好直接用百度、高德。

连线图表

jsPlumb

jsPlumb适用于必须绘制图表的Web应用程序.例如类似于Visio的应用程序或工作流程设计器等。由于图表项目和连接的所有参数都是非常精细可控的,因此您可以绘制您可以想到的任何类型的图表的!

基本概念

Souce 源节点、Target 目标节点、Anchor 锚点、Endpoint 端点、Connector 连接

vivaGraphJs

springy

graphviz

visjs

Canvas

Fabricjs

二维的h5 canvas库

自定义

http://fabricjs.com/customization

支持鼠标事件

http://fabricjs.com/events

(function() {
  var canvas = this.__canvas = new fabric.Canvas('c');
  fabric.Object.prototype.transparentCorners = false;

  canvas.on('mouse:over', function(e) {
    e.target.set('fill', 'red');
    canvas.renderAll();
  });

  canvas.on('mouse:out', function(e) {
    e.target.set('fill', 'green');
    canvas.renderAll();
  });

  // add random objects
  for (var i = 15; i--; ) {
    var dim = fabric.util.getRandomInt(30, 60);
    var klass = ['Rect', 'Triangle', 'Circle'][fabric.util.getRandomInt(0,2)];
    var options = {
      top: fabric.util.getRandomInt(0, 600),
      left: fabric.util.getRandomInt(0, 600),
      fill: 'green'
    };
    if (klass === 'Circle') {
      options.radius = dim;
    }
    else {
      options.width = dim;
      options.height = dim;
    }
    canvas.add(new fabric[klass](options));
  }
})();

fabricjs-react

支持react的fabricjs库

import React from 'react'

import { FabricJSCanvas, useFabricJSEditor } from 'fabricjs-react'

const App = () => {
  const { editor, onReady } = useFabricJSEditor()
  const onAddCircle = () => {
    editor?.addCircle()
  }
  const onAddRectangle = () => {
    editor?.addRectangle()
  }

  return (<div>
    <button onClick={onAddCircle}>Add circle</button>
    <button onClick={onAddRectangle}>Add Rectangle</button>
    <FabricJSCanvas className="sample-canvas" onReady={onReady} />
  </div>)
}

export default App

Rough.js

canvas和svg绘制库

安装

npm install --save roughjs

使用

import rough from 'roughjs';

// canvas
const rc = rough.canvas(document.getElementById('canvas'));
rc.rectangle(10, 10, 200, 200); // x, y, width, height

//svg
const rc = rough.svg(svg);
let node = rc.rectangle(10, 10, 200, 200); // x, y, width, height
svg.appendChild(node);

布局

dagre

骨架屏实现

骨架屏(Skeleton Screen)是指在页面数据加载完成前,先给用户展示出页面的大致结构(灰色占位图),在拿到接口数据后渲染出实际页面内容然后替换掉。Skeleton Screen 是近两年开始流行的加载控件,本质上是界面加载过程中的过渡效果。

假如能在加载前把网页的大概轮廓预先显示,接着再逐渐加载真正内容,这样既降低了用户的焦灼情绪,又能使界面加载过程变得自然通畅,不会造成网页长时间白屏或者闪烁。这就是 Skeleton Screen !

骨架屏的实现方案:

1.使用图片、svg 或者手动编写骨架屏代码:使用 HTML + CSS 的方式,我们可以很快的完成骨架屏效果,但是面对视觉设计的改版以及需求的更迭,我们对骨架屏的跟进修改会非常被动,这种机械化重复劳作的方式此时未免显得有些机动性不足;

2.通过预渲染手动书写的代码生成相应的骨架屏:该方案做的比较成熟的是 vue-skeleton-webpack-plugin,通过 vueSSR 结合 webpack 在构建时渲染写好的 vue 骨架屏组件,将预渲染生成的 DOM 节点和相关样式插入到最终输出的 html 中。

3.饿了么内部的生成骨架页面的工具:该方案通过一个 webpack 插件 page-skeleton-webpack-plugin 的方式与项目开发无缝集成,属于在自动生成骨架屏方面做的非常强大的了,并且可以启动 UI 界面专门调整骨架屏,但是在面对复杂的页面也会有不尽如人意的地方,而且生成的骨架屏节点是基于页面本身的结构和 CSS,存在嵌套比较深的情况,体积不会太小,并且只支持 history 模式。

https://mp.weixin.qq.com/s/yLtk5dNfmXK92b7qe9Eyfw

如果你觉得我的文章对你有帮助的话,希望可以推荐和交流一下。欢迎關注和 Star 本博客或者关注我的 Github