d3.js图表_如何使用D3.js建立历史价格图表

d3.js图表

逐步可视化财务数据集的方法 (A step by step approach towards visualizing financial datasets)

It is a challenge to communicate data and display these visualizations on multiple devices and platforms.

交流数据并在多个设备和平台上显示这些可视化内容是一个挑战。

“Data is just like crude. It’s valuable, but if unrefined it cannot really be used.” - Michael Palmer

数据就像原油一样。 这很有价值,但是如果不完善,就不能真正使用。” - 迈克尔·帕尔默 ( Michael Palmer)

D3 (Data-Driven Documents) solves this age-old dilemma. It provides developers and analysts the ability to build customized visualizations for the Web with complete freedom. D3.js allows us to bind data to the DOM (Document Object Model). Then apply data-driven transformations to create refined visualizations of data.

D3(数据驱动的文档)解决了这一古老的难题。 它为开发人员和分析人员提供了完全自由地为Web构建自定义可视化效果的能力。 D3.js允许我们将数据绑定到DOM(文档对象模型)。 然后应用数据驱动的转换来创建精确的数据可视化。

In this tutorial, we will understand how we can make the D3.js library work for us.

在本教程中,我们将了解如何使D3.js库为我们工作。

入门 (Getting started)

We will be building a chart that illustrates the movement of a financial instrument over a period of time. This visualization resembles the price charts provided by Yahoo Finance. We will break down the various components required to render an interactive price chart that tracks a particular stock.

我们将建立一个图表,说明一段时间内金融工具的走势。 这种可视化效果类似于Yahoo Finance提供的价格图表。 我们将分解绘制交互式价格图表以跟踪特定股票所需的各种组件。

Required components:

所需组件:

  1. Loading and parsing of data

    加载和解析数据
  2. SVG element

    SVG元素
  3. X and Y axes

    X和Y轴
  4. Close price line chart

    收盘价折线图
  5. Simple moving average curve chart with some calculations

    带有一些计算的简单移动平均曲线图
  6. Volume series bar chart

    体积系列条形图
  7. Mouseover crosshair and legend

    鼠标悬停十字线和图例
加载和解析数据 (Loading and Parsing of Data)
const loadData = d3.json('sample-data.json').then(data => {
  const chartResultsData = data['chart']['result'][0];
  const quoteData = chartResultsData['indicators']['quote'][0];
  return chartResultsData['timestamp'].map((time, index) => ({
    date: new Date(time * 1000),
    high: quoteData['high'][index],
    low: quoteData['low'][index],
    open: quoteData['open'][index],
    close: quoteData['close'][index],
    volume: quoteData['volume'][index]
  }));
});

First, we will use the fetch module to load our sample data. D3-fetch also supports other formats such as TSV and CSV files. The data will then be further processed to return an array of objects. Each object contains the trade timestamp, high price, low price, open price, close price, and trade volume.

首先,我们将使用访存模块加载示例数据。 D3-fetch还支持其他格式,例如TSV和CSV文件。 然后将进一步处理数据以返回对象数组。 每个对象都包含交易时间戳,高价,低价,开盘价,收盘价和交易量。

body {
  background: #00151c;
}
#chart {
  background: #0e3040;
  color: #67809f;
}

Add the above base CSS properties to personalize the style of your chart for maximum visual appeal.

添加以上基本CSS属性以个性化图表样式,以最大程度地吸引视觉。

