YIXUNFE / blog

文章区
151 stars 25 forks source link

使用 stroke-dasharray 制作环形图 #60

Open YIXUNFE opened 8 years ago

YIXUNFE commented 8 years ago

使用 stroke-dasharray 制作环形图

近期项目中有一个环形图的需求,经过大家研究后决定使用一款开源插件完成。选用插件主要是考虑到需要兼容 IE678,而目前我们团队中没人熟悉 VML。但是这里我仅简单介绍下我们完成的 N 个实验性方案中的一个 —— 使用 stroke-dasharray 制作环形图。


原理

以前在《玩转虚线边框》的文章中提及过,SVG 元素可以通过 stroke-dasharray 设置虚线边框的长度以及虚线之间的宽度。所以我们只需要通过圆形的路径,使用虚线长度表述所占百分比即可。

比如环状图中有 4 个区域,每个区域各占 25%,那么每个区域应该设置虚线长度为周长的 25%,虚线与虚线之间的间距应该是周长的 75%。

我们可以使用 circle 元素画圆,并指定它的圆心与半径。由于我们最终需要的是圆环,那么我们也需要给圆形指定边宽 stroke-width

<circle cx="200" cy="200" r="120" stroke-width="80" stroke="#ff0000" fill="none"  ></circle>

1

加上 stroke-dasharray 之后:

<circle cx="200" cy="200" r="120" stroke-width="80" stroke="#ff0000" fill="none" stroke-dasharray="189 566" ></circle>

20

stroke-dasharray 的两个值,一个表示虚线的长度,另一个表示虚线与虚线之间的间距,而间距之间是一片空白,所以图形从圆变成了圆弧。然而这好像和我们想象中的有点不一样,我们希望它是从右上方开始的。所以我们需要转换一下坐标系。

<circle cx="200" cy="200" r="120" stroke-width="80" stroke="#ff0000" fill="none" stroke-dasharray="189 566" transform="rotate(-90, 200, 200)" ></circle>

设置 transformrotate 属性,里面的三个参数分别表示旋转的角度、旋转中心的 X 轴坐标,旋转中心的 Y 轴坐标。这里旋转中心就是我们的圆心。

30

这样我们就完成了第一个区域的制作。接下来我们制作第二个区域。第二个区域的圆弧起始位置应该在第一个圆弧的结束位置,即 0 度处(-90 + 360 * 0.25)。

<circle cx="200" cy="200" r="120" stroke-width="80" stroke="#ffff00" fill="none" stroke-dasharray="189 566" transform="rotate(0, 200, 200)" ></circle>

40

以此类推,我们就可以完成整个环状图的构建工作。


抽象

为了能够更加方便的绘制环状图,我们抽象出了一个方法。

/**
 * createArcs 
 * @param {Object} args 配置项
 * @example createArcs({
      bin: div,
      width: 400,
      height: 400,
      r: 180,
      arcWidth: 20,
      data: [
        {color: '#ff0000', percent: 0.2, text: '第1项'},
        {color: '#ffff00', percent: 0.7, text: '第2项'},
        {color: '#ff00ff', percent: 0.1, text: '第3项'}
      ]
    })
 */
function createArcs (args) {
  var html = '<svg id="ringSvg" width="' + args.width + '" height="' + args.width + '" >',
    cx = args.width / 2, cy = args.height / 2,
    r = args.r,
    aw = args.arcWidth,
    data = args.data,
    i = 0, l = 0,
    deltaR = 15, //鼠标移动上去时候的偏移值
    circleLong = r * 2 * Math.PI,
    cl2 = (r + deltaR) * 2 * Math.PI,
    currentReg = 0,
    itemData = []

  html += '<circle class="defaut-arc" cx="' + cx + '" cy="' + cy + '" r="' + r + '" stroke-width="' + aw + '" stroke="none" fill="none"></circle>'

  l = data.length
  for (i; i < l; i++) {
    html += (function (i) {
      return getCircle(data[i])
    }(i))
  }

  function getCircle (d) {
    var l1 = Math.ceil(circleLong * d.percent),
      l2 = Math.ceil(circleLong * (1 - d.percent)),
      l3 = Math.ceil(cl2 * d.percent),
      l4 = Math.ceil(cl2 * (1 - d.percent)),
      html = ''
      // rotate(' + (d.percent * 180 + currentReg * 360) + ', ' + cx + ', ' + cy + ')
    html = '<circle class="arc-item arc-item' + i + '" cx="' + cx + '" cy="' + cy + '" r="' + r + '" stroke-width="' + aw + '" stroke="' + d.color + '" fill="none" transform="rotate(' + (-90 + currentReg * 360) + ', ' + cx + ', ' + cy + ')" stroke-dasharray="' + l1 + ' ' + l2 + '"></circle>'
    currentReg = (d.percent * 100 + currentReg * 100) / 100
    itemData.push([l1 + ' ' + l2, l3 + ' ' + l4])
    return html
  }

  html += '</svg>'

  args.bin.innerHTML = html

}

查看 DEMO


过渡动画

为了能够多点交互效果,我们还尝试了设置半径做放大缩小的效果。

//正常时
<circle cx="200" cy="200" stroke-dasharray="189 566" transform="rotate(-90, 200, 200)" r="120" stroke-width="80" stroke="#ff0000" fill="none"  ></circle>

//放大时
<circle cx="200" cy="200" stroke-dasharray="213 637" transform="rotate(-90, 200, 200)" r="135" stroke-width="80" stroke="#ff0000" fill="none"  ></circle>
.arc-item {transition: r ease-in .25s, stroke-dasharray ease-in .25s}

由于半径改变了,所以 stroke-dasharray 也会相应变化,因为周长和半径相关。

23

这个效果目前在 Chrome 48 下可以完美运行,但 Chrome 42 确认不行,IE 、FF 也不行。可能原因是 CSS 并不支持 r 属性的过渡。因为规范中列出的 CSS 支持的 SVG 属性中并没有 r 属性。


用百分比设置 stroke-dasharray

以前也没想到过使用百分比设置 stroke-dasharray,这次由于是比例关系,用百分比设置看起来会方便很多,比如 stroke-dasharray="25% 75%",是不是就可以省去周长的计算呢?答案是不行。经过一些测试,发现百分比的参照物并不是圆形自身,而是 svg 元素。百分比的取值也比较奇特,100% 表示的是参照物高宽的平均值,即 (width + height) / 2


Thanks