龙空技术网

D3.js实战教程:6 可视化分布

启辰8 108

前言:

而今姐妹们对“js定位当前位置原理”可能比较讲究,咱们都需要学习一些“js定位当前位置原理”的相关知识。那么小编同时在网摘上网罗了一些有关“js定位当前位置原理””的相关知识,希望各位老铁们能喜欢,各位老铁们快快来学习一下吧!

本章涵盖将数据点分组到箱中绘制直方图使用金字塔图并排比较两个分布计算数据集的四分位数并生成箱线图使用小提琴图比较多个类别的分布

可视化分布是数据可视化中的常见要求。我们使用数据分布来评估数据值在特定括号内出现的频率或数据点出现在某个范围内的概率。

在本章中,我们将研究美国数据可视化从业者的工资分布。我们将构建的报告背后的数据来自数据可视化协会 (DVS) () 主办的 2021 年行业状况调查。您可以在图 6.1 中查看此报告,也可以在 上在线查看此报告。

对于本报告,我们将首先构建最常见的数据分布表示形式,即直方图,以可视化调查中 788 名美国受薪受访者的工资。然后,我们将使用两种类型的可视化来比较女性和男性受访者的工资:金字塔图和箱形图。第一个对于并排比较两个类别很方便。与直方图相比,后者提供了额外的信息层,揭示了数据集的四分位数和中位数。

我们将通过调查数据可视化中不同角色(如分析师、开发人员和设计师)的收入分配来完成本章。我们将使用小提琴图来显示每个角色的分布形状,我们将在其中添加四分位数范围和平均值。

图 6.1 在本章中,我们将构建四个图表来可视化美国数据可视化从业人员的工资分布。

为了构建图 6.1 中所示的图表,我们将介绍条柱的概念。条柱是一组数据点,通常宽度相等。使用 D3 创建分布可视化时,我们将数据预处理到箱中,然后使用这些箱绘制图表。

在开始之前,请转到第 6 章的代码文件。如果您还没有,可以从本书的 GitHub 存储库 () 下载它们。在名为 chapter_06 的文件夹中,代码文件按节进行组织。要开始本章的练习,请在代码编辑器中打开 6.1-Binning_data 中的 start 文件夹并启动本地 Web 服务器。如果您需要有关设置本地开发环境的帮助,请参阅附录 A。您可以在位于本章代码文件根目录下的自述文件中找到有关项目文件夹结构的更多详细信息。

警告

使用本章的代码文件时,在代码编辑器中仅打开一个开始文件夹或一个结束文件夹。如果一次打开章节的所有文件并使用 Live Server 扩展为项目提供服务,则数据文件的路径将无法按预期工作。

工程的数据文件夹包含一个 CSV 文件,其中每行对应于对 DVS 调查的响应。数据集有四列:唯一标识符 (uid)、受访者的角色(设计师、开发人员、记者等)、受访者的性别以及他的美元工资等级。在调查中,参与者可以选择$ 10K或$ 20K的括号(“$ 10,000 - $ 19,999”,“$ 20,000 - $ 39,999”,“$ 40,000 - $ 59,999”等)。出于比较目的,选择“240,000 美元或更多”选项的受访者将不包括在可视化中。

6.1 数据分箱

为了可视化数据分布,我们通常需要通过将数据集的数据点分组到桶或箱中来预处理数据集,这些桶或箱是沿轴宽度相等的组。在 D3 中,我们使用模块 d3-array () 中的方法 d3.bin() 来执行此操作。

图 6.2 为了可视化 D3 的分布,我们将原始数据集传递给函数 d3.bin()。此函数返回一个新数据集,其中包括条柱、其边界及其相应的数据点。

让我们以本章项目中的数据集 data.csv 为例。该数据集包含 788 名数据从业者的年薪,并从受访者可以选择其工资等级的调查中提取。这些括号涵盖 $10K 或 $20K USD 的范围,例如“$10,000 - $19,999”、“$20,000 - $39,999”、“$40,000 - $59,000”、“$60,000 - $79,999”等。每个工资等级都可以作为如何在数据可视化中使用箱的示例。我们知道受访者的实际工资存在于垃圾箱的范围内,但我们不知道实际价值。

为了用一个例子来说明箱的概念,我们将开始处理本章的项目。确保 6.1-Binning_data 中包含的起始文件夹在代码编辑器中处于打开状态,并且本地 Web 服务器正在运行。如果转到文件load-data.js,您将看到数据集已经使用第3章中讨论的方法d3.csv()加载到项目中。您可以在示例 6.1 中看到相关代码。

由于调查数据集没有列出每个受访者的实际工资,因此我们调用函数 getRandomSalary() 来获取位于工资等级下限和上限之间的随机整数值。这可能不是我们在实际数据可视化项目中会做的事情,但它将允许我们使用真实的发行版。

警告

由于我们使用函数 Math.random() 来生成工资值,因此您在执行本章项目时获得的结果可能与本书中介绍的结果略有不同,但整体外观和感觉应该保持不变。

在加载数据时,我们会过滤掉“240,000 美元或以上”的收入,因为我们不知道这个括号的上限。一旦计算出随机工资,并且数据完成加载,我们调用函数drawHistogram(),drawBoxplot(),drawPyramid()和drawViolinCharts(),我们将在其中构建每个可视化。这些函数将数据集作为参数,并已在其相应的 JavaScript 文件中声明。

示例 6.1 获取和格式化数据集(加载数据.js)

