# d3教程

# 颜色变化

var ybRamp = d3.scaleLinear()
.interpolate(d3.interpolateLab)
.domain([0,maxValue]).range(["yellow", "blue"]);
var ybRamp = d3.scaleLinear()
.interpolate(d3.interpolateHcl)
.domain([0,maxValue]).range(["yellow", "blue"]);
var ybRamp = d3.scaleLinear()
.interpolate(d3.interpolateHsl)
.domain([0,maxValue]).range(["yellow", "blue"]);

# D3曲线变化

tweetLine.curve(d3.curveBasis)//尽量不接触圆点的曲线
retweetLine.curve(d3.curveStep)//直线水平或垂直的线段构成
favLine.curve(d3.curveCardinal)//过圆点的曲线
//另外默认的线段是连接每两个圆点的线段构成的!!!
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style>
			*{margin:0;padding:0}
			svg{
				width:800px;
				height:520px;
			}
		</style>
	</head>
	<body>
		<svg></svg>
	</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
d3.csv("./tweetdata.csv")
.then(data=>{
	const blue = "#5eaec5", green = "#92c463", orange = "#fe9a22"
	xScale = d3.scaleLinear().domain([1,10.5]).range([20,480])
	yScale = d3.scaleLinear().domain([0,35]).range([480,20])
	xAxis = d3.axisBottom()
	.scale(xScale)
	.tickSize(480)
	.tickValues([1,2,3,4,5,6,7,8,9,10])
	d3.select("svg").append("g").attr("id", "xAxisG").call(xAxis)
	yAxis = d3.axisRight()
	.scale(yScale)
	.ticks(10)
	.tickSize(480)
	d3.select("svg").append("g").attr("id", "yAxisG").call(yAxis)
	d3.select("svg").selectAll("circle.tweets")
	.data(data)
	.enter()
	.append("circle")
	.attr("class", "tweets")
	.attr("r", 5)
	.attr("cx", d => xScale(d.day))
	.attr("cy", d => yScale(d.tweets))
	.style("fill", blue)
	d3.select("svg").selectAll("circle.retweets")
	.data(data)
	.enter()
	.append("circle")
	.attr("class", "retweets")
	.attr("r", 5)
	.attr("cx", d => xScale(d.day))
	.attr("cy", d => yScale(d.retweets))
	.style("fill", green)
	d3.select("svg").selectAll("circle.favorites")
	.data(data)
	.enter()
	.append("circle")
	.attr("class", "favorites")
	.attr("r", 5)
	.attr("cx", d => xScale(d.day))
	.attr("cy", d => yScale(d.favorites))
	.style("fill", orange)
	
	const lambdaXScale = d => xScale(d.day)
		var tweetLine = d3.line()
		.x(lambdaXScale).curve(d3.curveBasis)
		.y(d => yScale(d.tweets))
		var retweetLine = d3.line()
		.x(lambdaXScale).curve(d3.curveStep)
		.y(d => yScale(d.retweets))
		var favLine = d3.line()
		.x(lambdaXScale).curve(d3.curveCardinal)
		.y(d => yScale(d.favorites))
		d3.select("svg")
		.append("path")
		.attr("d", tweetLine(data))
		.attr("fill", "none")
		.attr("stroke", blue)
		.attr("stroke-width", 2)
		d3.select("svg")
		.append("path")
		.attr("d", retweetLine(data))
		.attr("fill", "none")
		.attr("stroke", green)
		.attr("stroke-width", 2)
		d3.select("svg")
		.append("path")
		.attr("d", favLine(data))
		.attr("fill", "none")
		.attr("stroke", orange)
		.attr("stroke-width", 2)
})
.catch(err=>{console.log(err)})

</script>

day,tweets,retweets,favorites
1,1,2,5
2,6,11,3
3,3,0,1
4,5,2,6
5,10,29,16
6,4,22,10
7,3,14,1
8,5,7,7
9,1,35,22
10,4,16,15

# D3堆积图

d3.area 可以生成一个图形生成器,输入一个数组数据,将返回值传入 path 元素的 d 属性就可以渲染出对应的 area 图形。

