视觉开发 学习 参考课本
I’ve been using D3 quite a bit recently to build an analytics dashboard. The main reason I went with D3 was because I got tired of dealing with the limitations of the various charting libraries and trying to understand their documentation.
我最近一直在使用D3来构建分析仪表板。 我选择D3的主要原因是因为我厌倦了处理各种图表库的局限性并试图理解它们的文档。
I found myself in a situation where I was spending more time trying to figure out whether the library I was using could do what I wanted rather than actually trying to figure out how to do what I wanted.
我发现自己处于一种情况下,我花了更多时间试图确定我正在使用的库是否可以完成我想要的工作,而不是实际尝试确定如何执行我想要的工作。
With D3, there is almost never a case of whether you can do something. It’s always about how you can do something.
使用D3,几乎永远不会出现您是否可以做某事的情况。 它总是与您如何做某事有关。
Granted, getting to grips with D3’s API is a mammoth challenge but it’s worth the hassle.
诚然,掌握D3的API是一个巨大的挑战,但值得一试。
In this article, I’m going to try to give you a visual representation of my understanding of D3 after working with it for a while now.
在本文中,我将在一段时间后尝试以直观的方式展示我对D3的理解。
The thing I love about D3 is that its built on top of the smallest primitives, and you have access to all of those primitives.
我喜欢D3的地方是它建立在最小的原语之上,您可以访问所有这些原语。
If you were to use a typical charting library for React, you’d probably do something like this
如果您要为React使用典型的图表库,则可能会执行以下操作
import { BarChart } from 'generic-charting-library'
export default class Chart() {
render() {
return (
<BarChart data={data} /
)
}
}
The above is perfectly fine for most use cases. However, the moment you want to do anything slightly more complicated like interacting with the bar chart in unique ways (think beyond just displaying a tool tip), my personal experience has been that it turns into a war with the documentation of the library.
上面对于大多数用例来说都是完美的。 但是,当您想做一些更复杂的事情时,例如以独特的方式与条形图进行交互(不仅仅显示工具提示),我的个人经验是,它与库的文档变成了一场战争。
First, you have to figure out if it is even possible to do this with the library, and then you have to figure out how to do it.
首先,您必须确定是否甚至可以使用该库执行此操作,然后必须确定如何执行此操作。
The first part, in my experience, is the hard bit. It is quite frustrating to look for something that you’re not sure exists. However, with D3, that isn’t the case. Pretty much anything you want to do can be done with D3. It is simply a matter of figuring out how to do it.
根据我的经验,第一部分是困难的部分。 寻找不确定的东西真是令人沮丧。 但是,对于D3,情况并非如此。 使用D3几乎可以完成任何您想做的事情。 这只是弄清楚如何做的问题。
The downside of course is that D3’s API and documentation is so vast and expansive, that you end up having disparate pieces of knowledge of how things work. This is also a consequence of how I chose to learn D3, that is by building something with it. When you choose to build something with a technology, you only look up the pieces relevant to what you’re currently building.
当然,不利的一面是D3的API和文档如此之大和广泛,以至于您最终对事物的工作原理有了不同的了解。 这也是我选择学习D3的结果,那就是通过使用它来构建一些东西。 当您选择使用某种技术进行构建时,您只会查找与当前正在构建的内容相关的部分。
Say, for instance, you wanted to build a bar chart. Well, you’re probably to going look up something like how to define and place axes on a web page. Then, you’ll probably look up how to define the actual bars of the chart themselves. These are well defined problems and have straightforward solutions.
举例来说,您想构建一个条形图。 好吧,您可能需要查找类似如何在网页上定义和放置轴的内容。 然后,您可能会查找如何定义图表本身的实际柱线。 这些是定义明确的问题,并且有直接的解决方案。
I took pretty much the same route and ended up in a frustrating place where I could get things to work but I couldn’t quite understand how it was all coming together.
我几乎走了同样的路,最终到了一个令人沮丧的地方,在那里我可以使事情工作,但我不太明白这一切是如何融合在一起的。
I’m going to explain my thought process of how I put together all the different pieces.
我将解释我如何组合所有不同部分的思考过程。
The following image is that of a simplistic bar chart (something we’ll try to recreate for this post)
下图是条形图的简化图(我们将尝试为该帖子重新创建该图)
Now, here’s the same bar chart with the different components being marked out. By components, I mean the different things we will need to worry about when creating the bar chart using D3.
现在,这是相同的条形图,其中标记了不同的组件。 对于组件,我的意思是在使用D3创建条形图时我们需要担心的不同事情。
We’ll explore everything except the spacing between ticks in detail. The spacing between ticks will take care of itself when figure out how to construct the axes.
除了刻度线之间的间隔,我们将详细研究所有内容。 找出如何构造坐标轴时,刻度线之间的间距会自动处理。
Before we can even start with composing the small pieces that make up the big picture, we need to understand the one element that is a part of every component in the chart above: the SVG element.
在我们甚至开始组成构成全局的小片段之前,我们需要了解上图中每个组件的一部分的一个元素:SVG元素。
A D3 chart is primarily composed of SVG elements. In the bar chart above, the x-axis, the y-axis, each individual bar are all instances of SVG elements.
D3图表主要由SVG元素组成。 在上面的条形图中,x轴,y轴,每个单独的条形图都是SVG元素的所有实例。
I recommend reading this page to get a better understanding of what an SVG is. SVG’s are essentially how you describe 2D graphics on a web page.
我建议阅读此页以更好地了解什么是SVG。 SVG本质上就是您在网页上描述2D图形的方式。
The most basic example of an SVG element is the circle element.
SVG元素的最基本示例是circle元素。
See the Pen qGMxzO by Zaid Humayun (@redixhumayun) on CodePen.
在CodePen上查看Zaid Humayun( @redixhumayun )的Pen qGMxzO 。
Take a look at the codepen above and you should see the definition for the circle SVG inside the HTML file.
看一下上面的代码笔,您应该在HTML文件中看到圆圈SVG的定义。
I’d advise going through the SVG documentation on MDN (linked above), and familiarizing yourself with it. D3 makes extensive use of SVG’s.
我建议您仔细阅读MDN上的SVG文档(上面已链接),并熟悉它。 D3广泛使用SVG。
There is a specific kind of SVG element called a G element. Similar to the way we defined the circle element above, we define this with
有一种特定的SVG元素称为G元素。 与我们上面定义圆形元素的方式类似,我们使用
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<!-- Using g to inherit presentation attributes -->
<g fill="white" stroke="green" stroke-width="5">
<circle cx="40" cy="40" r="25" />
<circle cx="60" cy="60" r="25" /> </g>
</svg>
Think of a g
element as similar to a div
element that is used as a container in HTML. They are both used to group certain elements.
可以将g
元素视为与HTML中用作容器的div
元素相似。 它们都用于对某些元素进行分组。
Read the MDN documentation for g
here
在此处阅读MDN文档以获取g
If you want to understand why people make use of the g
element, read this
如果您想了解人们为什么使用g
元素,请阅读此内容
Before we continue any further, let’s quickly create the data we’ll be using. Here is a sample JSON file we can use to create a bar chart.
在继续进行下一步之前,让我们快速创建将要使用的数据。 这是一个示例JSON文件,可用于创建条形图。
[
{
"key": "A",
"value": 20
},
{
"key": "B",
"value": 40
},
{
"key": "C",
"value": 80
},
{
"key": "D",
"value": 55
},
{
"key": "E",
"value": 70
}
]
Let’s start off with creating a simple chart area first. The way to create a chart area is to set up a basic SVG element and then assign a viewBox attribute to it.
让我们首先创建一个简单的图表区域。 创建图表区域的方法是设置一个基本的SVG元素,然后为其分配一个viewBox属性。
Just ignore what the viewBox attribute is for now. It isn’t relevant to the discussion of this post.
只需忽略一下viewBox属性是什么。 这与本文的讨论无关。
<svg class="chart" viewBox="0 0 800 600">
You won’t see anything on the screen yet because the chart is transparent at this point. If you use the browser inspector, however, you will see the SVG element.
您现在还看不到屏幕上的任何内容,因为此时图表是透明的。 但是,如果使用浏览器检查器,则会看到SVG元素。
We’ll also define some dimensions for our chart area like the height, width and margins.
我们还将为图表区域定义一些尺寸,例如高度,宽度和边距。
const height = 600
const width = 800
const margin = { top: 15, right: 30, bottom: 15, left: 30 }
Now that we’ve defined the dimensions we need to actually create the area for our chart in the DOM. To do that, we need to use something called d3.select
现在,我们已经定义了尺寸,我们需要为DOM中的图表实际创建区域。 为此,我们需要使用名为d3.select
东西。
Think of this as exactly the same as the set of document.getElementBy[X]
commands the DOM offers natively.
将此与DOM本身提供的document.getElementBy[X]
命令集完全相同。
When you use something like d3.select('.chart')
, you are asking D3 to select an element with a class named chart.
当您使用类似d3.select('.chart')
,您正在要求D3选择一个名为chart的类的元素。
Note that we’re saving the selection inside of a variable. This will be important later.
请注意,我们将选择内容保存在变量中。 稍后将很重要。
When you select something with d3.select
, D3 allows you to use method chaining to alter attributes like width and height, like I've done here.
当您使用d3.select
选择d3.select
,D3允许您使用方法链接来更改属性,例如宽度和高度,就像我在这里所做的那样。
const chart = d3.select(".chart")
.attr("width", width)
.attr("height", height)
What we’ll end up with is something like the following image
我们最终将得到如下图所示的图像
Don’t worry about the margins for now. We’ll take care of that later.
现在不用担心利润率。 我们稍后会处理。
Now, we start with the meaty part of D3: creating and placing our axes.
现在,我们从D3的最重要部分开始:创建并放置轴。
Before we can start, we need to understand something fundamental about the way D3 axes work: they are essentially a mapping from one set of values to another set of values.
在开始之前,我们需要了解有关D3轴工作方式的一些基本知识:它们本质上是从一组值到另一组值的映射。
The two sets of values are called domain and range. D3’s mapping works from the domain onto the range.
这两组值称为域和范围。 D3的映射从域到范围。
I have defined two really simple number lines to illustrate the domain and range. The range is the exact same as the domain with double the number of markings.
我定义了两个非常简单的数字线来说明域和范围。 范围与标记数量加倍的域完全相同。
In this example its really easy to see how the domain can be mapped to the range. You just need to multiply the value by 2 since the range has double the number of ticks and has the same starting tick value of 0.
在此示例中,非常容易看到如何将域映射到范围。 您只需要将该值乘以2,因为该范围的刻度数是原来的两倍,并且起始刻度值为0。
I’ve drawn two dashed lines to show the following mappings
我绘制了两条虚线以显示以下映射
2 -> 4
5.5 -> 11
Now, D3 is not limited only to having real numbers (or even just numbers) to define scales. You can even use characters to define your scales.
现在,D3不仅限于使用实数(甚至只是数字)来定义比例。 您甚至可以使用字符来定义比例。
We’ll start with the Y scale.
我们将从Y比例尺开始。
D3 has different kinds of scales but the one we’ll be using is called the linear scale.
D3具有不同种类的比例尺,但我们将使用的一种称为线性比例尺。
To define the scale we need two things: the domain and the range.
要定义规模,我们需要两件事:域和范围。
We’ll use a simple, stupid rule to define our domain. We’ll assume that the minimum value we can have for one of our categories is 0 and the max value is 100. No negative numbers. The domain then becomes [0, 100]
我们将使用一个简单的愚蠢规则来定义我们的域。 我们假设我们其中一个类别的最小值为0,最大值为100。没有负数。 然后该域变为[0, 100]
const y = d3.scaleLinear()
.domain([0, 100])
.range([height - margin.bottom, margin.top])
One thing we need to examine here is the range. It took me a little while to understand why the range seems to be in “reverse”. My initial thought was that the range should be [margin.top, height - margin.bottom]
. But, we want our Y axis for the chart to start at the bottom and moves vertically upwards.
我们需要在这里检查的一件事是范围。 我花了一些时间才了解为什么范围似乎处于“反向”状态。 我最初的想法是范围应该是[margin.top, height - margin.bottom]
。 但是,我们希望图表的Y轴从底部开始并垂直向上移动。
We’ll consider the following two scenarios in a subsequent diagram to examine this.
我们将在后续图表中考虑以下两种情况,以对此进行研究。
1. .range([height - margin.bottom, margin.top])
2. .range([margin.top, height - margin.bottom])
The important difference between the two scenarios is that in the first scenario we are treating the value of height as our ‘zero’ value. In the second scenario, we are treating the margin.top
value as our 'zero' value.
两种情况之间的重要区别在于,在第一种情况下,我们将高度值视为“零”值。 在第二种情况下,我们将margin.top
值视为“零”值。
One thing to remember before we proceed further: the point of origin of every SVG coordinate system is at the top left corner.
在继续之前,要记住一件事:每个SVG坐标系的原点在左上角。
Interpreted another way, the bottom of the Y-axis is our ‘zero’ value in the first scenario and the top of the Y-axis is our ‘zero’ value in the second scenario.
用另一种方式解释,在第一种情况下,Y轴的底部是我们的“零”值,在第二种情况下,Y轴的顶部是我们的“零”值。
In the image above, scenario 1 is on the left and scenario 2 is on the right. You can see the direction of movement for the domain in each image.
在上图中,方案1位于左侧,方案2位于右侧。 您可以在每个图像中看到域的移动方向。
In scenario 1, the domain grows upwards from the bottom, which is what we want. In scenario 2, the domain grows downwards from the top, which is what we don’t want.
在方案1中,域从底部向上增长,这就是我们想要的。 在方案2中,域从顶部向下扩展,这是我们不想要的。
I appreciate that I might have made things more confusing for those of you who managed to grab the above intuitively but this is something that took me a while to figure out. If you understand intuitively, don’t worry about the above. If you still don’t get it, you will by the end of this post.
我很高兴为那些设法直观地掌握以上内容的人带来一些困惑,但这是我花了一段时间才弄清楚的。 如果您凭直觉理解,则不必担心以上情况。 如果仍然不了解,您将在本文结尾。
The X scale is a little easier to figure out. We need the X scale to grow from left to right keeping in mind the width of our chart area and also the margins on the left and right.
X刻度稍微容易弄清楚。 我们需要X比例从左到右增长,同时要记住我们图表区域的宽度以及左右边距。
The domain on this scale is a little more confusing though because we aren’t dealing with numbers anymore. We are dealing with the letters of our categories instead.
但是,由于我们不再处理数字,因此这种规模的域更加令人困惑。 我们正在处理类别的字母。
To figure out how to construct this scale, we first need to understand something called the ordinal scale. The quickest way to understand the ordinal scale is to consider the differences between the linear and ordinal scales.
为了弄清楚如何构建此量表,我们首先需要了解一种称为序数表的东西。 理解序数标度的最快方法是考虑线性标度和序数标度之间的差异。
In the image above, you can see a poor drawing of the two scales. The important difference to note is that the linear scale is a continuous scale and the ordinal scale is a discrete scale.
在上图中,您可以看到两个比例尺的绘图效果不佳。 要注意的重要区别是线性刻度是连续刻度,而顺序刻度是离散刻度。
In the example of the linear scale, if you were to provide a value of 5.5, it would be mapped to the midway point between 5 and 6. However, if you were to provide a value of a letter somewhere between C and D (which doesn’t exist), D3 would have no idea how to map it. As far as D3 is concerned, there is no way to map that value because you have stated that all those values are discrete. That is, there are no connecting values in between.
在线性比例尺的示例中,如果要提供5.5的值,它将被映射到5到6之间的中间点。但是,如果要在C和D之间提供一个字母的值(即不存在),D3将不知道如何映射它。 就D3而言,由于已声明所有这些值都是离散的,因此无法映射该值。 即,两者之间没有连接值。
Now, let’s construct the X axis.
现在,让我们构造X轴。
function getKeys(array) {
return array.map(arrObj = {
return arrObj.category;
});
}
const keys = getKeys(data)
const x = d3.scaleOrdinal()
.domain([...keys])
.range([margin.left, width - margin.right])
If you’re wondering about the function in there and the variable keys, that is to extract all the categories present in our data and provide it to the domain function as an array.
如果您想知道其中的函数和变量键,那就是提取数据中存在的所有类别,并将其作为数组提供给域函数。
I could just as easily have written .domain(['A', 'B', 'C', 'D', 'E'])
but then I would have had to manually update that every time my data changed.
我可以很容易地编写.domain(['A', 'B', 'C', 'D', 'E'])
但是每次数据更改时,我都必须手动进行更新。
The range, as I have already mentioned, needs to grow from left to right. So, we leave out the margin on the left, move the length of the width and leave out the margin on the right.
正如我已经提到的,范围需要从左向右扩大。 因此,我们在左边留出空白,在宽度上移动长度,在右边留出空白。
Now, we have the chart area and the scales defined, we need to set up the axes themselves. Here is how we do that.
现在,我们已经定义了图表区域和比例,我们需要自行设置轴。 这是我们的方法。
const xAxis = d3.axisBottom(x)
Here, we are creating a function called xAxis which uses the d3.axisBottom
function with our x scale provided as a parameter.
在这里,我们正在创建一个名为xAxis的函数 ,该函数使用d3.axisBottom
函数并将x比例尺作为参数提供。
To actually display the X-axis on our chart, we need to do the following
要在图表上实际显示X轴,我们需要执行以下操作
chart.append('g')
.attr('transform', `translate(0, ${height})`)
.call(xAxis)
Two things to examine here.
这里要检查两件事。
We’re appending a g
element to our chart. We discussed the g
element in an earlier section. We then apply a transform to our g
element. This transform is something that comes up in D3 all the time.
我们将g
元素添加到图表中。 我们在前面的部分中讨论了g
元素。 然后,将变换应用于g
元素。 这种转换一直在D3中出现。
SVG’s have what are called transform functions. There are multiple kinds of transform functions, but the one we care about here is translate
. Translate
accepts two parameters an x
and y
co-ordinate. This signifies how many units of pixels to move the g
element either in the X or the Y direction.
SVG具有所谓的转换函数。 转换函数有多种,但是我们在这里关心的是translate
。 Translate
接受两个参数x
和y
坐标。 这表示要在X或Y方向上移动g
元素多少个像素单位。
You can read more about transforms here.
您可以在此处了解有关转换的更多信息。
The two parameters we provide to the translate
function are 0 and height
. Remember that the point of origin of our SVG chart is at the top left corner. Since, we already know this is a horizontal axis that begins at the point of origin, we need to move it vertically down by height
number of units.
我们提供给translate
函数的两个参数是0和height
。 请记住,SVG图表的原点位于左上角。 由于我们已经知道这是一条从原点开始的水平轴,因此我们需要将其垂直向下移动height
单位。
If you didn’t provide the transform attribute, the X-axis would be situated at the top of your chart.
如果不提供transform属性,则X轴将位于图表的顶部。
The last part of the method chain is a call
function where the xAxis is provided as a parameter. This is probably the most confusing aspect so far because of the poor choice of terminology.
方法链的最后一部分是call
函数,其中xAxis作为参数提供。 到目前为止,由于术语选择不多,这可能是最令人困惑的方面。
We’ll examine just these two lines first.
我们将首先检查这两行。
.append('g')
.attr('transform', `translate(0, ${height})`)
What you need to understand is that when you do something like chart.append('g')
, this appends a g
element onto the chart element, selects the g
element and then returns it. You can test this by doing the following
您需要了解的是,当您执行诸如chart.append('g')
,这会将g
元素追加到chart元素上,选择g
元素然后返回它。 您可以通过以下操作对此进行测试
const test = chart.append('g')
.attr('transform', `translate(0, ${height})`)
.call(xAxis)console.log(test)
When the result of the log shows up, you’ll see a g
element under a Selection
object. This is actually what enables us to do method chaining on the append
method. Since it returns the g
element, we can transform as part of the same method chain.
当日志结果显示时,您将在Selection
对象下看到一个g
元素。 这实际上使我们能够对append
方法进行方法链接。 由于它返回g
元素,因此我们可以将其作为同一方法链的一部分进行转换。
Let’s go to the last line now
现在转到最后一行
.call(xAxis)
Here’s what D3’s documentation says about call
这是D3的文档中关于call
Invokes the specified function exactly once, passing in this selection along with any optional arguments. Returns this selection.
完全调用一次指定的函数,并将此选择与任何可选参数一起传递。 返回此选择。
So, we know we utilize call as a function and we have to pass it a function as a parameter. We know this because the documentation says, it invokes the specific function exactly once. Now, the other thing to realize is that xAxis is also a function. You can verify this again by logging xAxis.
因此,我们知道我们将调用作为函数使用,并且必须将函数作为参数传递给它。 我们之所以知道这一点是因为文档说,它只调用一次特定的函数。 现在,要实现的另一件事是xAxis也是一个函数。 您可以通过登录xAxis再次验证这一点。
But, if xAxis is also a function then that needs a parameter passed to it as well. Read the documentation for call
again and you'll notice it says "passes in this selection...". This means that the xAxis function is being implicitly called with the g
selection returned from calling chart.append('g')
但是,如果xAxis也是一个函数,则也需要将参数传递给它。 再次阅读文档以进行call
,您会注意到它说“ passs in this selection ...”。 这意味着xAxis函数正在使用从调用chart.append('g')
返回的g
选择隐式调用。
Having to explain how call
works is precisely why I don't like it. There's too much implicitly happening that just seems like black magic.
不得不解释call
工作原理正是我不喜欢它的原因。 太多的隐性事件似乎就像黑魔法。
If you’re still confused about how call
works hopefully the following graphic clears it up for you.
如果您仍然对call
工作方式仍感到困惑,则可以使用以下图形为您清除call
。
Creating the Y axis now that we know how the X axis works is far simpler. We use the same principles but swap out axisBottom
for axisLeft
and change the translate function slightly.
现在我们知道X轴的工作原理,创建Y轴要简单得多。 我们使用相同的原理,但将axisBottom
换为axisLeft
并稍微更改了translate函数。
const yAxis = d3.axisLeft(y);
chart
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.bottom})`)
.call(yAxis);
You’ll notice that the transform
attribute has a translate
function where the y
attribute is set to margin.bottom
. If you go back to the range we set for the y scale, you'll notice we set it to height - margin.bottom
.
您会注意到, transform
属性具有translate
功能,其中y
属性设置为margin.bottom
。 如果返回到我们为y比例尺设置的范围,您会注意到我们将其设置为height - margin.bottom
。
When we call D3’s axisBottom
function, D3 will place this at height - margin.bottom
, but the bottom of the chart is actually at height
, so we add the margin.bottom
offset.
当我们调用D3的axisBottom
函数时,D3会将其放置在height - margin.bottom
,但图表的底部实际上是在height
,因此我们添加margin.bottom
偏移量。
This is the most visually important part of the chart because this is where the user actually gets to see the data.
这是图表中视觉上最重要的部分,因为这是用户实际看到数据的地方。
First, let me just show you the code that will create the bars for us and then step through it.
首先,让我向您展示将为我们创建栏的代码,然后逐步完成。
chart.selectAll('rect')
.data(data)
.join('rect')
.attr('x', d => x(d.category))
.attr('y', d => y(d.value))
.attr('width', x.bandwidth())
.attr('height', height - y(d.value))
.style('fill', 'steelblue')
The first two lines are straightforward. selectAll
works the same as select
except it returns all possible selections of a specific elemenet.
前两行很简单。 selectAll
与select
工作原理相同,只是它返回特定elemenet的所有可能选择。
Calling .data
allows you to define the data you want to associate with the DOM elements.
调用.data
允许您定义要与DOM元素关联的数据。
Now, the .join
is where the crux of D3 comes in. This is what makes D3 unbelievably powerful to create visualizations with.
现在, .join
是D3的关键所在。这就是使D3强大地创建可视化效果的原因。
If you want to read what Mike Bostock (the creator of D3) has to say on data joins, you can find that here.
如果您想阅读Mike Bostock(D3的创建者)对数据连接的看法,可以在这里找到。
What follows is my attempt at explaining what the .join
function does in the context of a bar chart.
接下来是我尝试解释.join
函数在条形图上下文中的作用。
So, if you go back and look at the data we defined earlier in this post, you’ll notice that it is an array. The reason is because this is the data structure D3 expects.
因此,如果回头看一下本文前面定义的数据,您会注意到它是一个数组。 原因是因为这是D3期望的数据结构。
The .join
function then takes every element of the array and constructs a corresponding DOM element with this data point attached.
然后, .join
函数获取数组的每个元素,并构造一个带有此数据点的DOM元素 。
Note: The .join
function used to earlier be separate functions called .enter
and .append
. However this syntax is a lot cleaner. Here is the GitHub issue where Mike Bostock first suggested it.
注意: .join
函数以前是单独的函数,称为 .enter
和 .append
。 但是,此语法更加简洁。 这 是Mike Bostock首次提出的GitHub问题。
Note: In the graphic above, it should read .join('rect')
not .join('bar')
注意:在上图中,它应显示为 .join('rect')
而不是 .join('bar')
The graphic above illustrates what is going on when you do a data join. If you take an array of 5 elements and perform a .join('rect')
on it, what D3 will do is create a rect SVG element for each of those elements.
上图说明了进行数据联接时发生的情况。 如果您采用5个元素组成的数组并对其执行.join('rect')
,则D3要做的是为每个元素创建一个rect SVG元素。
Another thing D3 will do is associate each data point from your array to its respective rect
element.
D3要做的另一件事是将数组中的每个数据点关联到其各自的rect
元素。
const data = [1, 2, 3, 4, 5]
const selection = d3.selectAll('rect')
.data(data)
.join('rect)
selection.each(function(d, i) {
console.log(d)
})
//1, 2, 3, 4, 5
The above code snippet shows you how to do the logging of each individual data point to satisfy your own curiosity.
上面的代码段向您展示了如何记录每个单独的数据点以满足自己的好奇心。
You could, of course, replace the rect
above with any other SVG element and you would have the same result.
当然,您可以用任何其他SVG元素替换上面的rect
,您将得到相同的结果。
Great, now we know how to create our bars but we still need to figure out how to place them. Before continuing, I recommend you read this MDN articleabout rects.
太好了,现在我们知道如何创建钢筋了,但是我们仍然需要弄清楚如何放置它们。 在继续之前,建议您阅读这篇有关rects的MDN文章 。
One thing that tripped me up a lot about working with D3 intially was trying to figure out how the SVG coordinate system works.
最初使用D3令我大为震惊的一件事是试图弄清楚SVG坐标系是如何工作的。
If you want a deeper understanding of how SVG coordinate systems work, check out this article
如果你想的SVG如何协调系统工作有了更深的了解,看看 这个 文章
The graphic above shows you how different measurements would impact the placement of a rect in the SVG coordinate space.
上图显示了不同的测量如何影响rect在SVG坐标空间中的放置。
A rect SVG element has four main attributes we’ll be concerned with: x, y, width and height.
矩形SVG元素具有四个主要属性,我们将关注它们:x,y,宽度和高度。
You can see how each of them relate to the SVG coordinate space in the image.
您可以看到它们各自与图像中SVG坐标空间的关系。
Let’s translate the above into code.
让我们将以上内容翻译为代码。
chart
.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => return x(d.category))
.attr("y", d => return y(d.value))
.attr("width", x.bandwidth())
.attr("height", d => height - y(d.value))
.style("fill", "steelblue");
Let’s step through the bits of the code after the .join
call.
让我们逐步了解.join
调用之后的代码位。
When we set the x
and y
attributes, we make a call to the respective scales we defined earlier. Remember when we defined the scales, we said that each of them would be functions that could be called with a value to map it from the domain to the range. That's precisely what we're doing here.
设置x
和y
属性时,将调用我们之前定义的各个比例。 请记住,在定义比例尺时,我们曾说过每个比例尺都是可以调用的函数,并带有将其从域映射到范围的值。 这正是我们在这里所做的。
Now, to understand the width attribute, we first need to go back to the ordinalScale
we defined. D3 has a function associated with each scale called the bandwidth
function. This returns the width of each band defined. D3 internally does this by dividing the range equally among each element of the domain.
现在,要了解width属性,我们首先需要回到我们定义的ordinalScale
。 D3具有与每个标度关联的函数,称为bandwidth
函数。 这将返回定义的每个波段的宽度。 D3在内部通过在域的每个元素之间平均划分范围来做到这一点。
So, we provided an array of 5 characters as the domain of the x axis and we set the range to [margin.left, width - margin.right]
, where width = 800
and margin = { left: 60, right: 60 }
因此,我们提供了一个由5个字符组成的数组作为x轴的域,并将范围设置为[margin.left, width - margin.right]
,其中width = 800
和margin = { left: 60, right: 60 }
So, we have
所以,我们有
(800 - 60 - 60) / 5 = 136
All units are in pixels.
Now, the height attribute is another thing that tripped me up for a long time because I couldn’t quite figure out why we were doing height - y(d.value)
to represent the height of the rect. Surely, it should have just been y(d.value)
?
现在,height属性是使我绊倒了很长时间的另一件事,因为我无法完全弄清楚为什么要进行height - y(d.value)
来表示矩形的高度。 当然,它应该只是y(d.value)
?
This is answered again by remembering that the SVG coordinate has its point of origin in the top left corner and the +ve Y-axis goes downwards.
记住SVG坐标的原点在左上角,而+ ve Y轴向下,再次回答了这一问题。
In the graphic above, I’ve presented my understanding of how the height of the bar is calculated. Again, if the calculation for the height of the bar makes intuitive sense for you, feel free to skip this.
在上面的图形中,我已经介绍了我对条形高度的计算方式的理解。 同样,如果对条形高度的计算对您来说很直观,请随时跳过此步骤。
The main thing to notice in the visual is that there is a difference between the axes of the SVG coordinate system and the axes of our chart. The Y axis for the SVG coordinate system is positive downwards but the Y axis for our chart is positive upwards.
视觉上要注意的主要事情是,SVG坐标系的轴与图表的轴之间存在差异。 SVG坐标系的Y轴向下为正,但图表的Y轴向上为正。
This is the reason I’ve drawn two separate sets of axes for both X and Y. Technically, the two Y axes should be superimposed on top of one another but that would make it hard to visually see it. But, you can assume that they are overlayed on top of each other.
这就是为什么我为X和Y绘制了两组独立的轴的原因。从技术上讲,两个Y轴应该彼此叠置,但是这样很难从视觉上看到它。 但是,您可以假定它们彼此重叠。
When we call the y scale function with y(d.value)
, we get a value that counts down the +ve Y-axis of the SVG coordinate system starting from the top. The height is shown on the side which is the entire length of the Y axis and then what remains is height - y(d.value)
, which is the height we are assigning to the bar.
当我们使用y(d.value)
调用y比例函数时,我们得到一个值,该值从顶部开始向下计数 SVG坐标系的+ ve Y轴。 高度显示在侧面,即Y轴的整个长度,然后剩下的是height - y(d.value)
,这是我们分配给条形的高度。
Now, we get to the easy bit. It’s only easy because of everything we’ve covered so far!
现在,我们开始简单一点。 这很简单,因为到目前为止我们已经介绍了所有内容!
Similar to how we’ve appended rects
to our SVG so far, we can also append text
as an SVG element like below:
到目前为止,类似于将rects
附加到SVG的方式,我们还可以将text
附加为SVG元素,如下所示:
chart.append('text')
.attr('x', width / 2)
.attr('y', margin.top)
.style('font-size', 32px)
.style('text-anchor', 'middle')
.text('Distribution Among Categories')
The text SVG element also has an x
and y
attribute that work very similarly to how the x
and y
attributes of the rect
work.
文本SVG元素还具有x
和y
属性,其工作方式与rect
的x
和y
属性的工作方式非常相似。
You can set different style attributes to the text element and you set the text itself using the .text
attribute.
您可以为text元素设置不同的样式属性,并使用.text
属性设置文本本身。
Now, let’s place the Y-axis label
现在,让我们放置Y轴标签
chart
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", margin.left / 4)
.text("Values")
Okay, this one is a little confusing, so let’s step through it.
好的,这有点令人困惑,所以让我们逐步解决它。
First, we apply a transform
to the element and set that value to rotate(-90)
. What this does is rotate the SVG coordinate system itself by -90 degrees.
首先,我们对元素应用transform
并将该值设置为rotate(-90)
。 这是将SVG坐标系本身旋转-90度。
Note: Everything that follows is my attempt to reverse engineer how the rotate function works. If I turn out to be wrong, please excuse me.
注意:以下所有内容都是我对反向旋转功能的工作方式进行的反向尝试。 如果我发现错了,请原谅。
The graphic above shows what happens to the coordinate system on applying rotate(-90)
. Now, you're probably even more confused because a negative rotation typically means a clockwise rotation. Yet, it looks like I've rotated anti-clockwise here.
上图显示了应用rotate(-90)
坐标系发生了什么。 现在,您可能会更加困惑,因为负旋转通常意味着顺时针旋转。 但是,好像我在这里逆时针旋转。
Well, remember that a typical coordinate system has the Y-axis pointing positively upwards. We have it pointing positively downwards. Therefore, our rotations are reversed.
好吧,请记住,典型的坐标系的Y轴指向正上方。 我们使它指向正下方。 因此,我们的旋转方向相反。
Now, our new X axis points in the opposite direction of the old Y axis and our new Y axis points in the direction of the old X axis.
现在,我们的新X轴指向旧Y轴的相反方向,我们的新Y轴指向旧X轴的方向。
Now, in the context of this new information, looking at the values of the x
and y
attributes makes more sense. Since our new X points opposite to the direction of the old Y, we set a negative value to the x
attribute.
现在,在此新信息的上下文中,查看x
和y
属性的值更有意义。 由于我们的新X点与旧Y方向相反,因此我们将x
属性设置为负值。
Okay, that was quite the post. I wasn’t envisioning it becoming quite so massive but we did cover a lot in detail. I hope you enjoyed going through this post and more than anything, I hope you have a better grasp of how D3 works. This is a truly wonderful library that provides you with a set of very powerful tools.
好的,那就是职位。 我并没有想到它会变得如此庞大,但是我们确实涵盖了很多细节。 我希望您喜欢这篇文章,并且比什么都重要,希望您对D3的工作原理有更好的了解。 这是一个真正出色的库,为您提供了一组非常强大的工具。
I’ve created a Code Sanbox here with a working version of the code from this post. Feel free to fork it and play around with it!
我在这里创建了一个代码Sanbox,并使用了该帖子中的代码。 随意分叉并玩转它!
If you want to follow me, you can do so on GitHub or Twitter. If you have any questions, please don’t hesitate to ask.
如果您想关注我,可以在 GitHub 或 Twitter上关注 。 如有任何疑问,请随时提出。
翻译自: https://www.freecodecamp.org/news/a-visual-reference-for-d3/
视觉开发 学习 参考课本