const getRandomSalary = (salary) => {                           #A  const lowerLimit = +salary.slice(1, salary.indexOf(" -"))     #A➥     .replace(",","");                                        #A  const upperLimit = +salary.slice(salary.indexOf(" $") + 2)    #A➥     .replace(",", "");                                       #A                                                                #A  return Math.floor(Math.random() * (upperLimit - lowerLimit) + #A➥     lowerLimit);                                             #A}                                                               #A d3.csv("./data/earnings_per_role.csv", d => {                   #B   if (d.pay_annual_USD !== "$240,000 or more") {                #C    return {                                                    #C      role: d.role,                                             #C      gender: d.gender,                                         #C      salary: getRandomSalary(d.pay_annual_USD)                 #C    };                                                          #C  }                                                             #C }).then(data => {                                               #D                                                                #D  drawHistogram(data);                                          #D  drawBoxplot(data);                                            #D  drawPyramid(data);                                            #D  drawViolinCharts(data);                                       #D                                                                #D});                                                             #D

现在让我们打开文件直方图.js并开始在函数 drawHistogram() 中工作。您将看到我们已经声明了图表的边距、宽度和高度。我们还按照第 4.2.1 节中描述的策略将 SVG 容器附加到 DOM 中,并将 SVG 组元素转换为内部图表的位置。这个群被保存到常量 innerChart 中,我们稍后将附加构成直方图的元素。对于本章中的每个图表都是如此。

然后我们用方法 d3.bin() 声明一个 bin 生成器。如以下代码片段所示,此方法可以使用几个访问器函数链接。例如,由于我们的数据由一个对象数组组成,因此我们链接函数 value() 以告诉 D3 我们想要可视化的值存储在哪个键下;在我们的项目中,关键是薪水。最后,通过调用箱生成器并将数据集作为参数传递来生成箱。请注意该过程与第 4 章中讨论的形状生成器相似。

const binGenerator = d3.bin()  .value(d => d.salary);const bins = binGenerator(data);

在最新的代码片段中,我们将 bin 生成器返回的数组保存到一个名为 bin 的常量中,稍后我们将重用以绘制直方图。如果将箱登录到控制台,您将看到它的结构为多维数组,如图 6.3 所示。顶级 bin 数组中的每个项目都包含该特定 bin 的数据点数组。箱的长度属性告诉我们它包含多少个数据点。它的下限和上限位于键 x0 和 x1 下。

图 6.3 在 bin 生成器返回的多维数组中,每个 bin 都是一个包含数据点的数组。在此图中,由 JavaScript 对象组成的数据点由 {...} 表示,以简洁起见。箱的下限和上限可在键 x0 和 x1 下访问。

为了说明箱的概念,我们在图 6.4 中将数据点绘制到各自的箱中。每个数据点都是一个圆圈,其水平位置对应于薪水,其垂直位置是任意的,以减少圆圈之间的重叠。如您所见,在 60 万美元到 140 万美元之间有更高的数据点密度,而当我们向四肢移动时,密度会降低。这种现象也将在我们稍后创建的分布可视化中可见。尽管图 6.4 不是可视化分布的传统方法,但它可能会帮助您更好地掌握箱的概念。

图 6.4 各自箱中的工资数据点。每个圆圈的水平位置对应于薪水,而它们的垂直位置是任意的,以减少重叠。

6.2 绘制直方图

直方图概述了值在数据集中的分布方式,使我们能够发现值的集中位置或它们之间是否存在明显的差距。在本节中,我们将构建一个直方图来可视化 755 名数据可视化从业者的工资分布。

完成的直方图如图 6.1 和托管项目 () 所示。您会注意到直方图只是由矩形组成,每个工资等级一个矩形。要使用 D3 构建此图,我们所要做的就是使用上一节中生成的箱作为数据,将矩形元素附加到具有数据绑定模式的 SVG 容器中。然后,我们使用 D3 刻度计算每个矩形的位置,并根据它们所代表的数据点数量设置它们的高度。

图 6.5 要生成直方图,我们首先使用 d3.bin() 方法对数据集进行预处理。此方法返回一个新数据集,其中数据点分布到箱(数组)中,每个箱都有下限和上限。然后,我们使用此新数据集将矩形附加到 SVG 容器。矩形的长度与相关条柱包含的数据点数成正比,而它们的位置对应于条柱的边界。

让我们从声明直方图的刻度开始。如图 6.6 所示,我们需要两个比例:一个用于水平定位矩形,我们将命名为 xScale ,另一个用于计算矩形的高度和垂直位置,名为 yScale 。由于尺度域和范围都是连续的,并且我们希望范围与域成线性比例,因此我们将使用线性尺度。

图 6.6 水平刻度负责沿 x 轴定位矩形。相反,垂直刻度沿 y 轴排列矩形并计算其高度。

仍然在文件直方图中的函数 drawBeetogram() 中工作.js ,声明水平和垂直刻度,如清单 6.2 中所述。水平刻度的范围从箱子涵盖的最低工资延伸到最高工资。它们分别位于第一个箱的下边界( bins[0].x0 )和最后一个箱的上限 ( bins[bins.length - 1].x1 )。这个刻度的范围从零延伸到内宽,覆盖了内图的整个宽度。我们将此比例保存到名为 xScale 的常量中。

另一方面,垂直刻度负责定位和缩放矩形。它的域从零延伸到最高箱中包含的数据点数(d3.max(箱,d => d.length))。它的范围涵盖了 内部高度 、内部图表的高度和零之间的值。因为在 SVG 中,y 轴从上到下,innerHeight 对应于直方图矩形的底部,而零对应于图表的顶部。我们将此刻度保存在名为 yScale 的常量中,并使用方法 nice() 将其链接起来,以确保 y 轴以舍入值结尾。

示例 6.2 声明比例(直方图.js)

const minSalary = bins[0].x0;                   #Aconst maxSalary = bins[bins.length - 1].x1;     #Aconst xScale = d3.scaleLinear()                 #B  .domain([minSalary, maxSalary])               #B  .range([0, innerWidth]);                      #B const binsMaxLength = d3.max(bins, d => d.length);  #Cconst yScale = d3.scaleLinear()                     #D  .domain([0, binsMaxLength])                       #D  .range([innerHeight, 0])                          #D  .nice();                                          #D

下一步是将一个矩形附加到每个条柱的图表中。为此,我们将数据绑定模式应用于 innerChart ,使用第 6.1 节中计算的箱作为数据,并要求 D3 为每个箱附加一个矩形元素。如图 6.7 所示,矩形的 x 和 y 属性对应于其左上角的位置,而其宽度和高度属性控制其尺寸。

图 6.7 直方图矩形的位置由其 x 和 y 属性控制,其尺寸由其宽度和高度属性控制。矩形的高度与其相关箱中包含的数据点数成正比。

在示例 6.3 中,我们使用数据绑定模式为 innerChart 选择中的每个箱附加一个矩形。然后,我们设置矩形的位置属性:

每个矩形的 x 属性对应于其下边界的位置,使用 xScale 计算。它们的 y 属性可以通过将其 bin 的长度属性传递给 yScale 来找到。矩形的宽度对应于其条柱的上限和下边界之间的距离。矩形的高度等于 innerChart 的高度与其左上角的垂直位置之差。

最后,我们将矩形的填充属性设置为 slateGray ,一个已经在文件共享常量中声明的颜色变量.js 。我们还在矩形中添加了白色笔划,以给人一种它们之间空间的错觉。

示例 6.3 追加矩形(直方图.js)

innerChart  .selectAll("rect")                                       #A  .data(bins)                                              #A  .join("rect")                                            #A    .attr("x", d => xScale(d.x0))                          #B    .attr("y", d => yScale(d.length))                      #B    .attr("width", d => xScale(d.x1) - xScale(d.x0))       #B    .attr("height", d => innerHeight - yScale(d.length))   #B    .attr("fill", slateGray)                               #C    .attr("stroke", white)                                 #C    .attr("stroke-width", 2);                              #C

作为最后一步,我们将轴和标签添加到直方图。我们首先初始化水平轴的底部轴构造函数,使用 d3.axisBottom() 和 xScale 方法作为参考,如清单 6.4 中所述。然后,我们将 SVG 组附加到内部图表,并将其转换为所选内容的底部。通过从此选择中调用轴构造函数,我们将组成轴的所有元素附加到组中:刻度、标签和整个范围内的水平线。最后,我们将一个文本元素附加到 SVG 容器中,作为轴的主标签。此标签的文本为“年薪(美元)”,位于容器的右下角。

我们对左轴进行类似的操作,使用方法d3.axisLeft()和yScale作为参考。附加轴元素后,我们将带有文本“频率”的标签添加到 SVG 容器的左上角。

示例 6.4 添加轴和标签(直方图.js)

const bottomAxis = d3.axisBottom(xScale);                 #AinnerChart                                                #B  .append("g")                                            #B    .attr("transform", `translate(0, ${innerHeight})`)    #B    .call(bottomAxis);                                    #Bsvg                                                       #C  .append("text")                                         #C    .text("Yearly salary (USD)")                          #C    .attr("text-anchor", "end")                           #C    .attr("x", width)                                     #C    .attr("y", height - 5);                               #C const leftAxis = d3.axisLeft(yScale);                     #DinnerChart                                                #E  .append("g")                                            #E    .call(leftAxis);                                      #Esvg                                                       #F  .append("text")                                         #F    .text("Frequency")                                    #F    .attr("x", 5)                                         #F    .attr("y", 20);                                       #F

您可以在图 6.8 中看到完整的直方图。在下一章中,我们将向此可视化添加筛选选项,使我们能够仅查看与标识为女性或男性的受访者相关的数据。

图6.8 美国数据可视化从业人员工资分布的完整直方图。

6.3 创建金字塔图

可视化分布的另一种方法是使用金字塔图。该图表由两个并排竖立的垂直直方图组成,并且由于 D3 箱而易于构建。我们使用金字塔图来比较两个类别的分布。

金字塔图的一个常见用例是女性和男性之间的年龄分布,如图 6.9 所示的示例所示。此类图表通常使用条形来可视化数据,但也可以使用棒棒糖或哑铃形状。金字塔图的 x 轴通常使用百分比作为单位。例如,下图表明,20至24岁的妇女约占总人口的3%,而85至89岁的男子约占1%。

图 6.9 2021 年加拿大男女年龄分布金字塔图。左侧的图表使用条形来可视化数据,而右侧的图表使用棒棒糖或哑铃形状。

小项目:构建金字塔图

现在您已经熟悉了 d3.bin() 并知道如何将矩形附加到图表,您已经掌握了构建金字塔图的所有关键。在本节中,我们要求您创建如下图所示的金字塔图,该金字塔图表示美国数据可视化从业者的收入,这些从业者的身份为女性和男性。

在此图表中,女性的收入由紫色条表示,男性的收入由橙色条表示。条形的长度与工资等级中的受访者人数成正比。例如,我们观察到12%的受访者是收入在60万美元到80万美元之间的女性,而大约6.5%是同一工资等级的男性。横轴的前半部分保留给女性,从 15% 延伸到 0,而后半部分是为男性保留的,从 0 延伸到 15%。纵轴表示工资,从 0 到 $240k。

金字塔图可视化了美国数据可视化从业者的工资分布,标识为女性和男性。

以下是一些可能有助于您完成本练习的提示:

·在文件金字塔中工作.js ,在函数 drawPyramid() 内。SVG 容器和内部图表(SVG 组)已经附加到 div#pyramid 中。

·使用 d3.bin() 并根据薪水生成箱。您需要为女性和男性生成单独的箱。

·您可以为女性和男性声明不同的水平刻度。女性的量表在x轴的前半部分延伸,而男性的量表在后半部分延伸。

·条形的长度与每个工资等级所代表的答复者总数的百分比成正比。这个百分比大约在15到<>%之间变化。

·金字塔图的每一侧都与第 6.2 节中创建的直方图完全相同。只是方向不同!

·颜色常量在共享常量中可用.js .

如果您在任何时候遇到困难或想将您的解决方案与我们的解决方案进行比较,您可以在附录 D 的清单 D.6.1 和本章代码文件的文件夹 6.3-Pyramid / 末尾找到它。但我们鼓励您尝试自己完成它;这是最好的学习方式!

请注意,您的解决方案可能与我们的解决方案略有不同。在开发中,通常有多种方法可以实现相同的结果。

6.4 生成箱线图

箱线图是另一种熟悉的分布可视化方法。它们的主要作用是突出显示数据集的中位数并说明四分位数,四分位数将数据点分为四组大小或多或少相等的组。箱形图的优点是比直方图更紧凑,但对于不熟悉统计数据的读者来说可能更难理解。

如图 6.10 所示,箱形图由一个矩形组成,指示 50% 的数据点所在的位置。此矩形涵盖四分位数间距,从第一个四分位数(第 25 个百分位数)到第三个四分位数(第 75个百分位数)。它还与中位数相交,中位数是分隔数据点下半部分和上半部分的阈值。

从矩形的底部和顶部延伸的垂直线称为晶须。较低的一个从数据集中的最小值扩展到第一个四分位数,而较高的一个从第三个四分位数扩展到最大值。

图 6.10 箱形图由以单个形状编码的五条信息组成:数据集中的最小值、第一个四分位数或第 25 个百分位数、中位数或平均值、第三个四分位数或第75个百分位数以及最大值。

要使用 D3 生成箱形图,我们首先在正在使用的数据集中找到最小值和最大值。然后,我们使用分位数刻度来计算四分位数和中位数。最后,我们将矩形和线元素附加到 SVG 容器,并设置它们的属性以匹配最小、最大、中位数和四分位数位置。

图 6.11 使用 D3 生成箱形图的步骤。

6.4.1 用分位数刻度计算四分位数

分位数是将数据分布拆分为大小相似的组的切割点。另一方面,四分位数是一种分位数,将数据分为四组。在 D3 中,我们使用分位数刻度 () 来计算分位数和四分位数。

让我们回到本章的项目,计算图 6.1 所示的箱形图和托管项目 () 的四分位数。在本项目中,箱线图用于比较女性和男性的工资,这意味着我们需要使用两个数据集:一个包含女性工资,另一个包含男性工资。在本节中,我们将在函数 drawBoxplot() 中工作,该函数包含在文件 box-plot.js 中。请注意,它已经包含了将 SVG 容器附加到 DOM 的代码,以及根据自第 4 章以来使用的策略,将 SVG 容器附加到 DOM 和一个 SVG 组以保存内部图表的代码。

在下面的代码片段中,我们使用 JavaScript filter() 方法来隔离女性的工资,并使用 JavaScript map() 方法将它们映射到一个新的数组中。我们将这个数组保存在 恒定的女性工资 .

const femalesSalaries = data.filter(d => d.gender === "Female") ➥.map(d => d.salary);

为了计算四分位数,我们使用 D3 的分位数刻度,其域和范围都是离散的。域是一组数据点,这里是女性或男性的工资,而范围决定了计算的分位数。如果我们想计算四分位数,从而将数据分为四组,则范围必须是四个元素的数组,例如 [0, 1, 2, 3] .

在下面的代码片段中,我们使用 d3.scaleQuantile() 声明分位数刻度,将女性工资数组作为域传递,将四个值的数组作为范围传递。我们将这个尺度保存到恒定的母四分尺中。

const femalesQuartilesScale = d3.scaleQuantile()  .domain(femalesSalaries)  .range([0, 1, 2, 3]);

最后,我们通过调用 femaleQuartilesScale 的 quantiles() 访问函数来计算四分位数,该函数返回分位数阈值。

const femalesQuartiles = femalesQuartilesScale.quantiles();

如果我们将阈值保存到常量 femalesQuartile 中并将它们记录到控制台中,我们将获得一个包含三个值的数组:

第一个四分位数或第 25个百分位数第二个四分位数或第 50个百分位数,也称为中位数第三个四分位数或第 75个百分位数

请注意,我们也可以直接使用模块 d3-array () 中的方法 d3.median() 找到中位数。此方法将一系列值(此处为薪水)作为参数。

d3.median(femalesSalaries);

在清单 6.5 中,我们对男性工资重复了相同的过程。我们还使用 d3.extent() 计算男女工资的最小值和最大值,它返回一个包含两个值的数组:最小值和最大值。我们很快将使用四分位数、最小值和最大值在图表上定位箱形图的矩形和线元素。

示例 6.5 计算四分位数、最小值和最大值(箱形图.js)

const femalesSalaries = data.filter(d => d.gender === "Female")  #A➥    .map(d => d.salary);                                       #Aconst femalesQuartilesScale = d3.scaleQuantile()                 #B  .domain(femalesSalaries)                                       #B  .range([0, 1, 2, 3]);                                          #B const femalesQuartiles = femalesQuartilesScale.quantiles();      #Cconst femalesExtent = d3.extent(femalesSalaries);                #C const malesSalaries = data.filter(d => d.gender === "Male")      #A➥    .map(d => d.salary);                                       #Aconst malesQuartilesScale = d3.scaleQuantile()                   #B  .domain(malesSalaries)                                         #B  .range([0, 1, 2, 3]);                                          #B const malesQuartiles = malesQuartilesScale.quantiles();          #Cconst malesExtent = d3.extent(malesSalaries);                    #C
6.4.2 在图表上定位多个箱线图

在我们的项目中,我们希望在图形中放置两个箱形图,一个可视化女性的工资分布,另一个可视化男性的工资分布。如图6.1所示,在托管项目()上,性别沿横轴分布,而薪金沿纵轴分布。这意味着对于水平轴,我们需要一个比例,该刻度接受域、性别的离散值,并沿连续范围(可用水平空间)输出值。对于垂直刻度,我们需要一个刻度,该刻度采用连续域、工资,并沿图表的垂直高度(即连续范围)输出值。

图 6.12 水平刻度将负责沿 x 轴放置男女箱形图。垂直刻度将沿 y 轴分布工资,并允许计算箱形图的最小、最大值、中位数和四分位数值的位置。

点刻度

为了沿 x 轴定位性别,我们将使用 D3 的点刻度 d3.scalePoint() ()。点刻度与第3章和第5章中使用的频段刻度非常相似,只是带宽为零。此刻度用于沿连续范围分布离散单元。

在图 6.13 中,我们展示了一个点刻度,它使用字母数组作为域,使用可用的水平空间作为范围。然后,我们将刻度与方法 padding() 链接起来,以设置其外部填充,即两端的空白区域。padding() 方法接受 0 到 1 之间的因子作为参数。外填充的计算方法是该因子乘以步长的大小(填充因子*步长),步长是相邻点之间的空间。

图6.13 D3的点标度与带标相似,带宽为零。它的域由分布在连续范围内的离散元素列表组成。

在声明示例 6.6 中的音阶之前,我们创建了一个包含两个字符串的数组:“Female”和“Male”,并将其命名为 genders。然后,我们声明一个点刻度,该刻度将用于沿 x 轴均匀分布性别。我们将 genders 数组作为刻度的域传递,将其范围设置为从零延伸到图表的内部宽度,并将其填充设置为 0.5,或两个箱形图位置之间距离的 50%。

就像我们在 6.2 节中构建的直方图一样,我们希望工资与其沿 y 轴的位置成线性比例。为此,我们声明了一个线性刻度。它的域从零延伸到数据集中的最高工资,用 d3.max() 计算。它的范围从图表的内部高度扩展到零,因为 y 轴在从上到下的方向上为正。最后,我们将方法nice()链接到线性刻度,以确保轴以圆形值结束。

示例 6.6 声明比例(箱形图.js)

const genders = ["Female", "Male"];               #Aconst xScale = d3.scalePoint()                    #A  .domain(genders)                                #A  .range([0, innerWidth])                         #A  .padding(0.5);                                  #A const maxSalary = d3.max(data, d => d.salary);    #Bconst yScale = d3.scaleLinear()                   #B  .domain([0, maxSalary])                         #B  .range([innerHeight, 0])                        #B  .nice();                                        #B

然后,我们使用刻度绘制箱形图的轴。在清单 6.7 中,我们首先声明了底部轴的构造函数,使用 d3.axisBottom() 方法并将 xScale 作为引用传递。为了隐藏轴的外部刻度,我们将构造函数与 tickSizeOuter() 链接起来,我们给它一个零值。然后我们将一个 SVG 组附加到内部图表,将其转换为底部,并调用构造函数来生成轴元素。

类似地,我们为左轴声明一个构造函数,使用 d3.axisLeft() 并传递 yScale 作为引用。我们将一个组附加到内部图表并调用构造函数。最后,我们通过向 SVG 容器追加文本元素并为其指定“年薪 (UDS)”值,在 y 轴上方显示一个标签。

示例 6.7 绘制轴(箱形图.js)

const bottomAxis = d3.axisBottom(xScale)                 #A  .tickSizeOuter(0);                                     #AinnerChart                                               #B  .append("g")                                           #B    .attr("transform", `translate(0, ${innerHeight})`)   #B    .call(bottomAxis);                                   #B const leftAxis = d3.axisLeft(yScale);                    #CinnerChart                                               #C  .append("g")                                           #C    .call(leftAxis);                                     #Csvg                                                      #D  .append("text")                                        #D    .text("Yearly salary (USD)")                         #D    .attr("x", 0)                                        #D    .attr("y", 20);                                      #D
6.4.3 绘制箱形图

我们现在准备绘制箱形图!如前所述,箱线图由三个元素组成,如图 6.14 所示:

从第一个四分位数延伸到第三个四分位数的矩形。位于中位数的一条线,对应于第二个四分位数。晶须,或从最小值延伸到第一个四分位数和从第三个四分位数延伸到最大值的线。

图 6.14 箱形图由一个从第一个四分位数延伸到第三个四分位数的矩形、一条位于中位数的线和从最小值延伸到第一个四分位数以及从第三个四分位数延伸到最大值的晶须组成。

要绘制箱形图,我们首先声明两个常量: boxplotWidth 和 boxplotStrokeWidth ,分别负责保存箱形图宽度的值和笔画宽度。在清单 6.8 中,我们分别给了它们 60px 和 3px 的值,但您可以根据自己的喜好随意调整它们。这些值可以多次用于绘制箱形图,因此将它们放在常量中有助于将来进行更改。

然后,我们遍历示例 6.8 中声明的 genders 数组。对于每个性别,我们将一个 SVG 组附加到内部图表。我们将 stroke 属性设置为在共享常量中声明的颜色 slateGray (#305252.js ,并将 stroke-width 属性设置为之前声明的常量 boxplotStrokeWidth。附加到此组中的元素将继承这些属性,从而防止我们多次设置它们。

接下来,我们将一个矩形元素附加到 SVG 组。要设置其 x 属性(其左上角的水平位置),我们将当前性别传递给 xScale .由于 xScale 返回箱形图中心的位置,因此我们需要减去箱形图宽度的一半才能获得 x 属性。y 属性(左上角的垂直位置)对应于第三个四分位数的位置,我们通过将四分位数数组中的第三个值传递给 yScale 来获得该位置。矩形的宽度用 boxplotWidth 设置。它的高度是通过从第一个四分位数之一中减去第三个四分位数的位置来计算的(因为 y 轴在从上到下的方向上是正的!这些最后的值也由 yScale 返回。最后,我们将矩形的填充属性设置为值“透明”。

中位数的位置用 SVG 线表示。在水平方向上,线条从矩形的左侧延伸到右侧。这两个值(x1和x2)是通过找到矩形的中心(xScale(gender))并加上或减去箱形图宽度的一半来计算的。直线(y1和y2)的垂直位置是通过将每个性别的第二个四分位数传递给yScale来找到的。为了强调中位数,我们为女性和男性赋予其笔画不同的颜色(分别为“#826C7F”和“#FA7E61”,与共享常量中声明的常量 womenColor 和 menColor .js )和 10px 的笔画宽度不同。

示例 6.8 绘制箱形图的矩形和中位数(箱形图.js)

const boxplotWidth = 60;                                                 #Aconst boxplotStrokeWidth = 4;                                            #A genders.forEach(gender => {                                              #B   const boxplotContainer = innerChart                                    #C    .append("g")                                                         #C      .attr("stroke", slateGray)                                         #C      .attr("stroke-width", boxplotStrokeWidth);                         #C   boxplotContainer                                                       #D    .append("rect")                                                      #D      .attr("x", xScale(gender) - boxplotWidth/2)                        #D      .attr("y", gender === "Female"                                     #D        ? yScale(femalesQuartiles[2])                                    #D        : yScale(malesQuartiles[2]))                                     #D      .attr("width", boxplotWidth)                                       #D      .attr("height", gender === "Female"                                #D        ? yScale(femalesQuartiles[0]) - yScale(femalesQuartiles[2])      #D        : yScale(malesQuartiles[0]) - yScale(malesQuartiles[2]))         #D      .attr("fill", "transparent");                                      #D   boxplotContainer                                                       #E    .append("line")                                                      #E      .attr("x1", xScale(gender) - boxplotWidth/2)                       #E      .attr("x2", xScale(gender) + boxplotWidth/2)                       #E      .attr("y1", gender === "Female"                                    #E        ? yScale(femalesQuartiles[1])                                    #E        : yScale(malesQuartiles[1]))                                     #E      .attr("y2", gender === "Female"                                    #E        ? yScale(femalesQuartiles[1])                                    #E        : yScale(malesQuartiles[1]))                                     #E      .attr("stroke", gender === "Female"                                #E        ? womenColor                                                     #E        : menColor)                                                      #E      .attr("stroke-width", 10);                                         #E });                                                                      #B

在清单 6.9 中,我们使用 SVG 线在矩形的底部和顶部添加晶须。底部的胡须由一条从最低工资值到第一个四分位数的垂直线和一条位于最低工资位置的水平线组成。顶须由从第三个四分位数到最高工资的垂直线和最高工资位置的水平线组成。

示例 6.9 绘制箱形图的胡须(箱形图.js)

genders.forEach(gender => {   ...   boxplotContainer                                  #A    .append("line")                                 #A      .attr("x1", xScale(gender))                   #A      .attr("x2", xScale(gender))                   #A      .attr("y1", gender === "Female"               #A        ? yScale(femalesExtent[1])                  #A        : yScale(malesExtent[1]))                   #A      .attr("y2", gender === "Female"               #A        ? yScale(femalesQuartiles[2])               #A        : yScale(malesQuartiles[2]));               #A   boxplotContainer                                  #B    .append("line")                                 #B      .attr("x1", xScale(gender) - boxplotWidth/2)  #B      .attr("x2", xScale(gender) + boxplotWidth/2)  #B      .attr("y1", gender === "Female"               #B        ? yScale(femalesExtent[0])                  #B        : yScale(malesExtent[0]))                   #B      .attr("y2", gender === "Female"               #B        ? yScale(femalesExtent[0])                  #B        : yScale(malesExtent[0]));                  #B   boxplotContainer                                  #C    .append("line")                                 #C      .attr("x1", xScale(gender))                   #C      .attr("x2", xScale(gender))                   #C      .attr("y1", gender === "Female"               #C        ? yScale(femalesQuartiles[0])               #C        : yScale(malesQuartiles[0]))                #C      .attr("y2", gender === "Female"               #C        ? yScale(femalesExtent[0])                  #C                  : yScale(malesExtent[0]));                  #C   boxplotContainer                                  #D    .append("line")                                 #D      .attr("x1", xScale(gender) - boxplotWidth/2)  #D      .attr("x2", xScale(gender) + boxplotWidth/2)  #D      .attr("y1", gender === "Female"               #D        ? yScale(femalesExtent[1])                  #D        : yScale(malesExtent[1]))                   #D      .attr("y2", gender === "Female"               #D        ? yScale(femalesExtent[1])                  #D        : yScale(malesExtent[1]));                  #D });

完成后,您的箱形图将类似于图 6.15 中的箱形图。请注意,本章图中显示的箱形图与您的结果之间可能存在细微差异,因为工资值是在负载数据中随机生成的.js .

图 6.15 美国从事数据可视化工作的女性和男性工资分布的完整箱形图。

如果您仍然不确定箱形图的含义,请看图 6.16。它显示了箱形图可视化的实际数据点。每个圆圈的垂直位置对应于它所代表的薪水,而水平位置是随机计算的,以减少重叠。请注意,矩形中的圆密度更高,矩形包含 50% 的数据点。当我们沿着晶须远离矩形时,这种密度会降低。

图 6.16 箱形图可视化的薪资数据点。每个圆圈的垂直位置对应于它所代表的薪水,而水平位置是随机计算的,以减少重叠。

6.5 比较分布与小提琴图

我们将以一个可视化来结束本章,该可视化结合了我们到目前为止讨论的许多概念:小提琴情节。小提琴图是镜像的曲线区域,在包含多个数据点和锥形的区域中凸起,而这些区域很少存在。它们常见于处理剂量和功效的医学图表中,但也更普遍地用于可视化分布。与仅显示预定义信息(最小值、最大值、中位数和四分位数)的箱形图不同,小提琴图对整个分布进行编码。它们通常与附加信息(如均值和四分位数的位置)相结合。

在本节中,我们将比较数据集领先的五个数据可视化角色的薪酬分布:分析师、开发人员、设计师、科学家和领导。我们选择不可视化其他职业,如制图师和教师,因为它们的数据点数量很少。图 6.17 显示了我们将为五个角色中的每一个构建的小提琴图。如您所见,它们与沿四分位数范围的一条线和平均或平均工资位置的点组合在一起。我们也可以选择表示中位数而不是平均值;这两种方法都是有效的。

图6.17 五大数据可视化角色薪酬分布的小提琴图。每个图都与一条从第一个四分位数延伸到第三个四分位数的灰线和一个位于平均值处的白点组合在一起。

我们将开始在文件小提琴.js 中工作,在函数 drawViolinCharts() 中。请注意,该文件已包含将 SVG 容器和内部图表添加到 DOM 的代码。我们还声明了名为 角色 ,其中包括一个对象列表,其中包含我们想要可视化的职业的 id。

要构建小提琴图,我们需要隔离每个角色的工资,计算它们的平均值或平均值,将它们组织到箱中并计算它们的四分位数。在示例 6.10 中,我们在遍历角色数组的同时执行这些计算。为了隔离当前角色的数据,我们使用 JavaScript 方法 filter() 并链接方法 map() 来生成一个仅包含薪水值的数组。我们将此数组存储在键工资下,以便在角色数组中访问它。

然后,我们使用方法 d3.mean() 计算工资数组的平均值。此方法是模块 d3-array () 的一部分,其工作原理类似于 d3.min() 和 d3.max()。

我们按照本章中使用的技术,使用 d3.bin() 将工资和具有 D3 分位数刻度的四分位数组织到箱中。所有这些值也保存在角色数组中。

最后,我们使用 JavaScript 方法 sort() 对角色进行排序以确保平均值按升序显示。这将提高图表的可读性并促进比较。

示例 6.10 提取小提琴图的信息(小提琴.js)

const roles = [                                      #A  {id: "Designer" },                                 #A  {id: "Scientist" },                                #A  {id: "Developer" },                                #A  {id: "Analyst" },                                  #A  {id: "Leadership" },                               #A];                                                   #A roles.forEach(role => {                              #B   role["salaries"] = data                            #C    .filter(d => d.role === role.id)                 #C    .map(d => d.salary);                             #C   role["mean"] = d3.mean(role.salaries);             #D   role["bins"] = d3.bin()(role.salaries);            #E   const quartilesScale = d3.scaleQuantile()          #F    .domain(role.salaries)                           #F    .range([0, 1, 2, 3]);                            #F  role["quartiles"] = quartilesScale.quantiles();    #F });                                                  #B roles.sort((a, b) => a.mean - b.mean);               #G

图 6.18 显示我们需要三个音阶来构建小提琴图表:

用于沿 x 轴分布角色的点刻度。工资值沿 y 轴的线性刻度。一种线性刻度,用于根据每个箱内的数据点数计算小提琴图的宽度。

图 6.18 要构建小提琴图表,我们需要三个刻度:负责沿 x 轴分布角色的点刻度,沿 y 轴分布工资值的线性刻度,以及根据每个箱内数据点的数量计算小提琴图宽度的线性刻度。

在示例 6.11 中,我们声明了一个点刻度,负责沿 x 轴分布角色,并将其保存在名为 xScale 的常量中。如第 6.4.2 节所述,d3.scalePoint() 接受离散域,此处是使用 JavaScript map() 方法创建的角色数组,并从连续范围(内部图表宽度上的可用空间)返回值。我们使用 padding() 方法在轴的两端设置填充,并为其指定值 0.7,即比例步长的 70%。

然后,我们声明常数 yScale ,用于在 y 轴上分配工资的线性刻度。量表的域是连续的,并在数据集中的零和最大工资之间延伸。范围也是连续的,沿内部图表的垂直空间返回值。我们将这个刻度与方法 nice() 链接起来,以确保 y 轴以整数结尾。

最后,我们声明另一个线性刻度,名为 小提琴刻度 ,它将负责计算小提琴图的宽度。此宽度将沿 y 轴变化,具体取决于每个工资等级中的数据点数。

6.11 声明小提琴图(小提琴.js)的音阶

const xScale = d3.scalePoint()                       #A  .domain(roles.map(d => d.id))                      #A  .range([0, innerWidth])                            #A  .padding(0.7);                                     #A const maxSalary = d3.max(data, d => d.salary);       #Bconst yScale = d3.scaleLinear()                      #B  .domain([0, maxSalary])                            #B  .range([innerHeight, 0])                           #B  .nice();                                           #B let maxBinLength = 0;                                #Croles.forEach(role => {                              #C  const max = d3.max(role.bins, d => d.length);      #C  if (max > maxBinLength) {                          #C    maxBinLength = max;                              #C  }                                                  #C});                                                  #C const violinsScale = d3.scaleLinear()                #D  .domain([0, maxBinLength])                         #D  .range([0, xScale.step()/2]);                      #D
练习:附加轴

在本书中,您已经见证了如何将轴多次附加到图表。现在轮到你了!使用清单 6.11 中声明的刻度为我们的小提琴图表绘制轴。角色应沿 x 轴显示,薪水应沿 y 轴显示。此外,在 y 轴上添加一个带有文本“年薪(美元)”的标签。

完成后,您的轴应类似于下图中的轴。如果您遇到困难或想将您的解决方案与我们的解决方案进行比较,请参阅附录 D 的清单 D.6.2 或本章代码文件中的文件夹 6.5-Violins / end。

小提琴图表的轴。角色沿 x 轴分布,而工资沿 y 轴表示。

小提琴图可以描述为围绕不可见的中心轴镜像的平滑直方图。为了说明此概念,我们将首先为每个角色创建一个垂直方向的直方图。

在示例 6.12 中,我们遍历角色,并将一个 SVG 组附加到每个角色的内部图表中,并将其保存在名为 roleContainer 的常量中。此策略将帮助我们保持标记整洁且易于检查。

然后,我们使用数据绑定模式为角色中的每个 bin 追加一个矩形元素。矩形从当前角色的中心轴开始,该中心轴由 xScale 返回。它们的宽度与相关箱中包含的数据点数成正比,并由 小提琴比例 .最后,矩形的垂直位置和高度取决于工资括号,并由 yScale 返回。

示例 6.12 为每个角色绘制直方图(小提琴.js)

roles.forEach(role => {                                     #A   const roleContainer = innerChart                          #B    .append("g");                                           #B   roleContainer                                             #C    .selectAll(`.bar-${role.id}`)                           #C    .data(role.bins)                                        #C    .join("rect")                                           #C      .attr("class", `bar-${role.id}`)                      #C      .attr("x", xScale(role.id))                           #C      .attr("y", d => yScale(d.x1))                         #C      .attr("width", d => violinsScale(d.length))           #C      .attr("height", d => yScale(d.x0) - yScale(d.x1))     #C      .attr("fill", slateGray)                              #C      .attr("fill-opacity", 0.4)                            #C      .attr("stroke", white)                                #C      .attr("stroke-width", 2);                             #C });                                                         #A

完成后,直方图应类似于图 6.19 中的直方图。请注意,分析师的角色是我们拥有最多数据点的角色,而领导职位提供了最高薪水的可能性。虽然绘制直方图不是创建小提琴图表的必要步骤,但它们将帮助我们了解如何计算小提琴的路径。

图 6.19 每个职位的工资分布直方图。

要绘制小提琴图,我们所要做的就是绘制一条穿过每个直方图条尖端的曲线。在清单 6.13 中,我们仍在角色循环中工作。我们首先声明一个区域生成器并设置其访问器函数。我们希望该区域具有两个水平边界。第一个(x0)位于当前角色的中心线,而第二个(x1)位于相关条形的顶端。数据点垂直放置在每个柱线的中间,我们使用 d3.curveCatmullRom 平滑曲线。有关面积生成器的更多信息,请参阅第 4 章。

然后,我们将一个路径元素附加到 角色容器 .为了计算其 d 属性,我们调用区域生成器并将当前角色的箱作为参数传递。

6.13 绘制半小提琴图(小提琴.js)

roles.forEach(role => {   ...   const areaGenerator = d3.area()                                #A    .x0(d => xScale(role.id))                                    #A    .x1(d => xScale(role.id) + violinsScale(d.length))           #A    .y(d => yScale(d.x1) + ((yScale(d.x0) - yScale(d.x1))/2))    #A    .curve(d3.curveCatmullRom);                                  #A   roleContainer                                                  #B    .append("path")                                              #B      .attr("d", areaGenerator(role.bins))                       #B      .attr("fill", "transparent")                               #B      .attr("stroke", slateGray)                                 #B      .attr("stroke-width", 2);                                  #B };

您现在应该有如图 6.20 所示的半小提琴图。这些区域从每个角色的中心线开始,经过直方图条形的尖端。

图 6.20 通过在直方图的每个条形的尖端传递曲线绘制的半小提琴图。

要最终确定小提琴图,请首先注释掉创建直方图的代码行。在 JavaScript 中,单行注释以 // 开头,多行注释以 /* 开头,以 */ 结尾。

要完成小提琴的形状,我们只需要镜像小提琴的右半部分。在清单 6.14 中,我们更新了面积生成器的 x0() 访问器函数以反映 x1()。然后,我们删除路径的描边,将填充设置为共享常量中可用的颜色 slateGray.js (#305252),并将其不透明度更改为 30%。

6.14 绘制完整的小提琴图(小提琴.js)

roles.forEach(role => {   ...   const areaGenerator = d3.area()                                .x0(d => xScale(role.id) - violinsScale(d.length))           #A    .x1(d => xScale(role.id) + violinsScale(d.length))               .y(d => yScale(d.x1) + ((yScale(d.x0) - yScale(d.x1))/2))        .curve(d3.curveCatmullRom);                                     roleContainer                                                      .append("path")                                                    .attr("d", areaGenerator(role.bins))                             .attr("fill", slateGray)                                   #B      .attr("fill-opacity", 0.3);                                #B };

我们有小提琴情节!我们只需要添加一些细节即可完成本章的项目,您将在下面的练习中执行此操作。

图 6.21 每个数据可视化角色的小提琴图。

练习:将四分位数间距和平均值添加到小提琴图中

要完成本章的项目,请在小提琴图表上注明四分位数间距和平均值,如图 6.22 所示。规格如下:

·四分位数间距由从第一个四分位数扩展到第三个四分位数的矩形表示。这些值在角色数组中可用。

·矩形的宽度为 8px,其角的圆角半径为 4px。它们的颜色是灰色,您可以使用共享常量中可用的变量 gray.js (#606464)。

·平均值用圆圈表示。

·圆圈的半径为 3px,颜色为白色,您可以使用共享常量中可用的变量白色.js (#faffff)。

如果您在任何时候遇到困难或想将您的解决方案与我们的解决方案进行比较,您可以在附录 D 的清单 D.6.3 和本章代码文件的末尾的文件夹 6.5-Violins 中找到它。但是,像往常一样,我们鼓励您尝试自己完成它。您的解决方案可能与我们的略有不同,没关系!

图 6.22 具有四分位距和平均值的小提琴图。

本书的第一部分到此结束!您现在应该已经很好地掌握了 D3 的基本技术。在以下章节中,我们将深入探讨更高级的主题和可视化效果。

6.6 小结为了可视化数据分布,我们通常需要通过将数据集的数据点分组到桶或箱中来预处理数据集,这些桶或箱是沿轴宽度相等的组。在 D3 中,我们使用方法 d3.bin() 执行此操作。为了绘制直方图,我们使用数据绑定模式将矩形元素附加到选区,使用箱作为数据。每个矩形的长度与其相关箱中包含的数据点数成正比。金字塔图的结构类似于直方图的结构,只是它们用于并排比较两个分布。箱形图由从第一个四分位数到第三个四分位数的矩形和从最小值延伸到第一个四分位数和第三个四分位数到最大值的晶须或线组成。中位数通常由垂直线表示。在 D3 中,我们使用分位数比例 d3.scaleQuantile() 计算四分位数,它接受离散域和离散范围。如果我们想计算四分位数,从而将数据分为四组,则范围必须是四个元素的数组,例如 [0, 1, 2, 3] .要使用 D3 绘制箱形图,我们只需要将矩形和线元素附加到选区,并根据最小值、最大值、中位数和四分位数值的位置设置其属性。D3的点标度d3.scalePoint()类似于第3章中讨论的带标,只是带宽为零。它的域由分布在连续范围内的离散元素列表组成。小提琴图是镜像的曲线区域,在包含多个数据点和锥形的区域中凸起,而这些区域很少存在。它们可以被描述为围绕不可见的中心轴镜像的平滑直方图。要绘制小提琴图,我们需要生成一个穿过每个箱子尖端的区域。首先绘制直方图以定位可能会有所帮助。小提琴图通常与显示四分位数间距的矩形和位于平均值或中位数的圆圈相结合。

标签: #js定位当前位置原理 #htmlmap百分比 #html平均分布 #html图片均匀分布