d3.js 入门学习记录(七) 生成器

629 阅读7分钟

svg的基本形状

  1. line 线条
    • x1 定义线条起点的 x 坐标
    • y1 定义线条起点的 y 坐标
    • x2 定义线条终点的 x 坐标
    • y2 定义线条终点的 y 坐标
  2. polyline 折线
    • points 定义折线的一系列点坐标,坐标间用空格隔开
  3. circle 圆
    • cx 定义圆心的 x 坐标
    • cy 定义圆心的 y 坐标
    • r 定义圆的半径 r
  4. ellipse 椭圆
    • cx 定义圆心的 x 坐标
    • cy 定义圆心的 y 坐标
    • rx 定义椭圆在 x 轴方向上的半径
    • ry 定义椭圆在 y 轴方向上的半径
  5. rect 矩形
    • x 定义矩形的左上角的 x 坐标
    • y 定义矩形的左上角的 y 坐标
    • width 定义矩形的宽
    • height 定义矩形的高
  6. polygon 多边形
    • points 定义多边形的顶点坐标,坐标间用空格隔开
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .line,
        .polyline {
            fill: none;
            stroke: steelblue;
            stroke-width: 2;
        }

        .circle {
            stroke: steelblue;
            stroke-width: 2;
            fill: red;
            fill-opacity: .7;
        }

        .ellipse {
            stroke: steelblue;
            stroke-width: 2;
            fill: yellow;
            fill-opacity: .7;
        }

        .rect,
        .polygon {
            stroke: steelblue;
            stroke-width: 2;
            fill: pink;
            fill-opacity: .7;
        }
    </style>
</head>
<body>
<script src="../d3.js"></script>
<script>
    const svg = d3.select('body').append('svg')

    svg.attr('width', 1000)
        .attr('height', 400)

    svg.append('line')
        .attr('x1', 0)
        .attr('y1', 200)
        .attr('x2', 100)
        .attr('y2', 100)
        .classed('line', true)

    svg.append('circle')
        .attr('cx', 200)
        .attr('cy', 150)
        .attr('r', 50)
        .classed('circle', true)

    svg.append('ellipse')
        .attr('cx', 350)
        .attr('cy', 150)
        .attr('rx', 75)
        .attr('ry', 50)
        .classed('ellipse', true)

    svg.append('rect')
        .attr('x', 450)
        .attr('y', 100)
        .attr('width', 100)
        .attr('height', 100)
        .attr('rx', 10)
        .attr('ry', 20)
        .classed('rect', true)

    svg.append('polygon')
        .attr('points', '600,200 650,100 700,200')
        .classed('polygon', true)

    svg.append('polyline')
      .attr('points', '750,133 780,100 850,155 900,122')
      .classed('polyline', true)
</script>
</body>
</html>

效果如下:

线条生成器 line()

d3 的生成器可以帮助我们将数据映射到svg的属性中,简化了我们对svg的操作。线条生成器就可以帮助我们十分简单的绘制路径。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .line {
            fill: none;
            stroke-width: 2;
            stroke: steelblue;
        }
        .dot {
            fill: #fff;
            stroke: steelblue;
        }
    </style>
</head>
<body>
<h4>Interpolation Mode:</h4>
<div class="control-group">
    <button onclick="render(d3.curveLinear)">linear</button>
    <button onclick="render(d3.curveLinearClosed)"> linear closed</button>
    <button onclick="render(d3.curveStepBefore)">step before</button>
    <button onclick="render(d3.curveStepAfter)">step after</button>
    <button onclick="render(d3.curveBasis)">basic</button>
    <button onclick="render(d3.curveBasisOpen)">basic open</button>
    <button onclick="render(d3.curveBasisClosed)">basic closed</button>
    <button onclick="render(d3.curveBundle)">bundle</button>
    <button onclick="render(d3.curveCardinal)">cardinal</button>
    <button onclick="render(d3.curveCardinalOpen)">cardinal open</button>
    <button onclick="render(d3.curveCardinalClosed)">cardinal closed</button>
    <button onclick="render(d3.curveMonotoneY)">monotone y</button>
    <button onclick="render(d3.curveCatmullRom)">catmull rom</button>