附加SVG元素 (Appending the SVG Element)
const initialiseChart = data => {
  const margin = { top: 50, right: 50, bottom: 50, left: 50 };
  const width = window.innerWidth - margin.left - margin.right;
  const height = window.innerHeight - margin.top - margin.bottom; 
  // add SVG to the page
  const svg = d3
    .select('#chart')
    .append('svg')
    .attr('width', width + margin['left'] + margin['right'])
    .attr('height', height + margin['top'] + margin['bottom'])
    .call(responsivefy)
    .append('g')
    .attr('transform', `translate(${margin['left']},  ${margin['top']})`);

Subsequently, we can use the append() method to append the SVG element to the <div> element with the id, chart. Next, we use the attr() method to assign the width and height of the SVG element. We then call the responsivefy() method (originally written by Brendan Sudol). This allows the SVG element to have responsive capabilities by listening to window resize events.

随后,我们可以使用append()方法将SVG元素附加到具有e id,图表的<d iv>元素。 接下来,我们e the attr()方法来分配SVG元素的宽度和高度。 然后,我们调用l the responsi vefy()方法(最初由Brendan Sudol写)。 这允许SVG元素通过侦听窗口调整大小事件来具有响应能力。

Remember to append the SVG group element to the above SVG element before translating it using the values from the margin constant.

在使用margin常量的值转换SVG组元素之前,请记住将其附加到上述SVG元素上。

渲染X和Y轴 (Rendering the X and Y Axes)

Before rendering the axes component, we will need to define our domain and range, which will then be used to create our scales for the axes

在渲染轴组件之前,我们需要定义我们的域和范围,然后将其用于创建轴的比例

// find data range
const xMin = d3.min(data, d => {
  return d['date'];
});
const xMax = d3.max(data, d => {
  return d['date'];
});
const yMin = d3.min(data, d => {
  return d['close'];
});
const yMax = d3.max(data, d => {
  return d['close'];
});
// scales for the charts
const xScale = d3
  .scaleTime()
  .domain([xMin, xMax])
  .range([0, width]);
const yScale = d3
  .scaleLinear()
  .domain([yMin - 5, yMax])
  .range([height, 0]);

The x and y axes for the close price line chart consist of the trade date and close price respectively. Therefore, we have to define the minimum and maximum x and y values, using d3.max() and d3.min(). We can then make use of D3-scale’s scaleTime() and scaleLinear() to create the time scale on the x-axis and the linear scale on the y-axis respectively. The range of the scales is defined by the width and height of our SVG element.

收盘价折线图的x轴和y轴分别由交易日期和收盘价组成。 因此,我们必须使用d3.max()d3.min()定义最小和最大x和y值。 然后,我们可以利用D3-scalescaleTime()scaleLinear()在x轴上创建时间标度,并在y轴上创建线性标度。 比例尺的范围由SVG元素的宽度和高度定义。

// create the axes component
svg
  .append('g')
  .attr('id', 'xAxis')
  .attr('transform', `translate(0, ${height})`)
  .call(d3.axisBottom(xScale));
svg
  .append('g')
  .attr('id', 'yAxis')
  .attr('transform', `translate(${width}, 0)`)
  .call(d3.axisRight(yScale));

After this step, we need to append the first g element to the SVG element, which calls the d3.axisBottom() method, taking in xScale as the parameter to generate the x-axis. The x-axis is then translated to the bottom of the chart area. Similarly, the y-axis is generated by appending the g element, calling d3.axisRight() with yScale as the parameter, before translating the y-axis to the right of the chart area.

完成此步骤后,我们需要将第一个g元素附加到SVG元素,该元素将调用d3.axisBottom()方法,并使用xScale作为参数来生成x轴。 然后,将x轴平移到图表区域的底部。 类似地,在将y轴平移到图表区域右侧之前,通过附加g元素并使用yScale作为参数调用d3.axisRight()来yScale y轴。

渲染收盘价折线图 (Rendering the Close Price Line Chart)
// generates close price line chart when called
const line = d3
  .line()
  .x(d => {
    return xScale(d['date']);
  })
  .y(d => {
    return yScale(d['close']);
  });
// Append the path and bind data
svg
 .append('path')
 .data([data])
 .style('fill', 'none')
 .attr('id', 'priceChart')
 .attr('stroke', 'steelblue')
 .attr('stroke-width', '1.5')
 .attr('d', line);

Now, we can append the path element inside our main SVG element, followed by passing our parsed dataset,data. We set the attribute d with our helper function, line. which calls the d3.line() method. The x and y attributes of the line accept the anonymous functions and return the date and close price respectively.

现在,我们可以将path元素追加到主要的SVG元素内,然后传递解析后的数据集data 。 我们使用辅助函数line设置属性d 。 调用d3.line()方法。 该行的xy属性接受匿名函数并分别返回日期和收盘价。

By now, this is how your chart should look like:

现在,这是您的图表应如下所示:

渲染简单移动平均曲线 (Rendering the Simple Moving Average Curve)

Instead of relying purely on the close price as our only form of technical indicator, we use the Simple Moving Average. This average identifies uptrends and downtrends for the particular security.

我们不是单纯依靠收盘价作为我们技术指标的唯一形式,而是使用简单移动平均线 。 该平均值确定特定证券的上升趋势和下降趋势。

const movingAverage = (data, numberOfPricePoints) => {
  return data.map((row, index, total) => {
    const start = Math.max(0, index - numberOfPricePoints);
    const end = index;
    const subset = total.slice(start, end + 1);
    const sum = subset.reduce((a, b) => {
      return a + b['close'];
    }, 0);
    return {
      date: row['date'],
      average: sum / subset.length
    };
  });
};

We define our helper function, movingAverage to calculate the simple moving average. This function accepts two parameters, namely the dataset, and the number of price points, or periods. It then returns an array of objects, with each object containing the date and average for each data point.

我们定义了辅助函数movingAverage以计算简单的移动平均值。 该函数接受两个参数,即数据集和价格点或期间数。 然后,它返回一个对象数组,每个对象包含每个数据点的日期和平均值。

// calculates simple moving average over 50 days
const movingAverageData = movingAverage(data, 49);
// generates moving average curve when called
const movingAverageLine = d3
 .line()
 .x(d => {
  return xScale(d['date']);
 })
 .y(d => {
  return yScale(d['average']);
 })
  .curve(d3.curveBasis);
svg
  .append('path')
  .data([movingAverageData])
  .style('fill', 'none')
  .attr('id', 'movingAverageLine')
  .attr('stroke', '#FF8900')
  .attr('d', movingAverageLine);

For our current context, movingAverage() calculates the simple moving average over a period of 50 days. Similar to the close price line chart, we append the path element within our main SVG element, followed by passing our moving average dataset, and setting the attribute d with our helper function, movingAverageLine. The only difference from the above is that we passed d3.curveBasis to d3.line().curve() in order to achieve a curve.

对于我们当前的上下文, movingAverage()计算50天期间内的简单移动平均值。 与收盘价折线图类似,我们将path元素追加到主要的SVG元素中,然后传递移动平均数据集,并使用我们的辅助函数movingAverageLine设置属性d 。 与上述内容的唯一区别是,我们将d3.curveBasis传递给d3.line().curve()以获得曲线。

This results in the simple moving average curve overlaid on top of our current chart:

这导致简单的移动平均线曲线叠加在当前图表上方:

渲染体积系列条形图 (Rendering the Volume Series Bar Chart)

For this component, we will be rendering the trade volume in the form of a color-coded bar chart occupying the same SVG element. The bars are green when the stock closes higher than the previous day’s close price. They are red when the stock closes lower than the previous day’s close price. This illustrates the volume traded for each trade date. This can then be used alongside the above chart to analyze price movements.

对于此组件,我们将以占用相同SVG元素的彩色条形图的形式呈现交易 。 当股票收盘价高于前一天的收盘价时,这些柱形为绿色。 当股票收盘价低于前一日的收盘价时,它们为红色。 这说明了每个交易日的交易量。 然后可以将其与上面的图表一起使用,以分析价格走势。

/* Volume series bars */
const volData = data.filter(d => d['volume'] !== null && d['volume']   !== 0);
const yMinVolume = d3.min(volData, d => {
  return Math.min(d['volume']);
});
const yMaxVolume = d3.max(volData, d => {
  return Math.max(d['volume']);
});
const yVolumeScale = d3
  .scaleLinear()
  .domain([yMinVolume, yMaxVolume])
  .range([height, 0]);

The x and y axes for the volume series bar chart consist of the trade date and volume respectively. Thus, we will need to redefine the minimum and maximum y values and make use of scaleLinear()on the y-axis. The range of these scales are defined by the width and height of our SVG element. We will be reusing xScale since the x-axis of the bar chart corresponds similarly to the trade date.

数量系列条形图的x和y轴分别由交易日期和数量组成。 因此,我们将需要重新定义最小和最大y值,并在y轴上使用scaleLinear() 。 这些比例的范围由SVG元素的宽度和高度定义。 由于条形图的x轴与交易日期相似,因此我们将重新使用xScale

svg
  .selectAll()
  .data(volData)
  .enter()
  .append('rect')
  .attr('x', d => {
    return xScale(d['date']);
  })
  .attr('y', d => {
    return yVolumeScale(d['volume']);
  })
  .attr('fill', (d, i) => {
    if (i === 0) {
      return '#03a678';
    } else {  
      return volData[i - 1].close > d.close ? '#c0392b' : '#03a678'; 
    }
  })
  .attr('width', 1)
  .attr('height', d => {
    return height - yVolumeScale(d['volume']);
  });

This section relies on your understanding of how theselectAll() method works with the enter() and append() methods. You may wish to read this (written by Mike Bostock himself) if you are unfamiliar with those methods. This may be important as those methods are used as part of the enter-update-exit pattern, which I may cover in a subsequent tutorial.

本节取决于您对selectAll()方法如何与enter()append()方法一起使用的理解。 您不妨读一读 (书面迈克·博斯托克自己),如果你不熟悉这些方法。 这可能很重要,因为这些方法被用作enter-update-exit模式的一部分,我将在后续教程中介绍。

To render the bars, we will first use .selectAll() to return an empty selection, or an empty array. Next, we pass volData to define the height of each bar. The enter() method compares the volData dataset with the selection from selectAll(), which is currently empty. Currently, the DOM does not contain any <rect> element. Thus, the append() method accepts an argument ‘rect’, which creates a new <rect> element in the DOM for every single object in volData.

要渲染这些条,我们将首先使用.selectAll()返回一个空选择或一个空数组。 接下来,我们传递volData来定义每个条形的高度。 enter()方法将volData数据集与selectAll()的选择selectAll()当前为空enter()进行比较。 当前,DOM不包含任何<re ct>元素。 因此, the ap PEND()方法接受一个ARG ument “矩形”,这产生a new DOM中的每SINGL <矩形>元素e objec在volData吨。

Here is a breakdown of the attributes of the bars. We will be using the following attributes: x, y, fill, width, and height.

这是条形的属性细分。 我们将使用以下属性: xyfillwidthheight

.attr('x', d => {
  return xScale(d['date']);
})
.attr('y', d => {
  return yVolumeScale(d['volume']);
})

The first attr() method defines the x-coordinate. It accepts an anonymous function which returns the date. Similarly, the second attr() method defines the y-coordinate. It accepts an anonymous function which returns the volume. These will define the position of each bar.

第一个attr()方法定义x坐标。 它接受一个返回日期的匿名函数。 同样,第二个attr()方法定义y坐标。 它接受一个返回卷的匿名函数。 这些将定义每个条的位置。

.attr('width', 1)
.attr('height', d => {
  return height - yVolumeScale(d['volume']);
});

We assign a width of 1 pixel to each bar. To make the bar stretch from the top (defined by y)to the x-axis, simply deduct the height with the y value.

我们为每个条分配1像素的宽度。 要使条形图从顶部(由y定义)延伸到x轴,只需用y值减去高度即可。

.attr('fill', (d, i) => {
  if (i === 0) {
    return '#03a678';
  } else {  
    return volData[i - 1].close > d.close ? '#c0392b' : '#03a678'; 
  }
})

Remember the way that the bars will be color coded? We will be using the fill attribute to define the colors of each bar. For stocks that closed higher than the previous day’s close price, the bar will be green in color. Otherwise, the bar will be red.

还记得条带颜色编码的方式吗? 我们将使用fill属性定义每个条形的颜色。 对于收盘价高于前一日收盘价的股票,该柱形为绿色。 否则,该条将为红色。

This is how your current chart should look like:

这是您当前图表的外观:

渲染十字准线和图例以进行交互 (Rendering Crosshair and Legend for interactivity)

We have reached the final step of this tutorial, whereby we will generate a mouseover crosshair that displays drop lines. Mousing over the various points in the chart will cause the legends to be updated. This provides us the full information (open price, close price, high price, low price, and volume) for each trade date.

我们已经到达了本教程的最后一步,我们将生成一个鼠标悬停十字准线来显示放置线。 将鼠标悬停在图表中的各个点上将使图例得到更新。 这为我们提供了每个交易日期的完整信息(开盘价,收盘价,高价,低价和交易量)。

The following section is referenced from Micah Stubb’s excellent example.

以下部分是从Micah Stubb的出色示例中引用

// renders x and y crosshair
const focus = svg
  .append('g')
  .attr('class', 'focus')
  .style('display', 'none');
focus.append('circle').attr('r', 4.5);
focus.append('line').classed('x', true);
focus.append('line').classed('y', true);
svg
  .append('rect')
  .attr('class', 'overlay')
  .attr('width', width)
  .attr('height', height)
  .on('mouseover', () => focus.style('display', null))
  .on('mouseout', () => focus.style('display', 'none'))
  .on('mousemove', generateCrosshair);
d3.select('.overlay').style('fill', 'none');
d3.select('.overlay').style('pointer-events', 'all');
d3.selectAll('.focus line').style('fill', 'none');
d3.selectAll('.focus line').style('stroke', '#67809f');
d3.selectAll('.focus line').style('stroke-width', '1.5px');
d3.selectAll('.focus line').style('stroke-dasharray', '3 3');

The crosshair consists of a translucent circle with drop lines consisting of dashes. The above code block provides the styling of the individual elements. Upon mouseover, it will generate the crosshair based on the function below.

十字准线由半透明圆组成,滴线由虚线组成。 上面的代码块提供了各个元素的样式。 鼠标悬停时,它将基于以下功能生成十字准线。

const bisectDate = d3.bisector(d => d.date).left;
function generateCrosshair() {
  //returns corresponding value from the domain
  const correspondingDate = xScale.invert(d3.mouse(this)[0]);
  //gets insertion point
  const i = bisectDate(data, correspondingDate, 1);
  const d0 = data[i - 1];
  const d1 = data[i];
  const currentPoint = correspondingDate - d0['date'] > d1['date'] - correspondingDate ? d1 : d0;
  
  focus.attr('transform',`translate(${xScale(currentPoint['date'])},     ${yScale(currentPoint['close'])})`);
focus
  .select('line.x')
  .attr('x1', 0)
  .attr('x2', width - xScale(currentPoint['date']))
  .attr('y1', 0)
  .attr('y2', 0);
focus
  .select('line.y')
  .attr('x1', 0)
  .attr('x2', 0)
  .attr('y1', 0)
  .attr('y2', height - yScale(currentPoint['close']));
 updateLegends(currentPoint);
}

We can then make use of the d3.bisector() method to locate the insertion point, which will highlight the closest data point on the close price line graph. After determining the currentPoint, the drop lines will be updated. The updateLegends() method uses the currentPoint as the parameter.

然后,我们可以使用d3.bisector()方法定位插入点,该插入点将在收盘价折线图上突出显示最近的数据点。 确定currentPoint ,将更新放置线。 updateLegends()方法使用currentPoint作为参数。

const updateLegends = currentData => {  d3.selectAll('.lineLegend').remove();
const updateLegends = currentData => {
  d3.selectAll('.lineLegend').remove();
  const legendKeys = Object.keys(data[0]);
  const lineLegend = svg
    .selectAll('.lineLegend')
    .data(legendKeys)
    .enter()
    .append('g')
    .attr('class', 'lineLegend')
    .attr('transform', (d, i) => {
      return `translate(0, ${i * 20})`;
    });
  lineLegend
    .append('text')
    .text(d => {
      if (d === 'date') {
        return `${d}: ${currentData[d].toLocaleDateString()}`;
      } else if ( d === 'high' || d === 'low' || d === 'open' || d === 'close') {
        return `${d}: ${currentData[d].toFixed(2)}`;
      } else {
        return `${d}: ${currentData[d]}`;
      }
    })
    .style('fill', 'white')
    .attr('transform', 'translate(15,9)');
  };

The updateLegends() method updates the legend by displaying the date, open price, close price, high price, low price, and volume of the selected mouseover point on the close line graph. Similar to the Volume bar charts, we will make use of the selectAll() method with the enter() and append() methods.

updateLegends()方法通过在闭合折线图上显示日期,开盘价,收盘价,高价,低价和选定鼠标悬停点的数量来更新图例。 与体积条形图类似,我们将使用selectAll()方法以及enter()append()方法。

To render the legends, we will use.selectAll('.lineLegend') to select the legends, followed by calling the remove() method to remove them. Next, we pass the keys of the legends, legendKeys, which will be used to define the height of each bar. The enter() method is called, which compares the volData dataset and at the selection from selectAll(), which is currently empty. Currently, the DOM does not contain any <rect> element. Thus, the append() method accepts an argument ‘rect’, which creates a new <rect> element in the DOM for every single object in volData.

要渲染图例,我们将使用.selectAll('.lineLegend')选择图例,然后调用remove()方法将其删除。 接下来,我们传递图例的键legendKeys ,这些键将用于定义每个小节的高度。 调用enter()方法,该方法比较volData数据集和在selectAll()中的选择selectAll()当前为空)。 当前,DOM不包含任何<re ct>元素。 因此, the ap PEND()方法接受一个ARG ument “矩形”,这产生a new DOM中的每SINGL <矩形>元素e objec在volData吨。

Next, append the legends with their respective properties. We further process the values by converting the prices to 2 decimal places. We also set the date object to the default locale for readability.

接下来,将图例附加其各自的属性。 我们通过将价格转换为2个小数位来进一步处理这些值。 为了便于阅读,我们还将date对象设置默认语言环境

This will be the end result:

这将是最终结果:

总结思想 (Closing Thoughts)

Congratulations! You have reached the end of this tutorial. As demonstrated above, D3.js is simple yet dynamic. It allows you to create custom visualizations for all your data sets. In the coming weeks, I will release the second part of this series which will deep dive into D3.js’s enter-update-exit pattern. Meanwhile, you may wish to check out the API documentation, more tutorials, and other interesting visualizations built with D3.js.

恭喜你! 您已经到了本教程的结尾。 如上所述,D3.js简单但动态。 它允许您为所有数据集创建自定义可视化。 在接下来的几周中,我将发布本系列的第二部分,它将深入探讨D3.js的enter-update-exit模式。 同时,您不妨查看一下API文档更多教程以及使用D3.js构建的其他有趣的可视化

Feel free to check out the source code as well as the full demonstration of this tutorial. Thank you, and I hope you have learned something new today!

随时检查源代码以及本教程的完整演示 。 谢谢,希望您今天学到了一些新知识!

Special thanks to Debbie Leong for reviewing this article.

特别感谢Debbie Leong审阅了本文。

翻译自: https://www.freecodecamp.org/news/how-to-build-historical-price-charts-with-d3-js-72214aaf6ba3/

d3.js图表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值