<!DOCTYPE html>
<html>
  <body>
    <style>
      svg {
        border: 1px solid lightgrey;
      }

    </style>
    <script src="http://d3js.org/d3.v5.min.js"></script>
    <script type="text/javascript">
      const maxHeight = 400;
      const maxWidth = 600;
      const barWidth = 20;

      const colorArray = ['#38CCCB', '#0074D9', '#2FCC40', '#FEDC00', '#FF4036', 'lightgrey'];

      function singleArea() {
          const data = [
            {
              date: '2019-01-01',
              value: 1,
            },
            {
              date: '2019-01-02',
              value: 10,
            },
            {
              date: '2019-01-03',
              value: 35,
            },
          ];

        const xScale = d3.scaleTime()
          .domain(d3.extent(data, (d) => new Date(d.date)))
          .range([0, maxWidth]);
		
		console.log(d3.extent(data, (d) => new Date(d.date)))

        const yScale = d3.scaleLinear()
          .domain(d3.extent(data, (d) => d.value))
          .range([maxHeight, 0])

        const area = d3.area()
          .x((d) => xScale(new Date(d.date)))
          .y1((d) => yScale(d.value))
          .y0((d) => yScale(0))

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

        svg.append('g')
          .selectAll('path.area')
          .data([data])
          .enter()
          .append('path')
          .attr('class', 'area')
          .attr('d', area)
          .attr('fill', colorArray[0])
      }
      singleArea()
  

    </script>
  </body>
</html>
<!DOCTYPE html>
<html>
  <body>
    <style>
      svg {
        border: 1px solid lightgrey;
      }

    </style>
    <script src="http://d3js.org/d3.v5.min.js"></script>
    <script type="text/javascript">
      const maxHeight = 400;
      const maxWidth = 600;
      const barWidth = 20;
      const colorArray = ['#38CCCB', '#0074D9', '#2FCC40', '#FEDC00', '#FF4036', 'lightgrey'];
      function stackArea() {
        const stackData = [
          {
            year: 2017,
            quarter: 2,
            samsung: 0.229,
            apple: 0.118,
            huawei: 0.110,
            oppo: 0.08,
            xiaomi: 0.062,
            others: 0.401,
          },
          {
            year: 2017,
            quarter: 3,
            samsung: 0.221,
            apple: 0.124,
            huawei: 0.104,
            oppo: 0.081,
            xiaomi: 0.075,
            others: 0.396,
          },

          {
            year: 2017,
            quarter: 4,
            samsung: 0.189,
            apple: 0.196,
            huawei: 0.107,
            oppo: 0.069,
            xiaomi: 0.071,
            others: 0.368,
          },
          {
            year: 2018,
            quarter: 1,
            samsung: 0.235,
            apple: 0.157,
            huawei: 0.118,
            oppo: 0.074,
            xiaomi: 0.084,
            others: 0.332,
          },
        ]

        const stack = d3.stack()
          .keys(['apple', 'samsung', 'huawei', 'oppo', 'xiaomi', 'others']);

        const series = stack(stackData);

        const stackMin = (serie) => d3.min(serie, (d) => d[0]);
        const stackMax = (serie) => d3.max(serie, (d) => d[1]);
        const yScale = d3.scaleLinear()
          .domain([d3.min(series, stackMin), d3.max(series, stackMax)])
          .range([maxHeight, 0])

      const xAccessor = (d) => `${d.year}-${d.quarter}`;

      const xScale = d3.scalePoint()
        .domain(stackData.map(xAccessor))
        .range([0, maxWidth])

      const area = d3.area()
        .x((d) => xScale(xAccessor(d.data)))
        .y0((d) => yScale(d[0]))
        .y1((d) => yScale(d[1]))

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

      svg.append('g')
        .selectAll('path')
        .data(series)
        .enter()
        .append('path')
        .attr('d', area)
        .attr('fill', (d, i) => colorArray[i % colorArray.length])
      }

    
       stackArea()

    </script>
  </body>
</html>

# d3path

可以借助line创建线段,借助area创建面积,借助d属性展示

数据需要注意是创建线图 面积图的data是 二维数组 而创建饼图,条形图,散点图的则是一维数组

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Data Filter</title>
	<link rel="stylesheet" href="style.css">
   	<script src="https://d3js.org/d3.v4.min.js"></script>
</head>

<body>

