最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • “D3.js” 手绘分段折线图

    正文概述 掘金(黄刀小五)   2021-03-28   1022

    写在前面

    双手奉上代码链接 传送门 - ajun568

    双脚奉上最终效果图

    “D3.js” 手绘分段折线图

    观前提醒

    ? 本文以实现上图为最终目的,所有过程均服务于结果,而非对svgD3.js的系统学习。

    导读

    ? 朋友,你是否与我有相同的功能诉求,使用主流的图表库不易满足我们的需求;朋友,你是否又和我一样急于求成,想在短时间内完成相应的功能开发。如果屏幕前你也有相同的想法, 那D3是一个不错的选择,它易于上手且可针对需求定制化绘制。下面就让我们一同进入这个充满奇幻色彩的图形世界吧❗️

    准备工作

    yarn add d3

    截止2021-03,当前最新版本d3^6.6.0,我们以此版本来展开旅程。万变不离其宗,如若版本更替,建议采用最新版本。

    ?解剖

    拟定数据格式如下

    dataset = [
      {
        xValue: x轴数据 | Number,
        yValue: y轴数据 | Number,
        filled: 是否为实心点 | Boolean,
      },
      ...[and so on]
    ]
    

    look ? picture

    可将其拆解为以下几个部分:

    • 坐标轴 (x,y)(x, y)(x,y)

    • 坐标点 & 点到坐标轴的虚线

    • 路径

    • tooltip & hover时点到坐标轴的虚线

    浅谈SVG

    普通场景下 d3.jsd3.jsd3.js 就是 svgsvgsvg 的语法糖

    既然通篇都要与 svgsvgsvg 打交道,怎么能不认识一下这个可爱的小家伙呢,所谓知己知彼,百战不殆

    viewport => width / height: 指定画布的宽度和高度

    <svg width="800" height="400"></svg>
    

    viewBox => (x, y, width, height): 从(x,y)(x, y)(x,y)点, 向正方向选取宽为widthwidthwidth, 高为heightheightheight的矩形, 并放大至画布大小

    正方向如图:

    “D3.js” 手绘分段折线图

    来几段代码感受一下

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <rect width="20" height="15" fill="red"/>
    </svg>
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="10, -150, 400, 300">
      <rect width="20" height="15" fill="red"/>
    </svg>
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    此图为在(10, -150)位置, 向正方向选取400✖️300的画布并展示

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="-10, -10, 40, 30">
      <rect width="20" height="15" fill="red"/>
    </svg>
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    此图相当于将选取的元素放大了20倍

    常见标签

    rect 绘制矩形 @params => x, y, width, heightx,yx, yx,y 为矩形偏移量。上文都是以矩形举例的,就不在赘述了。

    circle 绘制圆形

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <circle cx="100" cy="60" r="50" fill="red"/>
    </svg>
    

    tips:tips:tips: fill 填充色

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    ellipse 绘制椭圆

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <ellipse cx="100" cy="60" rx="80" ry="50" fill="red"/>
    </svg>
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    text 绘制文本

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <text x="100" y="60" stroke="red">我是绘制的文字</text>
    </svg>
    

    tips:tips:tips:

    stroke 描边色

    style -> text-anchor 对齐方式, 默认middle, 可选start、middle、end

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    line 绘制直线

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <line x1="100" y1="60" x2="300" y2="10" stroke="red" stroke-width="2"/>
    </svg>
    

    tips:tips:tips: stroke-width 描边宽度

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    polyline 绘制折线

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <polyline points="30,140 100,60 300,10 350,50" fill="none" stroke="red" stroke-width="2"/>
    </svg>
    

    tips:tips:tips: 记得fillnone, 否则路径部分会被填充哟

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    path 路径

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <path d="M 20,20 L 100,60 L 20,100 L 60,60 Z" fill="red"/>
    </svg>
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    g 分组 ? 将标签进行分组, 便于归类或复用

    use 复制

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <path d="M 20,20 L 100,60 L 20,100 L 60,60 Z" fill="red"/>
      <use href="#arrow" x="200" y="0" />
    </svg>
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    defs 自定义图形

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <defs>
        <path id="arrow" d="M 20,20 L 100,60 L 20,100 L 60,60 Z"/>
      </defs>
      <use href="#arrow" x="200" y="0" fill="red" />
    </svg>
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    层级关系

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <rect x="0" y="0" width="30" height="30" fill="purple"/>
      <rect x="20" y="5" width="30" height="30" fill="blue"/>
      <rect x="40" y="10" width="30" height="30" fill="green"/>
      <rect x="60" y="15" width="30" height="30" fill="pink"/>
      <rect x="80" y="20" width="30" height="30" fill="red"/>
    </svg>
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    ✍️绘制画布

    前置知识准备的差不多了,伙计们,进入正题了❗️

    “D3.js” 手绘分段折线图

    FirstFirstFirst ofofof allallall, 我们先把htmlhtmlhtml结构建出来

    <div id="line"></div>
    

    SecondlySecondlySecondly, 我们用d3在这个div中创建出我们的画布, 宽高为400✖️800

    • d3d3d3为链式结构

    • select 选择元素 (选ididid)

    • selectAll 选择全部元素 (选元素, 选类)

    • append 追加元素

    • attr 添加属性

    const width = 800
    const height = 400
    
    d3.select('#line')
      .append('svg')
      .attr('width', width)
      .attr('height', height)
    

    ✍️绘制坐标轴

    比例尺

    在开始比例尺之前,我们先来看几个比较好用的函数:

    • d3.max(arr) return maxmaxmax

    • d3.min(arr) return minminmin

    • d3.extent(arr) return [min,max][min, max][min,max]

    本文所用到的为线性比例尺scaleLinear

    whatwhatwhat isisis 线性比例尺

    ? 将一个连续的区间,映射到另一区间 (domin 映射到 range)

    “D3.js” 手绘分段折线图

    翠花, 上代码

    ? xScale
    d3.scaleLinear() // 创建线性比例尺
      .domain([ // domin数据 [x.min, x.max]
        d3.min(xData),
        d3.max(xData)
      ])
      .rangeRound([0, width]) // range数据 [0, width]
    

    xAxis绘制

    接下来,我们用axisBottom创建一个向下的坐标轴,并通过scale调用设置好的比例尺

    ? xAxis
    d3.axisBottom().scale(xScale)
    

    然后, 通过call方法填充至画布上

    svg.call(xAxis)
    

    至此, 一个粗糙的x轴就画好了, 接着奏乐接着舞

    “D3.js” 手绘分段折线图

    有没有发现什么不对劲:

    • 右侧的100100100惨遭截肢

    • 坐标轴占满了整个画布, 丝毫没有美感

    • xxx轴不应该在最下面吗, 左侧也要给它的好基友yyy轴留位置

    “D3.js” 手绘分段折线图

    1,21, 21,2的核心问题就是没有留白, 既然要留白, 加padding就完事了; 而333一个translate就可以搞定

    const padding = { top: 30, right: 30, bottom: 30, left: 30 }
    
    ? xScale
    - .rangeRound([0, width])
    + .rangeRound([0, width - padding.left - padding.right])
    
    ? svg
    svg
      .append('g')
      .attr('transform', `translate(${padding.left}, ${height - padding.bottom})`)
      .call(xAxis)
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    No.1

    • axis.tickValues([...arr]) 用于指定坐标轴显示的值

    • axis.tickFormat() 格式化坐标轴数据

    eg:eg:eg: 坐标轴数据按千分符形式格式化 axis.tickFormat(d3.format(",.0f")), 其中.0f为不格式小数部分

    No.2

    第二点则可理解为给左右两端各补一条数据, 然后对两点连线. 而要补数据, 就要拟定补多少, 我们引入一个份数的概念, 将1份定义为总长度的 1/dataset.length21/dataset.length * 21/dataset.length∗2, 最小为 1/101/101/10.

    // 份数计算
    + const length = dataset.length * 2
    + const partDistance = (d3.max(xData) - d3.min(xData)) / (length > 10 ? 10 : length)
    
    .domain([
    -   d3.min(xData),
    -   d3.max(xData)
    ])
    
    .domain([
    +  d3.min(xData, item => {
    +    return item - partDistance
    +  }),
    +  d3.max(xData, item => {
    +    return item + partDistance
    +  })
    ])
    

    No.3

    上文 “浅谈SVG中” 我们已经对 defsgpathuse 分别做了讲解.

    翠花, 上代码

    const arrowPath = 'M4,4 L20,12 L4,20 L8,12 Z'
    const axisColor = '#fff'
    const arrowOffsetDistance = 12
    
    svg
      .append('defs')
      .append('g')
      .attr('id', 'arrowX')
      .append('path')
      .attr('d', arrowPath)
      .attr('fill', axisColor)
    
    svg
      .append('use')
      .attr('href', '#arrowX')
      .attr('x', width - padding.right - arrowOffsetDistance)
      .attr('y', height - padding.bottom - arrowOffsetDistance)
    

    M4,4 L20,12 L4,20 L8,12 Z

    • d3.path() 创建path路径

    • moveTo(x, y) M

    • lineTo(x, y) L

    • closePath() Z

    故也可根据方法动态生成

    let arrowPath = d3.path() // M4,4 L20,12 L4,20 L8,12 Z
    arrowPath.moveTo(4, 4)
    arrowPath.lineTo(20, 12)
    arrowPath.lineTo(4, 20)
    arrowPath.lineTo(8, 12)
    arrowPath.closePath()
    

    No.+∞

    为了使坐标轴更美观, 我们来随意点缀几笔, 最终呈现效果如下:

    “D3.js” 手绘分段折线图

    tip:tip:tip: 通过select / selectAll去选中元素更改对应属性 (或stylestylestyle或svgsvgsvg的图形属性)

    补其它的好基友yyy轴斯密达

    “D3.js” 手绘分段折线图

    marker标记

    用于对图形做元素追加, 我们的坐标轴就非常符合这个特征

    • markerWidth / markerHeight 宽 / 高

    • refX / refY x/yx / yx/y 轴偏移量

    • markerUNits 是否允许marker随所连接图形的缩放而跟随缩放, 默认strokeWidth(缩放), 可选userSpaceOnUse(不缩放)

    • orient 旋转角度, 默认autoautoauto, 可指定具体旋转度数

    ❓ 如何对以绘制的图形做追加

    ? 对以绘制的图形添加以下属性: marker-end="url(#id)", 可选 [marker-start、marker-mid、marker-end]

    特别注意: marker可以理解为作用于点, 所以marker-mid对两点间的连线是没有效果的, 要至少三个点才能产生效果

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <marker id="arrow" markerWidth="12" markerHeight="12" refX="6" refY="6" orient="30deg">
        <path d="M2,2 L10,6 L2,10 L4,6 Z" fill="red" />
      </marker>
      <line x1="100" y1="60" x2="300" y2="60" stroke="red" stroke-width="2" marker-end="url(#arrow)" />
    </svg>
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    两种写法的对比如下:

    “D3.js” 手绘分段折线图

    绘制坐标点

    坐标点❓画个圈圈诅咒你 (画圆呀)

    空心点❓人体描边大法 (stroke-width 你值得拥有)

    映射到坐标轴❓比例尺 炼金术 (按比例尺映射回去)

    ?‍♂️ Code

    “D3.js” 手绘分段折线图

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    绘制折线

    点、线、面, 有了点我们开始连线. 用什么呢? 我建议大家用path(“绘制坐标轴”处已说明用法), 但我选择用line, 没什么原因, 任性而已, 不服你打我呀!

    “D3.js” 手绘分段折线图

    ? 知识点

    d3.line().defined() 当前点是否与其相邻点进行连线

    以点画线, 可点真的全了吗? 起点在哪里呀, 终点在哪里, 在那小朋友的眼睛里? 我们按前面计算的份数补全下数据, 然后绘图.

    // 折线数据处理
    let newDataset = []
    newDataset.push({
      xValue: dataset[0].xValue - partDistance,
      yValue: dataset[0].yValue,
      filled: true
    })
    
    newDataset.push(...dataset)
    
    newDataset.push({
      xValue: dataset[dataset.length - 1].xValue + partDistance,
      yValue: dataset[dataset.length - 1].yValue,
      filled: true
    })
    

    “D3.js” 手绘分段折线图

    线覆盖了空心点 ❓ 根据量子力学 - svgsvgsvg层级顺序定律, 一定是图层顺序搞反了. 换下代码顺序, 完美解决.

    xxx坐标相同的连线 ❓ 有没有注意到上面的defined, 不过, 我是要对其x坐标相同的点不进行连线, 而不是放空这个点, 还它自由. 克隆一个, Perfect, 完美解决问题. 而至于克隆哪个点, 随心情就好, 你说我两个都想要, 拖出去斩了.

    // 折线数据处理
    - newDataset.push(...dataset)
    + dataset.forEach(item => item.filled ? newDataset.push(item) : newDataset.push(...[item, item]))
    

    ?‍♂️ Code

    “D3.js” 手绘分段折线图

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    绘制坐标点到坐标轴的虚线

    stroke-dasharray : 用于绘制虚线, 每绘制xxx个像素点, 则空余yyy个像素点

    ?‍♂️ Code

    <svg width="400" height="300" viewBox="0, 0, 400, 300">
      <line x1="50" y1="50" x2="350" y2="50" stroke-dasharray="3" fill="none" stroke="red" stroke-width="3"></line>
      <line x1="50" y1="180" x2="350" y2="180" stroke-dasharray="6, 18, 4, 12" fill="none" stroke="red" stroke-width="3"></line>
    </svg>
    

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    找到点, 连上线, 我们的虚线就画好了

    翠花, 上酸菜

    “D3.js” 手绘分段折线图

    ?‍♂️ Image

    “D3.js” 手绘分段折线图

    Tooltip

    在构思tooltip之前, 先抛出几个问题

    tooltip显示的是什么

    ? 对应点的坐标

    ❓ hover的区域是什么

    ? 整个坐标轴

    ❓ 显示的位置在哪里

    ? 对应点的旁边

    ❓ 还有需要注意的吗

    ? 不要超出边界范围

    “D3.js” 手绘分段折线图

    我们来总结下

    hoverhoverhover的是整个区域 ? 一个覆盖整个坐标轴的rectrectrect, 然后对此进行事件处理

    tooltiptooltiptooltip ? 一个显示在对应点旁边的小divdivdiv

    hoverhoverhover时找到x=c上对应的点的坐标, 对xxx轴和yyy轴连接虚线, 并在点旁显示tooltiptooltiptooltip

    躁动起来

    先来append一个rect, 然后对齐做事件处理. 这里用到我们熟悉且性感的老朋友们就可以了, 下面有请他们闪亮登场:

      svg
        .append('rect')
        .attr('width', areaWidth)
        .attr('height', areaHeight)
        .style('fill', 'none')
        .style('pointer-events', 'all')
        .style('cursor', 'pointer')
        .attr('transform', `translate(${padding.left}, ${padding.top})`)
        .on('mouseover', mouseOver)
        .on('mouseout', mouseOut)
        .on('mousemove', mouseMove)
    

    接下来我们把tooltiptooltiptooltip和其到xxx轴与到yyy轴的虚线绘制出来.

    这时会有这样一个疑惑, 我还没有计算位置, 怎么绘制, 看官莫急, 请看下文

    “D3.js” 手绘分段折线图

    mouseMove这个小家伙可以帮我们拿到所在点的offsetX, 那找点的问题就转变成了已知xxx求yyy, 进而转变成求关联x,yx,yx,y的函数表达式.

    已知首段和尾段yyy恒定(y=cy=cy=c), 中间各段为:

    (xx1)(y2y1)=(x2x1)(yy1)(x - x1) * (y2 - y1) = (x2 - x1) * (y - y1)(x−x1)∗(y2−y1)=(x2−x1)∗(y−y1)

    起源地下载网 » “D3.js” 手绘分段折线图

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元