本篇以一個簡單的demo示範一下在d3中實現曲線的區域填充。javascript
clip-path
:建立一個只有元素的部分區域能夠顯示的剪切區域。顯示clip-path內部的區域,而外部的區域不可見。html
區域填充也主要以clip-path
爲基礎來實現。java
<!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>
// 水平閾值 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');
// 添加一個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
填充的時候會把首尾點連起來,並非咱們想要的效果。因此咱們再首尾都添加一個點,來控制填充區域。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
。而後將兩個直線和曲線組合起來進行填充。// 紅線所示直線數據 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
來約定填充的區域。// 垂直閾值線 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)");