在一个网站上绘制折线图使用了ant/g2
,打包后的体积到了一兆多,这不行了,需要按需加载。但是它的支持不太友好,我尝试在官网上用它的方法来按需引入,不是缺这就是缺那,很不好用。
反正我这里只是画个折线图,其他的功能也用不上,那么我直接用D3自己手画不行么?
安装
可以查看官网选择直接使用<script>
标签的形式直接链接进来:
1 | <script src="https://d3js.org/d3.v5.min.js"></script> |
也可以使用npm安装,(点击查看npm地址):
1 | npm install d3 |
可以直接导入一个D3的命名空间:
1 | import * as d3 from 'd3'; |
也可以导入一个模块:
1 | import {scaleLinear} from 'd3-scacle'; |
我使用在angular项目中,自带ts,那么我希望d3也有ts声明,可以安装:
1 | npm i --save @types/d3 |
开始画图
定义折线图的展示参数
我们需要定义折线图的高度、宽度,以及它的边距:
1 | const width = 1000; |
在DOM中创建元素
为了方便,我们可以先在html中创建一个用id标记的dom节点来放置我们的折线图,当然,也可以以动态的方式在ts中创建,因为我们的焦点在使用D3,所以先简单点,手动创建dom:
1 | <div id="container"></div> |
然后我们在ts中加入生成画布的命令:
1 | const svg = select('#container') |
目前我们在页面中是看不到啥的,如果审查页面,则可以看到svg元素已经创建了。
定义数据结构
我们这里的折线图的数据结构为:
1 | interface ChartDataInterface { |
date
代表日期,uv
是个数据值,代表这天的访问量。
设置比例尺
比例尺是把一组输入域映射到输出域的函数。映射就是两个数据集之间元素相互对应的关系。好比地图上的距离比例尺,假设地图上1cm代表500米,那么5cm代表2500米。一个意思。
D3中有各种比例尺函数,有连续性的,有非连续性的。我们这里目前情况,对于y轴,因为它是一个数值,所以我们需要一个线性比例尺。对于x轴,是离散的点,需要一个序数比例尺。我们需要引入这两个比例尺:
1 | import {scaleLinear, scaleBand} from 'd3-scale'; |
有了比例尺,我们需要设置比例尺的domain
和range
:
- domain 设置比例尺的域 是一个值数组。将第一个元素映射到第一个频段,第二个元素映射到第二个频段,依次类推。
- range 设置比例尺的值范围。为指定的两个数字数组,默认范围是
[0, 1]
为了构造y轴的值范围,我们需要从y轴的数据中获取最大的值:
1 | import {max} from 'd3-array'; |
1 | const xScale = scaleBand() |
x轴的比例尺范围为从左到右,y轴的比例尺范围为从上到下,注意需要减去边距。
设置坐标轴
坐标轴我们需要使用axisBottom
和axisLeft
,即左轴和下轴,需要引入这两个符号:
1 | import {axisBottom, axisLeft} from 'd3-axis'; |
然后直接用我们前面构造的比例尺来生成坐标轴:
1 | const xAxis = axisBottom(xScale); |
接下来绘制坐标轴。
1 | svg.append('g') |
这样在页面中可以看到我们的坐标轴了:
绘制折线
首先我们需要一个折线的路径,需要line
函数,需要引入:
1 | import {line} from 'd3-shape'; |
用line
生成一个折线生成器,然后line.x
设置x坐标访问器,line.y
设置y坐标访问器。
1 | const linePath = line<ChartDataInterface>() |
这里需要注意,line
默认的的参数为[number, number][]
,我们这里需要提供一个类型给line
,让它接受我们的数据结构,不然会ts报错很烦。
然后开始绘制折线:
1 | svg.append('path') |
看看绘制出来的线:
嗯。。。。好像对,好像又不对,折线图的是绘制了出来了,但是为什么好像偏了???
自信点,不是好像,是确定偏了。😅
scaleBand
比例尺会将范围划分为domain
设置的n个带(n为domain
数组中值的个数),然后一个单元内居中对齐。所以我们这个折线需要对应x轴做偏移。
为了辅助我们观察方便,我们需要添加一个辅助线:
1 | svg.append('g') |
然后看我们的图:
结合前面的scaleBand
比例尺的规则,我们知道,比例尺把x轴划分为8个宽度(我们这里的数据是八组数据)。而一个宽度里面的刻度是居中的,所以我们需要让折线图便宜半个宽度单位即可。
但是又看了下图的位置,发现好像这个图是偏的:
看了下这个轴线的宽度是6.5px
。所以可以知道,我们需要的偏移量是一半的宽度,还需要减掉这个轴的宽度:
1 | const xOffset = xScale(this.chartData[1].date) - 6.5; |
然后我们再修正绘制的地方:
1 | svg.append('path') |
然后我们的折线图就完美了:
暂时到这里吧,基本的折线图绘制完毕,其他的美化和事件监听后续再完善。
动态获取y轴宽度
前面我们手动的量了y轴的宽度,这是治标不治本的方法。要想一劳永逸,我们需要动态获取y轴的宽度。
代码:
1 | const yAxisWidth: number = (this.svg.select('.yAxis').node() as SVGSVGElement).getBBox().width; |
测试看看:
(暂时先忽略x轴吧!😓)
这里的思想是:
- 首先获得y轴的宽度,备用
- 需要得到x轴的标尺第一个块的相对位置,然后取一半即为第一个点的x轴偏移量。
- 得到的x轴偏移量+y轴的宽度+图的左边的padding=整个曲线的偏移量
然后在绘曲线的时候直接使用:
1 | this.svg.append('path') |