<script type="text/javascript">
   var data = [ // <-C
       [
           {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}
       ],
       
     
   ];
   var margin=20
   var svg = d3.selectAll("body").append("svg").style("width",600).attr("height",500)
		   .style("border","1px solid grey").append("g")
		   .attr("transform",`translate(${margin},${margin})`)
   var xscale = d3.scaleLinear().domain([0,10]).range([margin,600-margin])
  var yscale = d3.scaleLinear().domain([0,10]).range([500-margin,margin])
   var line =d3.line()
				.x(d=>xscale(d.x))
				.y(d=>yscale(d.y))
				
				
	var area =d3.area().x((d)=>xscale(d.x)).y0(yscale(0)).y1(d=>yscale(d.y))
	
	
	
	svg.selectAll("path.path.line")
	       .data(data)
	   .enter()
	       .append("path")
		   .attr("class","path-line")
	       .attr("class", "line")            
	       .attr("d", function(d){return line(d);});
				
	svg.selectAll("path.path-area")
		.data(data)
		.enter()
		.append("path")
		.attr("fill","orange")
		.attr("class","path-area")
		.attr("d",function(d){
			return area(d)
		})
				
</script>


</body>

</html>

# mouse

<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
        html, body {
            height: 100%;
        }
        body {
            margin: 0;
        }
        svg {
            cursor: pointer;
            width: 100%;
            height: 100%;
        }
        circle {
            fill: none;
            stroke: steelblue;
        }
		div{
			height:0
		}
    </style>
<body>
	
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
    var r = 400;
    var svg = d3.select("body")
            .append("svg")
    var positionLabel = svg.append("text")
            .attr("x", 10)
            .attr("y", 30);
    svg.on("mousemove", function () { //<-A
        printPosition();
    });
            
    function printPosition() { //<-B
        var position = d3.mouse(svg.node()); //<-C
        positionLabel.text(position);
    }  
    svg.on("click", function () { //<-D
        for (var i = 1; i < 5; ++i) {
            var position = d3.mouse(svg.node());
			console.log(svg.node())
            var circle = svg.append("circle")
                    .attr("cx", position[0])
                    .attr("cy", position[1])
                    .attr("r", 0)
                    .style("stroke-width", 5 / (i))
                    .transition()
                        .delay(Math.pow(i, 2.5) * 50)
                        .duration(2000)
                        .ease(d3.easeQuadIn)
                    .attr("r", r)
                    .style("stroke-opacity", 0)
                    .on("end", function () {
                        d3.select(this).remove();
                    });
        }
    });
</script>

# 总结

  • d3选择得到的元素如svg svg.node()返回dom元素而svg.nodes()反回的是数组
  • d3.mouse(svg.node())得到坐标位置的x y
  • 另外还有触碰事件
  • touchstart:当触摸点被放在触摸屏上时。
  • touchmove:当触摸点在触摸屏上移动时。
  • touchend:当触摸点从触摸屏上拿开时。

# d3 zoom

<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
        html, body {
            height: 100%;
        }
        body {
            margin: 0;
        }
        svg {
            cursor: pointer;
            width: 100%;
            height: 100%;
        }
        circle {
            fill: none;
            stroke: steelblue;
        }
		div{
			height:0
		}
    </style>
<body>
	
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
    var width = 600, height = 350, r = 50;
    var data = [
        [width / 2 - r, height / 2 - r],
        [width / 2 - r, height / 2 + r],
        [width / 2 + r, height / 2 - r],
        [width / 2 + r, height / 2 + r]
    ];
    var svg = d3.select("body").append("svg")
            .attr("style", "1px solid black")
            .attr("width", width)
            .attr("height", height)
            .call( // <-A
                    d3.zoom() // <-B
                    .scaleExtent([0.1, 2]) // <-C
                    .on("zoom", zoomHandler) // <-D
            )
            .append("g");
		
    svg.selectAll("circle")
            .data(data)
            .enter().append("circle")
            .attr("r", r)
            .attr("transform", function (d) {
                return "translate(" + d + ")";
            });
    function zoomHandler() {
        var transform = d3.event.transform;
        svg.attr("transform", "translate("
            + transform.x + "," + transform.y
            + ")scale(" + transform.k + ")");
    }
</script>

# 总结

  • d3.zoom()缩放
  • scaleExtent控制放大缩小比例
  • on绑定zoom事件
  • transform.k 当前尺度的数字

# d3 drag

<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
        svg {
            border: 1px solid black;
        }
        
        circle {
            fill: #dc143c;
        }
    </style>
