d3.js 数据更新
It is common to remove the existing Scalable Vector Graphics (SVG) element by calling d3.select('#chart').remove()
, before rendering a new chart.
通常在渲染新图表之前,通过调用d3.select('#chart').remove()
来删除现有的可缩放矢量图形(SVG)元素。
However, there may be scenarios when you have to produce dynamic visualizations from sources such as external APIs. This article will show you how to do this using D3.js.
但是,在某些情况下,您必须从外部API等来源生成动态可视化效果。 本文将向您展示如何使用D3.js。
D3.js handles dynamic data by adopting the general update pattern. This is commonly described as a data-join, followed by operations on the enter, update and exit selections. Mastering these selection methods will enable you to produce seamless transitions between states, allowing you to tell meaningful stories with data.
D3.js通过采用常规更新模式来处理动态数据。 通常将其描述为数据联接,然后是输入,更新和退出选择的操作。 掌握这些选择方法将使您能够在状态之间进行无缝转换,从而使您可以用数据讲述有意义的故事。
We will be building a graph that illustrates the movement of a few Exchange-Traded Funds (ETFs) over the second half of 2018. The graph consists of the following tools:
我们将建立一个图表,说明一些交易所买卖基金(ETF)在2018年下半年的走势。该图表包含以下工具:
Trade volume bar chart
交易量条形图
50-day simple moving average
50天简单移动平均线
Bollinger Bands (20-day simple moving average, with standard deviation set at 2.0)
布林带 (20天简单移动平均线,标准偏差设置为2.0)
Open-high-low-close (OHLC) chart
开-高-低-关闭( OHLC )图表
These tools are commonly utilized in the technical analysis of stocks, commodities, and other securities. For example, traders may make use of the Bollinger Bands and Candlesticks to derive patterns which represent buy or sell signals.
这些工具通常用于股票,商品和其他证券的技术分析。 例如,交易者可以利用布林线和烛台来得出表示买入或卖出信号的形态。
This is how the graph will look like:
该图将如下所示:
This article aims to equip you with the fundamental theories of data joins and the enter-update-exit pattern in order to allow you to easily visualize dynamic datasets. In addition, we will be covering selection.join, which is introduced in D3.js’s v5.8.0 release.
本文旨在为您提供数据连接的基本理论和enter-update-exit模式,以便使您轻松可视化动态数据集。 此外,我们将覆盖selection.join ,这是在D3.js的V5.8.0版本中引入的。
The gist of the general update pattern is the selection of Document Object Model (DOM) elements, followed by binding of data to these elements. These elements are then created, updated or removed, to represent the necessary data.
常规更新模式的要点是选择文档对象模型(DOM)元素,然后将数据绑定到这些元素。 然后创建,更新或删除这些元素,以表示必要的数据。
Data join is the mapping of n
number of elements in the dataset with n
number of selected Document Object Model (DOM) nodes, specifying the required action to the DOM as the data changes.
数据联接是数据集中n
个元素与n
个选定文档对象模型(DOM)节点的映射,从而在数据更改时为DOM指定所需的操作。
We use the data()
method to map each data point to a corresponding element in the DOM selection. In addition, it is good practice to maintain object constancy by specifying a key as the unique identifier in each data point. Let’s take a look at the following example, which is the first step towards rendering the trade volume bars:
我们使用data()
方法将每个数据点映射到DOM选择中的相应元素。 此外,优良作法是通过在每个数据点中将键指定为唯一标识符来保持对象的一致性 。 让我们看下面的示例,这是渲染交易量条的第一步:
const bars = d3
.select('#volume-series')
.selectAll(.'vol')
.data(this.currentData, d => d['date']);
The above line of code selects all elements with the class vol
, followed by mapping the this.currentData
array with the selection of DOM elements using the data()
method.
上面的代码行选择了class类为vol
所有元素,然后使用data()
方法将this.currentData
数组与DOM元素的选择进行映射。
The second optional argument of data()
takes a data point as input and returns the date
property as the selected key for each data point.
data()
的第二个可选参数以一个数据点作为输入,并返回date
属性作为每个数据点的选定键。
.enter()
returns an enter selection which represents the elements that need to be added when the joined array is longer than the selection. This is followed by calling .append()
, which creates or updates elements on the DOM. We can implement this in the following manner:
.enter()
返回一个enter选择,表示连接的数组比选择长时需要添加的元素。 接下来是调用.append()
,它会在DOM上创建或更新元素。 我们可以通过以下方式实现此目的:
bars
.enter()
.append('rect')
.attr('class', 'vol')
.merge(bars)
.transition()
.duration(750)
.attr('x', d => this.xScale(d['date']))
.attr('y', d => yVolumeScale(d['volume']))
.attr('fill', (d, i) => {
if (i === 0) {
return '#03a678';
} else {
// green bar if price is rising during that period, and red when price is falling
return this.currentData[i - 1].close > d.close
? '#c0392b'
: '#03a678';
}
})
.attr('width', 1)
.attr('height', d => this.height - yVolumeScale(d['volume']));
.merge()
merges the update and enter selections, before applying the subsequent method chains to create animations between transitions, and to update their associated attributes. The above block of code enables you to perform the following actions on the selected DOM elements:
.merge()
合并更新并输入选择,然后应用后续方法链在过渡之间创建动画并更新其相关属性。 上面的代码块使您可以对选定的DOM元素执行以下操作:
The update selection, which consists of data points represented by the <rect>
elements on the graph, will have their attributes updated accordingly.
更新选择(由图形上<rect>
元素表示的数据点组成)将相应地更新其属性。
The creation of <rect>
elements with the class vol
, with the above attributes defined within each element as the enter selection consists of data points that are not represented on the graph.
创建具有类vol
的<rect>
元素,并在每个元素内定义上述属性作为enter选择,其中包含图上未表示的数据点。
Remove items from our dataset by following the simple steps below:bars.exit().remove();
请按照以下简单步骤从我们的数据集中删除项目:bars.exit()。remove();
.exit()
returns an exit selection, which specifies the data points that need to be removed. The .remove()
method subsequently deletes the selection from the DOM.
.exit()
返回退出选择,该选择指定需要删除的数据点。 .remove()
方法随后从DOM中删除选择。
This is how the volume series bars will respond to changes in data:
这是音量序列条将如何响应数据更改的方式:
Take note of how the DOM and the respective attributes of each <rect>
element are updated as we select a different dataset:
请注意在选择不同的数据集时,如何更新DOM和每个<rect>
元素的相应属性:
The introduction of selection.join
in v5.8.0 of D3.js has simplified the entire data join process. Separate functions are now passed to handle enter, update, and exit which in turn returns the merged enter and update selections.
D3.js v5.8.0中的selection.join
引入简化了整个数据联接过程。 独立的功能现在通过处理输入,更新,并退出这反过来又返回合并后的输入和更新的选择。
selection.join(
enter => // enter.. ,
update => // update.. ,
exit => // exit..
)
// allows chained operations on the returned selections
In the case of the volume series bars, the application of selection.join
will result in the following changes on our code:
对于音量系列条形图, selection.join
的应用将导致我们的代码发生以下更改:
//select, followed by updating data join
const bars = d3
.select('#volume-series')
.selectAll('.vol')
.data(this.currentData, d => d['date']);
bars.join(
enter =>
enter
.append('rect')
.attr('class', 'vol')
.attr('x', d => this.xScale(d['date']))
.attr('y', d => yVolumeScale(d['volume']))
.attr('fill', (d, i) => {
if (i === 0) {
return '#03a678';
} else {
return this.currentData[i - 1].close > d.close
? '#c0392b'
: '#03a678';
}
})
.attr('width', 1)
.attr('height', d => this.height - yVolumeScale(d['volume'])),
update =>
update
.transition()
.duration(750)
.attr('x', d => this.xScale(d['date']))
.attr('y', d => yVolumeScale(d['volume']))
.attr('fill', (d, i) => {
if (i === 0) {
return '#03a678';
} else {
return this.currentData[i - 1].close > d.close
? '#c0392b'
: '#03a678';
}
})
.attr('width', 1)
.attr('height', d => this.height - yVolumeScale(d['volume']))
);
Also, note that we have made some changes to the animation of the bars. Instead of passing the transition()
method to the merged enter and update selections, it is now used in the update selection such that transitions will only be applied when the dataset has changed.
另外,请注意,我们对条形的动画进行了一些更改。 现在,它不再将transition()
方法传递给合并的enter和update选择,而是在update选择中使用它,以便仅在数据集发生更改时才应用transition。
The returned enter and update selections are then merged and returned by selection.join
.
然后将返回的enter和update选择合并并通过selection.join
返回。
Similarly, we can apply selection.join
on the rendering of Bollinger Bands. Before rendering the Bands, we are required to calculate the following properties of each data point:
同样,我们可以在布林带的渲染中应用selection.join
。 在渲染波段之前,我们需要计算每个数据点的以下属性:
This is the formula for calculating standard deviation:
这是计算标准偏差的公式:
Now, we shall translate the above formula into JavaScript code:
现在,我们将上面的公式转换为JavaScript代码:
calculateBollingerBands(data, numberOfPricePoints) {
let sumSquaredDifference = 0;
return data.map((row, index, total) => {
const start = Math.max(0, index - numberOfPricePoints);
const end = index;
// divide the sum with subset.length to obtain moving average
const subset = total.slice(start, end + 1);
const sum = subset.reduce((a, b) => {
return a + b['close'];
}, 0);
const sumSquaredDifference = subset.reduce((a, b) => {
const average = sum / subset.length;
const dfferenceFromMean = b['close'] - average;
const squaredDifferenceFromMean = Math.pow(dfferenceFromMean, 2);
return a + squaredDifferenceFromMean;
}, 0);
const variance = sumSquaredDifference / subset.length;
return {
date: row['date'],
average: sum / subset.length,
standardDeviation: Math.sqrt(variance),
upperBand: sum / subset.length + Math.sqrt(variance) * 2,
lowerBand: sum / subset.length - Math.sqrt(variance) * 2
};
});
}
.
.
// calculates simple moving average, and standard deviation over 20 days
this.bollingerBandsData = this.calculateBollingerBands(validData, 19);
A quick explanation of the calculation of the standard deviation, and Bollinger Band values on the above block of code is as follows:
在上述代码块中,对标准偏差和布林带值的计算的简要说明如下:
For each iteration,
对于每次迭代,
With the data points defined, we can then make use of selection.join
to render Bollinger Bands:
定义好数据点后,我们便可以使用selection.join
渲染布林带:
// code not shown: rendering of upper and lower bands
.
.
// bollinger bands area chart
const area = d3
.area()
.x(d => this.xScale(d['date']))
.y0(d => this.yScale(d['upperBand']))
.y1(d => this.yScale(d['lowerBand']));
const areaSelect = d3
.select('#chart')
.select('svg')
.select('g')
.selectAll('.band-area')
.data([this.bollingerBandsData]);
areaSelect.join(
enter =>
enter
.append('path')
.style('fill', 'darkgrey')
.style('opacity', 0.2)
.style('pointer-events', 'none')
.attr('class', 'band-area')
.attr('clip-path', 'url(#clip)')
.attr('d', area),
update =>
update
.transition()
.duration(750)
.attr('d', area)
);
This renders the area chart which denotes the area filled by the Bollinger Bands. On the update function, we can use the selection.transition()
method to provide animated transitions on the update selection.
这将显示面积图,该面积图表示由布林带所填充的区域。 在更新功能上,我们可以使用selection.transition()
方法在更新选择上提供动画过渡。
The candlesticks chart displays the high, low, open and close prices of a stock for a specific period. Each candlestick represents a data point. Green represents when the stock closes higher while red represents when the stock closes at a lower value.
烛台图显示特定时期内股票的高,低,开盘和收盘价。 每个烛台代表一个数据点。 绿色表示股票收盘价较高时,红色表示股票收盘价较低时。
Unlike the Bollinger Bands, there is no need for additional calculations, as the prices are available in the existing dataset.
与布林带不同,由于价格可在现有数据集中获得,因此无需进行其他计算。
const bodyWidth = 5;
const candlesticksLine = d3
.line()
.x(d => d['x'])
.y(d => d['y']);
const candlesticksSelection = d3
.select('#chart')
.select('g')
.selectAll('.candlesticks')
.data(this.currentData, d => d['volume']);
candlesticksSelection.join(enter => {
const candlesticksEnter = enter
.append('g')
.attr('class', 'candlesticks')
.append('g')
.attr('class', 'bars')
.classed('up-day', d => d['close'] > d['open'])
.classed('down-day', d => d['close'] <= d['open']);
On the enter function, each candlestick is rendered based on its individual properties.
在输入功能上,每个烛台根据其各自的属性进行渲染。
First and foremost, each candlestick group element is assigned a class of up-day
if the close price is higher than the open price, and down-day
if the close price is lower than or equal to the open-price.
首先,如果收盘价高于开盘价,则为每个烛台组元素分配一个up-day
类别,如果收盘价低于或等于开盘价,则为down-day
分配一个类别。
candlesticksEnter
.append('path')
.classed('high-low', true)
.attr('d', d => {
return candlesticksLine([
{ x: this.xScale(d['date']), y: this.yScale(d['high']) },
{ x: this.xScale(d['date']), y: this.yScale(d['low']) }
]);
});
Next, we append the path
element, which represents the highest and lowest price of that day, to the above selection.
接下来,我们将path
元素(代表当天的最高和最低价格)附加到上述选择中。
candlesticksEnter
.append('rect')
.attr('x', d => this.xScale(d.date) - bodyWidth / 2)
.attr('y', d => {
return d['close'] > d['open']
? this.yScale(d.close)
: this.yScale(d.open);
})
.attr('width', bodyWidth)
.attr('height', d => {
return d['close'] > d['open']
? this.yScale(d.open) - this.yScale(d.close)
: this.yScale(d.close) - this.yScale(d.open);
});
});
This is followed by appending the rect
element to the selection. The height of each rect
element is directly proportionate to its day range, derived by subtracting the open price with the close price.
接下来,将rect
元素附加到所选内容。 每个rect
元素的高度均与其日范围成正比,该日范围是通过用开盘价减去开盘价得出的。
On our stylesheets, we will define the following CSS properties to our classes making the candlesticks red or green:
在样式表上,我们将为类定义以下CSS属性,使烛台变为红色或绿色:
.bars.up-day path {
stroke: #03a678;
}
.bars.down-day path {
stroke: #c0392b;
}
.bars.up-day rect {
fill: #03a678;
}
.bars.down-day rect {
fill: #c0392b;
}
This results in the rendering of the Bollinger Bands and candlesticks:
这导致布林带和烛台的渲染:
The new syntax has proven to be simpler and more intuitive than explicitly calling selection.enter
, selection.append
, selection.merge
, and selection.remove
.
与显式调用selection.enter
, selection.append
, selection.merge
和selection.remove
相比,新语法已被证明更简单,更直观。
Note that for those who are developing with D3.js’s v5.8.0 and beyond, it has been recommended by Mike Bostock that these users start using selection.join
due to the above advantages.
请注意,对于那些使用D3.js v5.8.0及更高版本进行开发的用户,Mike Bostock 建议这些用户由于上述优点而开始使用selection.join
。
The potential of D3.js is limitless and the above illustrations are merely the tip of the iceberg. Many satisfied users have created visualizations which are vastly more complex and sophisticated than the one show above. This list of free APIs may interest you if you are keen to embark on your own data visualization projects.
D3.js的潜力是无限的,上面的插图只是冰山一角。 许多满意的用户创建了可视化文件,这些可视化文件比上面显示的可视化文件复杂得多。 如果您热衷于自己的数据可视化项目,则此免费API列表可能会让您感兴趣。
Feel free to check out the source code and the full demonstration of this project.
Thank you very much for reading this article. If you have any questions or suggestions, feel free to leave them on the comments below!
非常感谢您阅读本文。 如果您有任何疑问或建议,请随时将它们留在下面的评论中!
New to D3.js? You may refer to this article on the basics of implementing common chart components.
D3.js的新手? 您可以参考 本文 以了解实现常见图表组件的基础。
Special thanks to Debbie Leong for reviewing this article.
特别感谢Debbie Leong审阅了本文。
Additional references:
其他参考:
翻译自: https://www.freecodecamp.org/news/how-to-work-with-d3-jss-general-update-pattern-8adce8d55418/
d3.js 数据更新