svg简单绘图+d3操作视图

261 阅读2分钟

d3基础知识

元素选择

  • 通过 css 选择器选择
  • 通过 dom 对象选择
  • 通过原生 js 获取 title 元素
  • 多选
  • 过滤元素
<h1 id="title">
    <p></p>
    <p></p>
    <p></p>
    <p></p>
</h1>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
    d3.select("#title").classed('sky', true);
    d3.select("p").classed('sky', true);
    const title = document.querySelector("#title");
    d3.select(title).classed('sky', true);
    
    d3.selectAll('p').classed('sky', true);
    d3.selectAll('p:nth-child(2)').classed('sky', true);
    d3.selectAll('p:nth-child(even)').classed('sky', true);
    
    d3.selectAll('p').filter((ele, ind) => (ind%2) == 1).classed('sky', true);;
    
    const n = [1, 2, 3];
    d3.selectAll('p').filter((ele, ind) => n.includes(ind)).classed('sky' ,true);
</script>

设置元素样式

  • 设置 dom 内联样式

    style 内联样式

    classed 增删 class

    attr 设置属性,会覆盖先前的属性值

  • style() 方法设置标题颜色

  • attr() 方法通过 style 属性的设置,改变标题颜色

  • classed(class, true | false) 方法增删标题的 class

  • attr() 方法设置 class 属性

  • 用 style 的回调函数,让所有 p 元素偶数行变蓝

  • 用 data 数据绑定,动态设置 p 元素的字体大小

const title = d3.select('#title');
title.style('color', 'red');
title.attr('style', 'color: red');
title.classed('sky', true);
// 前者不会覆盖,后者会覆盖掉前面的 class 属性值
title.attr('class', true);

d3.selectAll('p').style('color', (ele, ind) => ind%2 ? 'red' : 'black');

const data = [12, 16, 17, 24, 56];
d3.selectAll('p').data(data).style('font-size', (ele, ind) => {
    return `${ele}px`
});
d3.selectAll('p').data(data).style('font-size', (ele, ind) => `40px`);

设置元素属性

  • 设置 dom 属性

    attr 设置普通的内置属性和自定义属性

    property 设置特殊的内置属性

  • 基于答案,选择相应的选项

d3.selectAll('label').attr('class', 'opt');

const answer = [0, 1];
d3.selectAll('input')
	.filter((ele, ind) => answer.includes(ind))
	.property('checked' ,true);

设置元素内容

  • text 文本内容
  • html html 内容
const title = d3.select('#title');
const titleText = "123";
const p = `
	<p></p>
`;
title.text(titleText);
title.html(p);

增删元素

  1. 添加 DOM 元素
    • append 在当前选择集的最后追加指定名称的子元素
    • insert 前置元素,可声明在哪个元素前添加
    • data + join 以完全覆盖的方式添加元素
    • data + enter + append 以差值的方式添加元素
<body>
<h1 id="title">沁园春·长沙</h1>
<article id="cont"></article>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
    const arr=[
        '作者:毛主席',
        '独立寒秋,湘江北去,橘子洲头。',
        '看万山红遍,层林尽染;漫江碧透,百舸争流。',
        '鹰击长空,鱼翔浅底,万类霜天竞自由。',
        '怅寥廓,问苍茫大地,谁主沉浮?',
        '携来百侣曾游。忆往昔峥嵘岁月稠。',
        '恰同学少年,风华正茂;书生意气,挥斥方遒。',
        '指点江山,激扬文字,粪土当年万户侯。',
        '曾记否,到中流击水,浪遏飞舟?'
    ]
    const cont=d3.select('#cont');
    cont.append('p')
    	.text('独立寒秋,湘江北去,橘子洲头。---------')
    	.attr('id', 'p1');
    cont.insert('p', '#p1')
    	.text('123');
    cont.selectAll('p')
    	.data(arr)
    	.join('p')
    	.text(d=>d)
    const.selectAll('p')
    	.data(arr)
    	.enter()
    	.append('p')
    	.text(d=>d)
    
</script>
</body>
  1. 删除 DOM 元素
d3.select('p').remove();
// 删除第一个元素

svg

svg 绘图方式

  • svg 根节点
    1. version svg 版本号
    2. xmlns 命名空间 http://www/w3.org/2000/svg
    3. width | height svg 画布尺寸,如 400 ,400
    4. viewBox 视图框,总会在画布中完全显示,其中的图形会基于视图框的宽高按比例显示
<svg version="1.2"
     xmlns="http://www.w3.org/2000/svg"
     width="400"
     height="400"
     viewBox="0 0 400 400"
>
    <!-- viewBox 中的远点位置是根据自身宽高和总外 svg 层宽高等比在视图的位置,而内置元素的宽高就得在这个比例基础上换算 -->
	<rect width="400" height="400" fill="red" />
</svg>
  • svg 图形绘制
    • rect
    • circle
    • path
      • 直线
      • 圆弧
      • 二次贝塞尔曲线
      • 三次贝塞尔曲线
      • 圆形容器
<!--
    rect: x | y 矩形左上角点位置
      width | height 尺寸