<body>
	
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
    var width = 960, height = 500, r = 50;
    var data = [
        [width / 2 - r, height / 2 - r],
        [width / 2 - r, height / 2 + r],
        [width / 2 + r, height / 2 - r],
        [width / 2 + r, height / 2 + r]
    ];
    var svg = d3.select("body").append("svg")
            .attr("width", width)
            .attr("height", height)
            .append("g");
    var drag = d3.drag() // <-A
            .on("drag", move);
    svg.selectAll("circle")
            .data(data)
            .enter().append("circle")
            .attr("r", r)
            .attr("transform", function (d) {
                return "translate(" + d + ")";
            })
            .call(drag); // <-A
    function move(d) {
        var x = d3.event.x, // <-C
            y = d3.event.y;
        if(inBoundaries(x, y))
            d3.select(this) 
                .attr("transform", function (d) { // <-D
                    return "translate(" + x + ", " + y + ")";
                });
    }
    
    function inBoundaries(x, y){
        return (x >= (0 + r) && x <= (width - r)) 
            && (y >= (0 + r) && y <= (height - r));
    }
</script>



# 总结

  • d3.drag 拖动
  • dragstart 拖拽手势开始出发
  • drag 拖拽元素时触发
  • dragend拖拽结束时触发

# D3的力导向图

# 力模型
d3.forceSimulation([nodes])

# simulation.force(name,[force])函数

//链接力
.force("link", d3.forceLink(config.links))
 // 万有引力
.force("charge", d3.forceManyBody().strength(-300))
// 用指定的x坐标和y坐标创建一个居中力。
.force("center", d3.forceCenter(config.width / 2, config.height / 2))
//碰撞作用力,为节点指定一个radius区域来防止节点重叠,设置碰撞力的强度,范围[0,1],
//默认为0.7。设置迭代次数,默认为1,迭代次数越多最终的布局效果越好,但是计算复杂度更高
.force("collide", d3.forceCollide(100).strength(0.2).iterations(5))
d3.forceSimulation()
		.velocityDecay(0.7)
		.alphaDecay(0)
		.force("collision", d3.forceCollide(r + 1.5).strength(1));
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
        svg {
            border: 1px solid black;
        }
        
        circle {
            fill: #dc143c;
        }
    </style>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
    var w = 1000, h = 500, r = 4.5,
        nodes = [],
        force = d3.forceSimulation()
                .velocityDecay(0.7)
                .alphaDecay(0)
                .force("collision", d3.forceCollide(r + 1.5).strength(1));
    var svg = d3.select("body")
			.append("svg")
            .attr("width", w)
            .attr("height", h);
    force.on("tick", function () {
        svg.selectAll("circle")
            .attr("cx", function (d) {return d.x;})
            .attr("cy", function (d) {return d.y;});
    });
    svg.on("mousemove", function () {
		console.log(this)
        var point = d3.mouse(this),
            node = {x: point[0], y: point[1]}; // <-A
        svg.append("circle")
                .data([node])
            .attr("class", "node")
            .attr("cx", function (d) {return d.x;})
            .attr("cy", function (d) {return d.y;})
            .attr("r", 1e-6)
        .transition()
            .attr("r", r)
        .transition()
            .delay(7000)
            .attr("r", 1e-6)
            .on("end", function () {
                nodes.shift(); // <-B
                force.nodes(nodes);
            })
            .remove();
        nodes.push(node); // <-C
        force.nodes(nodes);
    });
    function noForce(){
        force.force("charge", null);
        force.force("x", null);
        force.force("y", null);
        force.restart();
    }
    function repulsion(){
        force.force("charge", d3.forceManyBody().strength(-10));
        force.force("x", null);
        force.force("y", null);
        force.restart();
    }
    function gravity(){
        force.force("charge", d3.forceManyBody().strength(1));
        force.force("x", null);
        force.force("y", null);
        force.restart();
    }
    function positioningWithGravity(){
        force.force("charge", d3.forceManyBody().strength(0.5));
        force.force("x", d3.forceX(w / 2));
        force.force("y", d3.forceY(h / 2));
        force.restart();
    }
    function positioningWithRepulsion(){
        force.force("charge", d3.forceManyBody().strength(-20));
        force.force("x", d3.forceX(w / 2));
        force.force("y", d3.forceY(h / 2));
        force.restart();
    }
</script>

<div class="control-group">
    <button onclick="noForce()">
        No Force
    </button>
    <button onclick="repulsion()">
        Repulsion
    </button>
    <button onclick="gravity()">
        Gravity
    </button>
    <button onclick="positioningWithGravity()">
        Positioning with Gravity
    </button>
    <button onclick="positioningWithRepulsion()">
        Positioning with Repulsion
    </button>
</div>

</body>

