学习D3.js(二十四)平移折线图

428 阅读5分钟

我正在参加「掘金·启航计划」

引入D3模块

  • 引入整个D3模块。
<!-- D3模块 -->
<script src="https://d3js.org/d3.v7.min.js"></script>

数据

  • 有的时候数据是在.csv文件中的,这里对.csv文件数据进行模拟。
function getMockCsvData() {
  return (
    'date,price\n' +
    'Jan 2000,1394.46\n' +
    'Feb 2000,1366.42\n' +
    'Mar 2000,1498.58\n' +
    'Apr 2000,1452.43\n' +
    'May 2000,1420.6\n' +
    'Jun 2000,1454.6\n' +
    'Jul 2000,1430.83\n' +
    'Aug 2000,1517.68\n' +
    'Sep 2000,1436.51\n' +
    'Oct 2000,1429.4\n' +
    'Nov 2000,1314.95\n' +
    'Dec 2000,1320.28\n' +
    'Jan 2001,1366.01\n' +
    'Feb 2001,1239.94\n' +
    'Mar 2001,1160.33\n' +
    'Apr 2001,1249.46\n' +
    'May 2001,1255.82\n' +
    'Jun 2001,1224.38\n' +
    'Jul 2001,1211.23\n' +
    'Aug 2001,1133.58\n' +
    'Sep 2001,1040.94\n' +
    'Oct 2001,1059.78\n' +
    'Nov 2001,1139.45\n' +
    'Dec 2001,1148.08\n' +
    'Jan 2002,1130.2\n' +
    'Feb 2002,1106.73\n' +
    'Mar 2002,1147.39\n' +
    'Apr 2002,1076.92\n' +
    'May 2002,1067.14\n' +
    'Jun 2002,989.82\n' +
    'Jul 2002,911.62\n' +
    'Aug 2002,916.07\n' +
    'Sep 2002,815.28\n' +
    'Oct 2002,885.76\n' +
    'Nov 2002,936.31\n' +
    'Dec 2002,879.82\n' +
    'Jan 2003,855.7\n' +
    'Feb 2003,841.15\n' +
    'Mar 2003,848.18\n' +
    'Apr 2003,916.92\n' +
    'May 2003,963.59\n' +
    'Jun 2003,974.5\n' +
    'Jul 2003,990.31\n' +
    'Aug 2003,1008.01\n' +
    'Sep 2003,995.97\n' +
    'Oct 2003,1050.71\n' +
    'Nov 2003,1058.2\n' +
    'Dec 2003,1111.92\n' +
    'Jan 2004,1131.13\n' +
    'Feb 2004,1144.94\n' +
    'Mar 2004,1126.21\n' +
    'Apr 2004,1107.3\n' +
    'May 2004,1120.68\n' +
    'Jun 2004,1140.84\n' +
    'Jul 2004,1101.72\n' +
    'Aug 2004,1104.24\n' +
    'Sep 2004,1114.58\n' +
    'Oct 2004,1130.2\n' +
    'Nov 2004,1173.82\n' +
    'Dec 2004,1211.92\n' +
    'Jan 2005,1181.27\n' +
    'Feb 2005,1203.6\n' +
    'Mar 2005,1180.59\n' +
    'Apr 2005,1156.85\n' +
    'May 2005,1191.5\n' +
    'Jun 2005,1191.33\n' +
    'Jul 2005,1234.18\n' +
    'Aug 2005,1220.33\n' +
    'Sep 2005,1228.81\n' +
    'Oct 2005,1207.01\n' +
    'Nov 2005,1249.48\n' +
    'Dec 2005,1248.29\n' +
    'Jan 2006,1280.08\n' +
    'Feb 2006,1280.66\n' +
    'Mar 2006,1294.87\n' +
    'Apr 2006,1310.61\n' +
    'May 2006,1270.09\n' +
    'Jun 2006,1270.2\n' +
    'Jul 2006,1276.66\n' +
    'Aug 2006,1303.82\n' +
    'Sep 2006,1335.85\n' +
    'Oct 2006,1377.94\n' +
    'Nov 2006,1400.63\n' +
    'Dec 2006,1418.3\n' +
    'Jan 2007,1438.24\n' +
    'Feb 2007,1406.82\n' +
    'Mar 2007,1420.86\n' +
    'Apr 2007,1482.37\n' +
    'May 2007,1530.62\n' +
    'Jun 2007,1503.35\n' +
    'Jul 2007,1455.27\n' +
    'Aug 2007,1473.99\n' +
    'Sep 2007,1526.75\n' +
    'Oct 2007,1549.38\n' +
    'Nov 2007,1481.14\n' +
    'Dec 2007,1468.36\n' +
    'Jan 2008,1378.55\n' +
    'Feb 2008,1330.63\n' +
    'Mar 2008,1322.7\n' +
    'Apr 2008,1385.59\n' +
    'May 2008,1400.38\n' +
    'Jun 2008,1280\n' +
    'Jul 2008,1267.38\n' +
    'Aug 2008,1282.83\n' +
    'Sep 2008,1166.36\n' +
    'Oct 2008,968.75\n' +
    'Nov 2008,896.24\n' +
    'Dec 2008,903.25\n' +
    'Jan 2009,825.88\n' +
    'Feb 2009,735.09\n' +
    'Mar 2009,797.87\n' +
    'Apr 2009,872.81\n' +
    'May 2009,919.14\n' +
    'Jun 2009,919.32\n' +
    'Jul 2009,987.48\n' +
    'Aug 2009,1020.62\n' +
    'Sep 2009,1057.08\n' +
    'Oct 2009,1036.19\n' +
    'Nov 2009,1095.63\n' +
    'Dec 2009,1115.1\n' +
    'Jan 2010,1073.87\n' +
    'Feb 2010,1104.49\n' +
    'Mar 2010,1140.45'
  )
}

// timeParse() 解析和格式化时间 -- %b月 %Y年
const parseDate = d3.timeParse('%b %Y')
function type(d) {
  d.date = parseDate(d.date)
  d.price = +d.price
  return d
}

// csvParse() 解析给定的CSV字符串,返回一个对象数组。 type() 额外处理每个对象的方法
const data = d3.csvParse(getMockCsvData(), type)
  1. d3.timeParse('%b %Y') 时间格式化。%b:月份、%Y:以世纪作为十进制数的年份。
  2. d3.csvParse() 解析给定的CSV字符串,返回一个对象数组。
  • 模拟CSV格式数据,使用d3的API转换为js数组。

添加画布

// 全局画布变量
const margin = { top: 50, right: 50, bottom: 125, left: 50 }
const margin2 = { top: 410, right: 50, bottom: 50, left: 50 }
const width = 960 - margin.left - margin.right
const height = 500 - margin.top - margin.bottom
const height2 = 500 - margin2.top - margin2.bottom

const svg = d3
  .select('.d3Chart')
  .append('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom)

比例尺和配置信息

// 比例尺
const x = d3.scaleTime().range([0, width])
const x2 = d3.scaleTime().range([0, width])
const y = d3.scaleLinear().range([height, 0])
const y2 = d3.scaleLinear().range([height2, 0])
  • 创建时间的线性比例尺。
  • 创建定量的线性比例尺。
// 坐标轴
const xAxis = d3.axisBottom(x)
const xAxis2 = d3.axisBottom(x2)
const yAxis = d3.axisLeft(y)
  • 创建坐标轴绘制函数。
// 为比例尺添加输入域。
x.domain(
  d3.extent(
    data.map(function (d) {
      return d.date
    })
  )
)
y.domain([
  0,
  d3.max(
    data.map(function (d) {
      return d.price
    })
  )
])
x2.domain(x.domain())
y2.domain(y.domain())
  • 为比例尺添加默认输入域。
// 沿x轴创建画笔
const brush = d3
  .brushX()
  .extent([
    [0, 0],
    [width, height2]
  ]) // 设置 画笔范围
  .on('brush', brushed)
function brushed(brush) {}
  • 创建二维画笔。图表的移动根据画笔选中区域进行变化。
// 区域绘制
const area = d3
  .area()
  .x(function (d) {
    return x(d.date)
  })
  .y0(height)
  .y1(function (d) {
    return y(d.price)
  })

const area2 = d3
  .area()
  .x(function (d) {
    return x2(d.date)
  })
  .y0(height2)
  .y1(function (d) {
    return y2(d.price)
  })
  1. d3.area() 创建面积生成器。
  • 创建面积生成器,这里的图表绘制的区域折线图。如果只需要折线,就使用线生成器。

绘制图表和画笔操作

绘制折线图表

const chartSvg = svg
  .append('g')
  .attr('class', 'focus')
  .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
// 绘制坐标轴
chartSvg
  .append('g')
  .attr('class', 'x axis')
  .attr('transform', 'translate(-1,' + height + ')')
  .call(xAxis)
chartSvg.append('g').attr('transform', 'translate(-1,0)').attr('class', 'y axis').call(yAxis)

// 绘制区域
chartSvg.append('path').datum(data).attr('class', 'area').attr('fill', 'steelblue').attr('d', area)

image.png

  • 创建图表绘制组,使用坐标轴创建函数。
  • 使用面积生成器,绘制折线。

绘制画笔区域

const context = svg
  .append('g')
  .attr('class', 'context')
  .attr('transform', 'translate(' + margin2.left + ',' + margin2.top + ')')
context
  .append('g')
  .attr('class', 'x axis')
  .attr('transform', 'translate(0,' + height2 + ')')
  .call(xAxis2)
// 折线区域
context.append('path').datum(data).attr('class', 'area').attr('fill', 'steelblue').attr('d', area2)
// 添加画笔
context
  .append('g')
  .attr('class', 'x brush')
  .call(brush)
  .selectAll('rect')
  .attr('y', -6)
  .attr('height', height2 + 7)

2.gif

  • 绘制画笔区域的图表,在图表上添加画笔功能。
// 设置裁剪
svg.append('defs').append('clipPath').attr('id', 'clip').append('rect').attr('width', width).attr('height', height)
chartSvg.select('.area').attr('clip-path', 'url(#clip)')
  • 创建一个裁剪区域,设置给折线区域。
/*
 * 画笔 回调
 */
function brushed(brush) {
  // x2.invert 计算与给定范围值对应的域值。
  const selection = brush.selection
  x.domain(selection.map(x2.invert))
  chartSvg.select('.area').attr('d', area)
  chartSvg.select('.x.axis').call(xAxis)
}

4.gif

  • 根据画笔选中的区域,获取比例尺对应的数组值。
  • 跟新图表的X轴比例尺,重新绘制X轴。实现平移效果

总结

本节使用的是裁剪的方式实现。每次变换都不改变原始数据,只修改x轴的比例尺,来重新绘制图形和x坐标轴。折线区域图会完整的绘制,使用裁剪的方式只加载图表范围的区域,就实现了平移效果。