-->
<rect x="100"
      y="100"
      width="400"
      height="400"
      fill="red"
/>

<!--
    circle: cx | cy 圆心位置
        r 半径
-->
<circle cx="300"
        cy="300"
        r="100"
        fill="red"
/>

<!--
    path路径: d 路径形状,大写字母为绝对点定位,小写字母为相对点定位
		 起点: M | m
		 下一个点: L | l
		 圆弧: A | a
		 二次贝塞尔曲线: Q | q, T | t
		 三次贝塞尔曲线: C | c, S | s
		 闭合路径 Z | z
-->


<path d="
        M
        300 300
        L
        500 300
        "
      fill="none"
      stroke="#000"
      stroke-width="10"
      stroke-dasharray="30 10"
/>

<!--
    圆弧: A | a ,如 M 300 300 A 150 200 0 0 0 500 300
		       rx, ry 半径
		       x-axis-rotation x 轴方向[0, 360]
		       large-arc-flag 是否显示最大弧
		       sweep-flag 弧在哪一侧 0 | 1
		       x y 结束点
-->
<path d="
         M 300 300
         A 150 200 20 1 0 500 300
        "
      fill="none"
      stroke="#000"
      stroke-width="5"
/>

<!-- 放在一个容器内,把共有属性抽离在容器标签内 -->
<g fill="none" stroke="#000">
        <path d="
                M
                100 400
                L
                200 400
                200 200
                300 200

                400 400
                500 400
            "

              stroke-width="2"
              stroke-dasharray="3"
        />
        <path d="
                M
                100 400
                C
                200 400
                200 200
                300 200
                S
                400 400
                500 400

                600 200
                700 200
            "
              stroke-width="10"
        />
    </g>

二次贝塞尔曲线

<!--
    二次贝塞尔曲线: Q | q, 如 M 100 400 Q 200 100 300 400
		                cpx1 cpy1 控制点
		                x y 结束点

    二次贝塞尔曲线延续: T | t, 如 T 500 400
    
-->
<path d="
		M
		100 400
		L
		200 100
         300 400
	"
	fill="none"
	stroke="#000"
	stroke-width="2"
	stroke-dasharray="3"
/>
<path d="
		M
		100 400
		Q
		200 100
		300 400
		T
		500 400
		700 400
	"
	fill="none"
	stroke="#000"
	stroke-width="5"
/>
<circle cx="100" cy="400" r="10" fill="red" />
<circle cx="200" cy="100" r="10" fill="red" />
<circle cx="300" cy="400" r="10" fill="red" />
<circle cx="500" cy="400" r="10" fill="red" />
<circle cx="700" cy="400" r="10" fill="red" />

二次贝塞尔曲线解析.jpg

延续的周期只需要描绘一个点,(上一个点x值 + 周期 T,上一个点y值)

三次贝塞尔曲线

<!--
    三次贝塞尔曲线:C | c, 如 M 100 400 C 200 400 200 200 300 200
                        cpx1 cpy1 控制点1
                        cpx2 cpy2 控制点2
                        x y 结束点

    三次贝塞尔曲线的延续: S | s, 如 s 400 400 500 400
-->
<path d="
		M
		100 400
		L
		200 400
         200 200
         300 200
         
         400 400
         500 400
         600 200
         700 200
	"
	fill="none"
	stroke="#000"
	stroke-width="2"
	stroke-dasharray="3"
/>
<path d="
		M
		100 400
		C
		200 400
         200 200
         300 200
         S
         400 400
         500 400
         600 200
         700 200
	"
	fill="none"
	stroke="#000"
	stroke-width="5"
/>
<circle cx="100" cy="400" r="10" fill="red" />
<circle cx="200" cy="400" r="10" fill="red" />
<circle cx="200" cy="200" r="10" fill="red" />
<circle cx="300" cy="200" r="10" fill="red" />
<circle cx="400" cy="400" r="10" fill="blue" />
<circle cx="500" cy="400" r="10" fill="blue" />
<circle cx="600" cy="200" r="10" fill="blue" />
<circle cx="700" cy="200" r="10" fill="blue" />

三次贝塞尔曲线解析.jpg

周期的规律:接下来的四个点画出下一个半周期图形

svg样式

  • 着色区域
    • fill 填充区域,默认 blank
    • stroke 描边区域,默认 none
<circle cx="300"
        cy="300"
        r="200"
        fill="red"
/>
  • 着色方式

    • 纯色
    <circle cx="300"
            cy="300"
            r="200"
            fill="red"
    />
    
    • 渐变色

      • 线性渐变
      <!--linearGradient 线性渐变
                  gradientTransform 渐变变换 https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function
                      rotate(a) 旋转[0,360]
                      translate(x,y) 移动
                      scale(sx,sy) 缩放
              stop 颜色节点
                  offset 偏移量[0,1]
                  stop-color 颜色
              渐变色的赋值 url(id)
      -->
      <defs>
      	<linearGradient id="gr"
                          gradientTransform="rotate(30)"
          >
              <stop offset="0" stop-color="#fff"/>
              <stop offset="0.5" stop-color="#00acae"/>
              <stop offset="1" stop-color="#fff" />
          </linearGradient>
      </defs>
      <circle cx="300"
              cy="300"
              r="200"
              fill="url(#gr)"
      />
      
      • 径向渐变
      <!--radialGradient 径向渐变
                  stop 颜色节点
                      offset 偏移量[0,1]
                      stop-color 颜色
      -->
      <defs>
      	<radialGrandient id="gr">
          	<stop offset="0" stop-color="#fff"/>
              <stop offset="0.5" stop-color="#00acec"/>
              <stop offset="1" stop-color="#fff"/>
          </radialGrandient>
      </defs>
      <circle cx="300"
              cy="300"
              r="200"
              fill="url(#gr)"
      />
      
    • 纹理

      • 注意插入图片边距(无边距图后宽高也必须和 viewBox 宽高一致)
      <!--pattern 纹理着色
                  id
                  viewBox 视图框
                  width height 纹理元素的尺寸,建议使用百分百
              polygon 多边形
                  points 点位,如 0,0 20,50 0,100 50,80 100,100 80,50 100,0 50,20
              image
                  href 图片地址
                  x|y 位置
                  widht|height 宽高
      -->
      
      <polygon points="0,0 20,50 0,100 50,80 100,100 80,50 100,0 50,20"/>
      <defs>
      	<pattern width="30%"
                   height="30%"
                   viewBox="0 0 100 100"
                   id="pt"
          >
              <image href="./images/rose/jpg"
                     width="100"
                     height="100"
              />
          </pattern>
      </defs>
      <circle cx="300"
              cy="300"
              r="200"
              fill="url(#pt)"
      />
      
      
      

svg封装

<body>
<div id="main"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
    const main = d3.select('#main');
    function Draw(dom) {
        return function(shape, option) {
            const obj = dom.append(shape);
            for(let [key, val] of Object.entries(option)) {
                obj.attr(key, val);
            }
            return obj;
        }
    }
    
    const svg = Draw(main)('svg', {
        version: 1.2,
        xmlns: 'https://www.w3.org/2000/svg',
        width: '100%',
        height: '100%',
        viewBox: '-400 -400 800 800'
    })
    // 建立以 svg 为容器的绘图方法
    const draw = Draw(svg);
    draw('rect', {
        x:-200
    })
</script>
</body>
	const rectCom={
        x:-200,
        y:0,
        width:400,
        height:200,
    }
	draw('rect',{
        ...rectCom,
        fill:red
    });

使用上述封装方法绘制一个机器人

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>机器人-d3</title>
    <style>
        html{height: 100%}
        body{height: 100%;margin: 0;}
        #main{
            width: 100%;
            height: 100%;
            background-color: antiquewhite;
        }
    </style>
</head>
<body>
<div id="main"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
    /*获取main 容器*/
    const main=d3.select('#main')

    /*在main容器中建立svg,并设置其相关属性*/
    /*const svg=main.append('svg')
        .attr('version',1.2)
        .attr('xmlns','http://www.w3.org/2000/svg')
        .attr('width','100%')
        .attr('height','100%')
        .attr('viewBox','-400 -400 800 800')*/

    const svg=Draw(main)('svg',{
        version:1.2,
        xmlns:'http://www.w3.org/2000/svg',
        width:'100%',
        height:'100%',
        viewBox:'-400 -400 800 800'
    })

    /*建立绘图函数Draw,以dom容器为参,返回绘图方法
    *   绘图方法以图形shape和配置项option为参
    *       用append方法向容器中添加图形obj
    *       用for……of……的方法,遍历Object.entries(配置项)的键值对
    *       用attr方法设置图形obj的属性
    *       返回图形obj
    * */
    function Draw(dom) {
        return function(shape,option){
            const obj=dom.append(shape)
            for(let [key,val] of Object.entries(option)){
                obj.attr(key,val)
            }
            return obj
        }
    }

    /*建立以svg为容器的绘图方法draw*/
    const draw=Draw(svg)

    /*绘制图形……*/
    draw('rect',{
        x:-200,
        y:0,
        width: 400,
        height: 200,
        fill:'red'
    })
    draw('rect',{
        x:-200,
        y:0,
        width: 400,
        height: 200,
        fill:'none',
        stroke:'#000',
        'stroke-width':40,
    })
    draw('rect',{
        x:-200,
        y:50,
        width: 400,
        height: 60,
        fill:'antiquewhite'
    })
    draw('path',{
        d:`
            M
            -100 150
            L
            100 150
        `,
        fill:'none',
        stroke:'#000',
        'stroke-width':40,
    })
    draw('circle',{
        cx:-100,
        cy:80,
        r: 20,
        fill:'red'
    })
    draw('path',{
        d:`
            M
            80 90
            A
            20 20
            0
            0
            1
            120 90
        `,
        fill:'red'
    })
    draw('path',{
        d:`
            M
            -200 -200
            C
            -100 -200
            -100 0
            0 0
            S
            100 -200
            200 -200
        `,
        fill:'none',
        stroke:'#000',
        'stroke-width':40,
    })
</script>
</body>
</html>

机器人.jpg