</div>
<script src="../d3.js"></script>
<script>
  const width = 500,
    height = 500,
    margin = 50,
    x = d3.scaleLinear()
      .domain([0, 10])
      .range([margin, width - margin]),
    y = d3.scaleLinear()
      .domain([0, 10])
      .range([height - margin, margin])

  const data = [
    [
      {x: 0, y: 5},
      {x: 1, y: 9},
      {x: 2, y: 7},
      {x: 3, y: 5},
      {x: 4, y: 3},
      {x: 6, y: 4},
      {x: 7, y: 2},
      {x: 8, y: 3},
      {x: 9, y: 2}
    ],
    d3.range(10).map(function (i) {
      return {x: i, y: Math.sin(i) + 5}
    })
  ]

  const svg = d3.select('body')
    .append('svg')
      .attr('width', width)
      .attr('height', height)

  const xScale = d3.scaleLinear()
    .domain([0, 10])
    .range([0, width - 2 * margin])

  const xAxis = d3.axisBottom()
    .scale(xScale)

  svg.append('g')
    .classed('x-axis', true)
    .attr('transform', function () {
      return `translate(${margin}, ${height - margin})`
    })
    .call(xAxis)

  const yScale = d3.scaleLinear()
    .domain([10, 0])
    .range([0, height - 2 * margin])

  const yAxis = d3.axisLeft()
    .scale(yScale)

  svg.append('g')
    .classed('y-axis', true)
    .attr('transform', function () {
      return `translate(${margin}, ${margin})`
    })
    .call(yAxis)

  function render(mode) {
    const line = d3.line()
      .x(function (d) {
        return x(d.x)
      })
      .y(function (d) {
        return y(d.y)
      })
      .curve(mode)

    const lines = svg.selectAll('path.line')
      .data(data)

    lines.enter()
      .append('path')
          .classed('line', true)
      .merge(lines)
        .attr('d', function (d) {
          return line(d)
        })
  }

  function renderDots() {
    data.forEach(function (item) {
      svg.append('g').selectAll('circle')
        .data(item)
        .enter()
          .append('circle')
             .classed('dot', true)
        .attr('cx', function (d) {
          return x(d.x)
        })
        .attr('cy', function (d) {
          return y(d.y)
        })
        .attr('r', 4.5)
    })
  }

  render(d3.curveLinear)
  renderDots()
</script>
</body>
</html>

效果如下:

在图表中,我们的线条是用 path 绘制的而不是 line,我们用line生成器来对数据进行处理,直接生成了 path 的 d 属性而不用我们去计算。

 const line = d3.line()
      .x(function (d) {
        return x(d.x)
      })
      .y(function (d) {
        return y(d.y)
      })
      .curve(mode)

上面这段代码就定义了一个线条生成器,我们通过 x() y() 去指定拿来计算坐标的 x y 值,返回数据在尺度中的对应值,因为这才是对应到图表中的坐标,然后 curve() 可以指定线条的类型。

查看内置的曲线类型可以在 github.com/d3/d3-shape…

区域生成器 area()

代码和线条生成器的示例一样,在 render 函数里加入以下:

const area = d3.area()
        .x(function (d) {
            return x(d.x)
        })
        .y0(y(0))
        .y1(function (d) {
            return y(d.y)
        })
        .curve(mode)

const areas = svg.selectAll('path.area')
    .data([data])

areas.enter()
    .append('path')
        .classed('area', true)
    .merge(areas)
    .attr('d', function (d) {
        return area(d)
    })

效果如下:

在线图的基础上,我们在其中多渲染了面积的部分,同样是借助于 path 元素。

区域生成器需要我们去定义上边界和下边界 x0() x1() y0() y1(),这里因为我们的 x0() x1()是同样的,可以用 x() 方法统一指定。

curve() 指定区域生成器的曲线类型,和线条生成器一般用同种。

圆弧生成器 arc()

