d3 曲線區域填充


本篇以一個簡單的demo示范一下在d3中實現曲線的區域填充。

clip-path

clip-path:創建一個只有元素的部分區域可以顯示的剪切區域。顯示clip-path內部的區域,而外部的區域不可見。

區域填充也主要以clip-path為基礎來實現。

區域填充

1.1 先畫一條曲線


<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>

	<body>
		<div id="test-svg">
		</div>
	</body>
	<script src="https://d3js.org/d3.v5.js"></script>
	<script type="text/javascript" src="js/2D.js"></script>
	<script>
		window.onload = function() {
			
			// 數據
			var data = [{
				date: new Date(2019, 3, 24),
				value: 23.24
			}, {
				date: new Date(2019, 3, 25),
				value: 72.15
			}, {
				date: new Date(2019, 3, 26),
				value: 38.84
			}, {
				date: new Date(2019, 3, 27),
				value: 58.62
			}, {
				date: new Date(2019, 3, 30),
				value: 10.80
			}, {
				date: new Date(2019, 4, 1),
				value: 85.47
			}];
			
			var width = 800,
				height = 400,
				padding = {
					top: 40,
					right: 40,
					bottom: 40,
					left: 40
				};
				
			var colors = d3.schemeSet2;
			var svg = d3.select("#test-svg")
				.append('svg')
				.attr('width', width + 'px')
				.attr('height', height + 'px');
				
			// x軸:時間軸
			var xScale = d3.scaleTime()
				.domain(d3.extent(data, function(d) {
					return d.date;
				}))
				.range([padding.left, width - padding.right]);
				
			var xAxis = d3.axisBottom()
				.scale(xScale)
				.tickSize(10);
				
			svg.append('g')
				.call(xAxis)
				.attr("transform", "translate(0," + (height - padding.bottom) + ")")
				.selectAll("text")
				.attr("font-size", "10px")
				.attr("dx", "50px");

			var ymax = d3.max(data, function(d) {
				return d.value;
			});

			// y軸
			var yScale = d3.scaleLinear()
				.domain([0, ymax])
				.range([height - padding.bottom, padding.top]);
				
			var yAxis = d3.axisLeft()
				.scale(yScale)
				.ticks(10);
				
			svg.append('g')
				.call(yAxis)
				.attr("transform", "translate(" + padding.left + ",0)");
				
			var curveLine = d3.line()
				.x(function(d) {
					return xScale(d.date);
				})
				.y(function(d) {
					return yScale(d.value);
				})
				.curve(d3.curveCatmullRom.alpha(0.5));
				
			svg.append("path")
				.datum(data)
				.attr("fill", "none")
				.attr("stroke", "steelblue")
				.attr("stroke-width", 1.5)
				.attr("stroke-linejoin", "round")
				.attr("stroke-linecap", "round")
				.attr("d", curveLine);

				
		}
	</script>

</html>

2.2 添加一條水平的閾值線


// 水平閾值
			svg.append('line')
				.attr('x1', 0)
				.attr('y1', yScale(45))
				.attr('x2', width)
				.attr('y2', yScale(45))
				.attr('stroke', '#FFA354')
				.attr('stroke-width', 1.5)
				.attr('stroke-dasharray', '6,4');

3.3 填充閾值線上面部分



// 添加一個clipPath 
svg.append("clipPath")
				.attr("id", "clip-th")
				.append("rect")
				.attr("x", 0)
				.attr("y", padding.top)
				.attr("width", width)
				.attr("height", yScale(45) - yScale(ymax));

// 再次添加一條曲線並填充區域            
            svg.append("path")
				.datum(data)
				.attr("fill", "steelblue")
				.attr("fill-opacity", 0.5)
				.attr("stroke", "none")
				.attr("d", curveLine)
				.attr('clip-path', "url(#clip-th)");


  • path填充的時候會把首尾點連起來,並不是我們想要的效果。所以我們再首尾都添加一個點,來控制填充區域。

4.4 修改數據,重新繪制填充區域


data.unshift({
				date: new Date(2019, 3, 24),
				value: 0
			});
			data.push({
				date: new Date(2019, 4, 1),
				value: 0
			});
            
            svg.append("path")
				.datum(data)
				.attr("fill", "steelblue")
				.attr("fill-opacity", 0.5)
				.attr("stroke", "none")
				.attr("d", curveLine)
				.attr('clip-path', "url(#clip-th)");

  • 目的是達到了但是效果並不是很理想。由於額外的添加了兩個點生成的曲線和原曲線產生了偏差。個人覺得比較理想的做法是
    再添加一條紅線所示的正常直線path。然后將兩個直線和曲線組合起來進行填充。

5.5 再次修改數據,重新繪制


// 紅線所示直線數據
var data2 = [{
				date: new Date(2019, 3, 24),
				value: 23.24
			}, {
				date: new Date(2019, 3, 24),
				value: 0
			}, {
				date: new Date(2019, 4, 1),
				value: 0
			}, {
				date: new Date(2019, 4, 1),
				value: 85.47
			}];

// 添加直線生成器
var line = d3.line()
				.x(function(d) {
					return xScale(d.date);
				})
				.y(function(d) {
					return yScale(d.value);
				});


// 繪制兩個path
container.append("path")
				.datum(data)
				.attr("fill", "none")
				.attr("d", curveLine);
				
			container.append("path")
				.datum(data2)
				.attr("fill", "none")
				.attr("d", line);

// 將兩個path合並
var combined = "";
			container.selectAll("path")
				.each(function() {
					combined += d3.select(this).attr("d");
				});
				
			container.selectAll("path").remove();
			
                        // 繪制合並后的path
			container.append("path")
				.attr("stroke", "none")
				.attr("d", combined)
				.attr("fill", "steelblue")
				.attr("fill-opacity", 0.5)
				.attr("fill-rule", "evenodd")
				.attr('clip-path', "url(#clip-th)");

  • 我們就能看到完全的區域填充了。切記要使用fill-rule來約定填充的區域。

6.6 添加兩條垂直的閾值


// 垂直閾值線
			svg.append('line')
				.attr('x1', xScale(new Date(2019, 3, 26)))
				.attr('y1', yScale(ymax))
				.attr('x2', xScale(new Date(2019, 3, 26)))
				.attr('y2', yScale(0))
				.attr('stroke', '#FFA354')
				.attr('stroke-width', 1.5)
				.attr('stroke-dasharray', '6,4')

			svg.append('line')
				.attr('x1', xScale(new Date(2019, 3, 28)))
				.attr('y1', yScale(ymax))
				.attr('x2', xScale(new Date(2019, 3, 28)))
				.attr('y2', yScale(0))
				.attr('stroke', '#FFA354')
				.attr('stroke-width', 1.5)
				.attr('stroke-dasharray', '6,4')

// clip-path

svg.append("clipPath")
				.attr("id", "clip-th2")
				.append("rect")
				.attr("x", xScale(new Date(2019, 3, 26)))
				.attr("y", yScale(45))
				.attr("width", xScale(new Date(2019, 3, 28)) - xScale(new Date(2019, 3, 26)))
				.attr("height", yScale(0) - yScale(45));

container.append("path")
				.attr("stroke", "none")
				.attr("d", combined)
				.attr("fill", "yellowgreen")
				.attr("fill-opacity", 0.5)
				.attr("fill-rule", "evenodd")
				.attr('clip-path', "url(#clip-th2)");

  • 這下就大功告成了!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM