D3数据可视化-折线-line

案例

折线图适合展示随着时间推进,数值的变化趋势。下图是几家科技公司在2009年6月到2019年6月间的股票价格图,数据来源于雅虎金融。

stock-price.png

解析

SVG 有几种元素都可以画出线段。

  • line: 用于绘制直线,只需要起始和结束点的x轴和y轴坐标就可以确定一段直线的位置。类似这样 <line x1="0" y1="0" x1="100" y="100" stroke="black" />

  • polyline: 折线元素,需要起始和结束以及中间各点的坐标来定位线段。例子像这样:
    <polyline points="0,0 20,30 50,40" fill="stroke" stroke="black"/>

  • path: 可以渲染各种各样的图形,d3.line 可以帮助我们生成 d 属性用于描述折线。

在上面的折线例子中,纵坐标代表股票价格,可以使用 scaleLinear 来 scale 值到图形高度。横坐标代表连续的时间点,每个刻度(tick)代表一天,scaleTime 正好适合这种场景。

需要注意的是,有的时间坐标场景并不适合使用 scaleTime。如果横坐标代表不同的月份,由于每个月份的天数不一样,那么每个月份刻度之间的距离会不同,这可能并不是我们期望的结果。如果希望月份之间距离相同,那么还是采用 scalePoint 比较合适。

实现

惯例先上Git地址

核心代码就几行

const xScale = d3.scaleTime()
  .domain([firstDate, lastDate])
  .range([0, maxWidth]);
const yScale = d3.scaleLinear()
  .domain([minY, maxY])
  .range([maxHeight, 0]);
const line = d3.line().x((d) => xScale(d)).y((d) => yScale(d));

d3.csv() 可以读取 csv 文件,因为这次需要读取几个比较大的文件,采用了并行读取的方式。

下面是完整版:

<!DOCTYPE html>
<html>
  <body>
    <style>
      svg {
        border: 1px solid lightgrey;
      }
    </style>

    <div>
      Stock Price of Tech Companies in the recent 10 years
    </div>
    <script src="http://d3js.org/d3.v5.min.js"></script>
    <script type="text/javascript">
      const maxHeight = 400;
      const maxWidth = 600;
      const barWidth = 20;
      const svg = d3.select('body')
        .append('svg')
        .attr('width', maxWidth + 50)
        .attr('height', maxHeight + 80);
      const colorArray = ['#38CCCB', '#0074D9', '#2FCC40', '#FEDC00', '#FF4036'];
      function renderLines(data, legends) {
        const getX = (d) => d.date;
        const getY = (d) => d.close;
        const lineMin = (data) => d3.min(data, getY);
        const lineMax = (data) => d3.max(data, getY);
        const xScale = d3.scaleTime()
          .domain(d3.extent(data[0], getX))
          .range([0, maxWidth]);
        const minY = d3.min(data, lineMin);
        const maxY = d3.max(data, lineMax);
        const yScale = d3.scaleLinear()
          .domain([minY, maxY])
          .range([maxHeight, 0]);
        const line = d3.line().x((d) => xScale(getX(d))).y((d) => yScale(getY(d)))
        svg.selectAll('path')
          .data(data)
          .enter()
          .append('path')
          .attr('d', (d) => {
            const lineData = line(d);
            return lineData;
          })
          .attr('stroke', (d, i) => colorArray[i % colorArray.length])
          .attr('fill', 'none');
        const axisRight = d3.axisRight(yScale);
        svg.append('g')
          .attr('transform', `translate(${maxWidth}, 0)`)
          .call(axisRight);
        const axisBottom = d3.axisBottom(xScale);
        svg.append('g')
          .attr('transform', `translate(0, ${maxHeight})`)
          .call(axisBottom);
        svg.append('g')
          .attr('width', 500)
          .attr('height', 30)
          .selectAll('.legend')
          .data(legends)
          .enter()
          .append('text')
          .attr('class', 'legend')
          .text((d) => d)
          .attr('y', maxHeight + 50)
          .attr('x', (d, i) => 50 + i * 100)
          .attr('stroke', (d, i) => colorArray[i % colorArray.length])
      }
      async function getData(fileLocation) {
        const data = await d3.csv(fileLocation, (row) => {
          return {
            date: new Date(row.Date),
            close: parseFloat(row.Close),
          };
        });
        return data;
      }
      async function render() {
        const files = ['AAPL.csv', 'INTC.csv', 'FB.csv', 'AMZN.csv', 'GOOG.csv']; // stock price in the recent 10 years
        const legends = files.map((file) => {
          const organization = /[a-zA-Z0-9]+(?=\.csv)/;
          const searchRes = organization.exec(file);
          return searchRes ? searchRes[0] : '';
        });
        const fetchQueue = files.map(getData);
        Promise.all(fetchQueue).then((data) => renderLines(data, legends));
      }
      render();
    </script>
  </body>
</html>
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容