圆弧生成器同样要和 path 一起使用。

常使用的设置有:

  • arc.innerRadius() 设置环的内半径
  • arc.outerRadius() 设置环的外半径
  • arc.cornerRadius() 设置拐角半径
  • arc.startAngle() 设置起始角度的取值,默认读取数据中的 startAngle 属性
  • arc.endAngle() 设置终止角度的取值,默认读取数据中的 endAngle 属性
  • arc.padAngle() 设置相邻两个环之间的间隙角度,默认读取数据中的 padAngle 属性
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<div class="control-group">
    <button onclick="render(0)">circle</button>
    <button onclick="render(100)">annulus</button>
    <button onclick="render(0, Math.PI)">circle sector</button>
    <button onclick="render(100, Math.PI)">annulus sector</button>
</div>
<script src="../d3.js"></script>
<script>
    const width = 400,
          height = 400,
          colors = d3.scaleOrdinal(d3.schemeCategory10)

    const svg = d3.select('body').append('svg')
            .classed('pie', true)
            .attr('height', height)
            .attr('width', width)

    function render(innerRadius, endAngle) {
        if (!endAngle) {
            endAngle = 2 * Math.PI
        }

        const data = [
            {startAngle: 0, endAngle: 0.1 * endAngle},
            {startAngle: 0.1 * endAngle, endAngle: 0.2 * endAngle},
            {startAngle: 0.2 * endAngle, endAngle: 0.4 * endAngle},
            {startAngle: 0.4 * endAngle, endAngle: 0.6 * endAngle},
            {startAngle: 0.6 * endAngle, endAngle: 0.7 * endAngle},
            {startAngle: 0.7 * endAngle, endAngle: 0.9 * endAngle},
            {startAngle: 0.9 * endAngle, endAngle: endAngle}
        ]

        const arc = d3.arc()
            .outerRadius(200)
            .innerRadius(innerRadius)

        svg.select('g').remove()

        svg.append('g')
            .attr('transform', 'translate(200, 200)')
        .selectAll('path')
              .data(data)
              .enter()
                .append('path')
                    .classed('arc', true)
                    .attr('fill', function (d, i) {
                        return colors(i)
                    })
                    .attr('d', function (d, i) {
                        return arc(d)
                    })
    }

    render(0)
</script>
</body>
</html>

效果如下:

圆弧的过渡

给圆弧添加过渡效果时,要使用中间帧 attrTween()。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<script src="../d3.js"></script>
<script>
    const width = 400,
          height = 400,
          endAngle = 2 * Math.PI,
          colors = d3.scaleOrdinal(d3.schemeCategory10)

    const svg = d3.select('body').append('svg')
        .classed('pie', true)
        .attr('width', width)
        .attr('height', height)

    function render(innerRadius) {
        const data = [
            {startAngle: 0, endAngle: 0.1 * endAngle},
            {startAngle: 0.1 * endAngle, endAngle: 0.2 * endAngle},
            {startAngle: 0.2 * endAngle, endAngle: 0.4 * endAngle},
            {startAngle: 0.4 * endAngle, endAngle: 0.6 * endAngle},
            {startAngle: 0.6 * endAngle, endAngle: 0.7 * endAngle},
            {startAngle: 0.7 * endAngle, endAngle: 0.9 * endAngle},
            {startAngle: 0.9 * endAngle, endAngle: endAngle}
        ]

        const arc = d3.arc().outerRadius(200).innerRadius(innerRadius)

        svg.select('g').remove()

        svg.append('g')
            .attr('transform', 'translate(200, 200)')
        .selectAll('path')
            .data(data)
            .enter()
                .append('path')
                    .classed('arc', true)
                    .attr('fill', (d, i) => colors(i))
            .transition().duration(1000).delay(3000)
            .attrTween('d', d => {
                const scale = d3.scaleLinear().domain([0, 1]).range([{startAngle: 0, endAngle: 0}, d])
                return t => arc(scale(t))
            })
    }

    render(100)
</script>
</body>
</html>

效果如下: