D3 学习总结-第一张图-01

779 阅读10分钟

环境 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。

image.png

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')
}

打印一下,打开控制器看看

image.png

我们可以看到它是一个d3 selection对象,带有_groups和_parents键。 d3选择对象是数组的一个子类。他们有很多很棒的方法,我们稍后将深入探索——现在对我们来说重要的是包含我们的#包装器目录的_groups列表。

添加SVG元素

我们的wapper对象又操作DOM元素的方法--让我们使用它的附加方法来添加一个新的SVG元素。

const wapper = d3.select('#demo-line-1')
const svg = wapper.append('svg')

打印svg

image.png 可以看到也是一个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个空间缩进的方法。这将使我们很容易发现我们的选择何时发生变化。

image.png 我的编辑器直接格式化了,后期找办法解决!\color{red}{我的编辑器直接格式化了,后期找办法解决!}

创建边界框

我们设置的svg的大小,现在设置图表为我们指定的边距。 让我们创建一个组,改变其内容,以尊重顶部和左边缘,这样我们就可以在一个地方处理这些。SVG元素本身不可见,但用于对其他元素进行分组。可以把它看作是SVG的

——一个针对其他元素的包装器。我们可以在元素中绘制图表,并使用CSS转换属性。 d3选择对象有一个.style()方法,用于添加和修改CSS样式。.style()方法的调用类似于.attr(),并将键值对作为其第一个和第二个参数。让我们使用.style()来改变我们的边界。

  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轴上的中间点。

image.png 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")

image.png

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))

image.png

记住,SVG元素默认为黑色填充,而没有线条,这就是为什么我们会看到这个黑色填充的形状。这不是我们想要的!让我们添加一些样式来得到一条没有填充的橙色线。

  const line = bounds
    .append('path')
    .attr('d', lineGenerator(dataset))
    .attr('fill', 'none')
    .attr('stroke', '#af9358')
    .attr('stroke-width', 2)

image.png 我们快到终点了,但缺少了一些东西。让我们画轴结束。

绘制轴

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)

创建一个元素来包含轴元素是一个好主意,主要有三个原因:

  1. 为了保持DOM的组织,用于调试或导出
  2. 如果我们想删除或更新轴,我们将需要一种简单的方法来标记所有元素
  3. 立即修改整个轴,例如当我们想要移动整个轴时。 轴看起来很对,但它放错了地方:

image.png 为什么.axisBottom()没有把轴画到正确的地方?d3的轴发生器函数知道将相对于轴线的标记和标记标签放在哪里,但它们不知道将轴本身放在哪里。为了将x轴移动到底部,我们可以移动x轴组,类似于我们使用CSS变换移动图表边界的方式。 为了将x轴移动到底部,我们移动x轴组类似于我们使用css变换移动图表边界的方式。

const xAxis = bounds.append('g').call(xAxisGenerator).style('transform', `translate(0,${dimensions.boundedHeight}px)`)

我们的第一个图表完成了!

image.png