环境 vue3+typescript+d3@7.1.1
学习书籍 Fullstack.D3.and.Data.Visualization.r15.2020.4
目标 通过这本书入门d3 数据可视化进阶之旅
因为是英文书籍 比较难回顾,写成章节方便以后看。
代码链接 提取码:1234
基础环境
安装
yarn add d3
引入
import * as d3 from 'd3'
获取数据
d3方式
const dataset = await d3.json("./../../my_weather_data.json")
d3有很多获取数据的方式不过我应该用不到,大家可以参考 d3.fetch
我将数据存在本地json
vue3 导入json数据需要添加声明
declare module '*.json' {
const value: any
export default value
}
建立第一个折线图
绘图
在绘制图表时,我们需要定义两个容器的尺寸:wrapper和bounds。
wapper包含整张图表,bounds包含数据 首先定义一个包含wapper大小和内边距的对象:
const dimensions = {
width: 400,
height: 400,
margin: {
top: 15,
right: 15,
bottom: 40,
left: 60,
},
boundedWidth: 0,
boundedHeight: 0,
}
我们想要一个小的上和右边距给图表一些空间。该线或y轴可能会溢出图表的边界。我们需要一个更大的底部和左边缘来为我们的轴创造空间。 让我们计算我们的边界的大小,并将其添加到我们的维度对象中。
dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right
dimensions.boundedHeight = dimensions.height - dimensions.margin.top - dimensions.margin.bottom
创建工作空间
要向页面添加元素,我们需要指定一个我们要应用的现有元素
<template>
<div id="demo-line-1"></div>
</template>
这时候我们会用到 d3-selection 我们可以使用d3.select(),参数是css的选择器,并返回第一个匹配的DOM元素(如果有的话)。 添加initChart方法,并在onMounted中调用
onMounted(() => {
initChart()
})
const initChart = () => {
const wapper = d3.select('.demo-line-1')
}
打印一下,打开控制器看看
我们可以看到它是一个d3 selection对象,带有_groups和_parents键。 d3选择对象是数组的一个子类。他们有很多很棒的方法,我们稍后将深入探索——现在对我们来说重要的是包含我们的#包装器目录的_groups列表。
添加SVG元素
我们的wapper对象又操作DOM元素的方法--让我们使用它的附加方法来添加一个新的SVG元素。
const wapper = d3.select('#demo-line-1')
const svg = wapper.append('svg')
打印svg
可以看到也是一个selection对象里面的_groups是我们添加的svg元素.
d3 selection 对象有一个.attr()方法用来添加或替换DOM属性,接收两个参数,第一个参数是属性名,第二个参数是值。
.attr()的值参数可以是一个常数,这是我们现在所需要的,也可以是一个函数,稍后我们将讨论。
const wrapper = d3.select("#demo-line-1")
const svg = wrapper.append("svg")
svg.attr("width", dimensions.width)
svg.attr("height", dimensions.height)
大部分d3-selection方法都会返回一个selection对象
- 任何选择或创建新对象的方法都将返回新的selection
- 任何操作当前selection的方法都将返回相同的selection 这允许我们在使用多种方法时通过chaining来保持代码的简洁。例如,我们可以将上述代码改写为
const wapper = d3.select('#demo-line-1')
const svg = wapper.append('svg').attr('width', dimensions.width).attr('height', dimensions.height)
在这本书中,我们将遵循常见的d3约定,为返回相同选择使用4个空间缩进的方法。这将使我们很容易发现我们的选择何时发生变化。
创建边界框
我们设置的svg的大小,现在设置图表为我们指定的边距。 让我们创建一个组,改变其内容,以尊重顶部和左边缘,这样我们就可以在一个地方处理这些。SVG元素本身不可见,但用于对其他元素进行分组。可以把它看作是SVG的
const bounds = svg.append('g').style('transform', `translate(${dimensions.margin.left}px,${dimensions.margin.top}px)`)
通过查看元素发现的大小是0x0,g会根据内部内容进行扩展,当我们开始绘制图表,我们会看到他的作用。
创建我们的刻度尺
我们现在要在y轴绘制每天的最高温度。
开始绘制之前,我们需要决定想要可视化的温度,我们是否需要100℃或者-100℃?最佳方案是通过数据集中的最低和最高温度来确定实际范围。
通常我们轴坐标从0开始,如果有零下温度,我们的这张图就无法绘制, d3的 d3-scale模块可以创建不同类型的刻度尺,刻度尺是一个在两个域之间转换的函数。
对于y轴,我们希望将值从温度域转换为像素域。如果我们的图表需要处理从10°华氏度到100°华氏度的温度,最大为55°华氏度的一天将在y轴的一半。
让我们创建一个将这些度转换为y值的尺度。如果我们的y轴是200px高,y尺度应该将55°F转换为100,y轴上的中间点。
d3-scale可以创建很多类型的刻度尺,在本章图中,我们用d3.scaleLinear(),因为我们的y轴数据是线性增加的数字
const yScale = d3.scaleLinear()
刻度尺需要两个参数:
domain:最小输入值和最大输入值,
range:最小输出值和最大输出值
现在我们需要数据中的最高和最低温度,d3-array模块的d3.extent()方法
const yScale = d3
.scaleLinear()
.domain(d3.extent(dataSet, yAccessor))
.range([dimensions.boundedHeight, 0])
这样我们就创建了我们的第一个刻度尺函数。
console.log(yScale(32))
数据为华氏温度,32即0摄氏度,通过此函数可以看到相应的刻度位置(即距离y轴底部的距离)。
我们现在做一个冰点区域,svg的标签正好适用。
const freezingTemperaturePlacement = yScale(32)
const freezingTemperatures = bounds
.append('rect')
.attr('x', 0)
.attr('width', dimensions.boundedWidth)
.attr('y', freezingTemperaturePlacement)
.attr('height', dimensions.boundedHeight - freezingTemperaturePlacement)
.attr("fill", "#e0f3f3")
让我们把它变成一个寒冷的蓝色,以表示“冻结”,并降低它的视觉重要性。您不能用背景或边框设置SVG元素的样式-相反,我们可以分别使用填充和笔画。稍后我们将更深入地讨论这些差异。正如我们所看到的,SVG元素的默认填充是黑色的,默认的笔画颜色是没有的,宽度为1px。
.attr("fill", "#e0f3f3")
在这段代码中,我们使用.attr()来设置填充,因为一个属性比链接的样式表具有比CSS更低的优先级,这将允许我们覆盖该值。如果我们使用了.style(),我们将设置一个内联样式,如果要覆盖他就需要css - !important
现在开始创建x轴
这看起来像我们的y轴,但是,由于我们使用的是日期对象,我们将使用一个知道如何处理日期对象的时间刻度尺。
const xScale = d3.scaleTime().domain(d3.extent(dataSet, xAccessor)).range([0, dimensions.boundedWidth])
现在我们已经定义了我们的刻度尺,我们就可以开始绘制我们的图表了!
绘制线条
时间轴本身将是一个单路径的SVG元素,path元素采用一个d属性(数据的缩写),告诉它们要做什么形状。
- M将移动到一个点(后面是x和y值),
- L将画一条线到一点(后面是x和y值),
- Z将画一条线回第一个点
bounds.append("path").attr("d", "M 0 0 L 100 0 L 100 100 L 0 50 Z")
d还有很多属性,但是我们不需要专门去学习,d3-shape有一个d3.line()方法,它将创建一个生成器,将数据点转换为一个d字符串。
const lineGenerator = d3.line()
我们的生成器需要两个信息 x轴值和y轴值
我们分别用x和y方法设置这些值,每个方法都需要一个参数:一个将数据点转换为x或y值的函数。 我们希望使用accessor函数,但请记住:accessor函数返回未缩放的值。 我们将用accessor函数和尺度来转换数据点,以得到像素空间中的缩放值。
const lineGenerator = d3
.line()
.x((d) => xScale(xAccessor(d)))
.y((d) => yScale(yAccessor(d)))
现在我们准备将路径元素添加到bounds中。
const line = bounds.append("path")
让我们将数据集输入我们的行生成器来创建d属性,并告诉线条是什么形状。
const line = bounds.append("path")
.attr("d", lineGenerator(dataset))
记住,SVG元素默认为黑色填充,而没有线条,这就是为什么我们会看到这个黑色填充的形状。这不是我们想要的!让我们添加一些样式来得到一条没有填充的橙色线。
const line = bounds
.append('path')
.attr('d', lineGenerator(dataset))
.attr('fill', 'none')
.attr('stroke', '#af9358')
.attr('stroke-width', 2)
我们快到终点了,但缺少了一些东西。让我们画轴结束。
绘制轴
y轴
让我们从y轴开始。d3的d3-axis模块有轴生成器的方法,它将为给定的比例绘制一个轴。与我们以前使用的方法不同,d3轴生成器将向页面附加多个元素。 每个方向都有一种方法,它将指定标签和标记的位置:
•axisTop
•axisRight
•axisBottom
•axisLeft
按照常见的约定,我们希望y轴的标签在轴线的左边,所以我们将使用d3.axisLeft()并将其传递给我们的y刻度尺。
const yAxisGenerator = d3.axisLeft()
.scale(yScale)
当我们调用轴生成器时,它将创建许多元素——让我们创建一个g元素来保存所有这些元素,并保持我们的DOM组织。然后我们将把这个新元素传递给我们的y轴生成器函数,告诉它在哪里绘制我们的轴
const yAxis = bounds.append("g")
yAxisGenerator(yAxis)
这个方法可以工作,但它将破坏我们的链式方法。为了解决这个问题,d3的选择有一个.call()方法,该方法将以该选择作为第一个参数来执行所提供的函数。 我们可以使用.call():
1.防止将我们的选择保存为变量,
2.保持选择器来继续添加链式操作
const yAxis = bounds.append('g').call(yAxisGenerator)
x轴 让我们用同样的方式创建x轴,这次是使用d3.axisBottom()。
const xAxisGenerator = d3.axisBottom(xScale)
const xAxis = bounds.append('g').call(xAxisGenerator)
创建一个元素来包含轴元素是一个好主意,主要有三个原因:
- 为了保持DOM的组织,用于调试或导出
- 如果我们想删除或更新轴,我们将需要一种简单的方法来标记所有元素
- 立即修改整个轴,例如当我们想要移动整个轴时。 轴看起来很对,但它放错了地方:
为什么.axisBottom()没有把轴画到正确的地方?d3的轴发生器函数知道将相对于轴线的标记和标记标签放在哪里,但它们不知道将轴本身放在哪里。为了将x轴移动到底部,我们可以移动x轴组,类似于我们使用CSS变换移动图表边界的方式。
为了将x轴移动到底部,我们移动x轴组类似于我们使用css变换移动图表边界的方式。
const xAxis = bounds.append('g').call(xAxisGenerator).style('transform', `translate(0,${dimensions.boundedHeight}px)`)
我们的第一个图表完成了!