# 总结

  • 创建模式 force = d3.forceSimulation()
  • 监听变化force.on("tick", function () {xxx})
  • 绑定力与点force.nodes(nodes)
  • strength 各个力的使用不一致
  • force.force("charge", d3.forceManyBody().strength(-10))在万有引力中正值吸引,负值排斥
  • nodes中的数据还可以配置其他参数
node = {x: point[0], y: point[1],
				vx: previousPoint?point[0]-previousPoint[0]:point[0],
                vy: previousPoint?point[1]-previousPoint[1]:point[1]};
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
	*{
		margin:0;padding:0
	}
         html, body {
            height: 100%;
        }
        body {
            margin: 0;
        }
        svg {
            width: 100%;
            height: 100%;
        }
        circle {
            fill: steelblue;
        }
        .line {
            stroke: grey;
        }
    </style>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
    var w = 1280, h = 540,
            r = 4.5, nodes = [], links = [];
    var force = d3.forceSimulation()
                    .velocityDecay(0.8)
                    .alphaDecay(0)
                    .force("charge", d3.forceManyBody().strength(-50).distanceMax(h / 4))
                    .force("collision", d3.forceCollide(r + 0.5).strength(1));
    var duration = 10000;
    var svg = d3.select("body")
            .append("svg")
                .attr("width", w)
                .attr("height", h);
    force.on("tick", function () {
        svg.selectAll("circle")
            .attr("cx", function (d) {return boundX(d.x);})
            .attr("cy", function (d) {return boundY(d.y);});
        svg.selectAll("line")
            .attr("x1", function (d) {return boundX(d.source.x);})
            .attr("y1", function (d) {return boundY(d.source.y);})
            .attr("x2", function (d) {return boundX(d.target.x);})
            .attr("y2", function (d) {return boundY(d.target.y);});
    });
    function boundX(x) {
        return x > (w - r) ? (w - r): (x > r ? x : r);
    }
    function boundY(y){
        return y > (h - r) ? (h - r) : (y > r ? y : r);
    }
    function offset() {
        return Math.random() * 100;
    }
    function createNodes(point) {
        var numberOfNodes = Math.round(Math.random() * 10);
        var newNodes = [];
        for (var i = 0; i < numberOfNodes; ++i) {
            newNodes.push({
                x: point[0] + offset(),
                y: point[1] + offset()
            });
        }
        newNodes.forEach(function(e){nodes.push(e)});
        return newNodes;
    }
    function createLinks(nodes) {
        var newLinks = [];
        for (var i = 0; i < nodes.length; ++i) { // <-A
            if(i == nodes.length - 1)
                newLinks.push(
                    {source: nodes[i], target: nodes[0]}
                );
            else
                newLinks.push(
                    {source: nodes[i], target: nodes[i + 1]}
                );
        }
        newLinks.forEach(function(e){links.push(e)});
        return newLinks;
    }
    svg.on("click", function () {
        var point = d3.mouse(this),
                newNodes = createNodes(point),
                newLinks = createLinks(newNodes);
        newNodes.forEach(function (node) {
            svg.append("circle")
                    .data([node])
                .attr("class", "node")
                .attr("cx", function (d) {return d.x;})
                .attr("cy", function (d) {return d.y;})
                .attr("r", 1e-6)
                    .call(d3.drag() // <-D
                            .on("start", dragStarted)
                            .on("drag", dragged)
                            .on("end", dragEnded))
                    .transition()
                .attr("r", 7)
                    .transition()
                    .delay(duration)
                .attr("r", 1e-6)
                .on("end", function () {nodes.shift();})
                .remove();
        });
        newLinks.forEach(function (link) {
            svg.append("line") // <-B
                    .data([link])
                .attr("class", "line")
                .attr("x1", function (d) {return d.source.x;})
                .attr("y1", function (d) {return d.source.y;})
                .attr("x2", function (d) {return d.target.x;})
                .attr("y2", function (d) {return d.target.y;})
                    .transition()
                    .delay(duration)
                .style("stroke-opacity", 1e-6)
                .on("end", function () {links.shift();})
                .remove();
        });
        force.nodes(nodes);
        force.force("link", d3.forceLink(links).strength(1).distance(20)); // <-C
        force.restart();
    });
    function dragStarted(d) {
        d.fx = d.x; // <-E
        d.fy = d.y;
    }
    function dragged(d) {
        d.fx = d3.event.x; // <-F
        d.fy = d3.event.y;
    }
    function dragEnded(d) {
        d.fx = null; // <-G
        d.fy = null;
    }
</script>



</body>

最后更新: 2/23/2021, 1:41:33 PM