【问题标题】:How to fill inner area between multiple SVG paths?如何填充多个 SVG 路径之间的内部区域?
【发布时间】:2016-12-28 22:21:52
【问题描述】:

我使用 d3js (v4),我想填充由多个路径定义的特定区域。

这是我的形状:

这是我的代码:

var width = 400,
  height = 350;

var svg = d3.select('#svgContainer')
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .style('background-color', 'white');

var shape = {
  leftEdge: [],
  topCurve: [],
  supportJunction: [],
  bottomCurve: [],
  centralSupport: [],
  width: 0
};

/* Set path data */
var startX = 200,
  startY = 80,
  centralSupportW = 10,
  centralSupportH = 25;

shape.centralSupport.push({
  x: startX - centralSupportW,
  y: startY - centralSupportH
});
shape.centralSupport.push({
  x: startX + centralSupportW,
  y: startY - centralSupportH
});
shape.centralSupport.push({
  x: startX + centralSupportW,
  y: startY + centralSupportH
});
shape.centralSupport.push({
  x: startX - centralSupportW,
  y: startY + centralSupportH
});

var shapeW = 80,
  shapeH = 60,
  curve = 40,
  intensity = 6;

shape.leftEdge.push({
  x: startX - shapeW + curve,
  y: startY + shapeH,
  id: 1
});
shape.leftEdge.push({
  x: startX - shapeW + curve / intensity,
  y: startY + shapeH,
  id: 2
});
shape.leftEdge.push({
  x: startX - shapeW,
  y: startY,
  id: 3
});
shape.leftEdge.push({
  x: startX - shapeW + curve / intensity,
  y: startY - shapeH,
  id: 4
});
shape.leftEdge.push({
  x: startX - shapeW + curve,
  y: startY - shapeH,
  id: 5
});

var topCurveIntensity = 10;
var centralPosition = 5;

shape.topCurve.push({
  x: shape.leftEdge[shape.leftEdge.length - 1].x,
  y: shape.leftEdge[shape.leftEdge.length - 1].y,
  id: 6
});
shape.topCurve.push({
  x: shape.leftEdge[shape.leftEdge.length - 1].x - topCurveIntensity,
  y: shape.centralSupport[0].y + centralPosition,
  id: 7
});
shape.topCurve.push({
  x: shape.centralSupport[0].x,
  y: shape.centralSupport[0].y + centralPosition,
  id: 8
});

shape.supportJunction.push({
  x: shape.centralSupport[0].x,
  y: shape.centralSupport[0].y + centralPosition,
  id: 9
});
shape.supportJunction.push({
  x: shape.centralSupport[shape.centralSupport.length - 1].x,
  y: shape.centralSupport[shape.centralSupport.length - 1].y - centralPosition,
  id: 10
});

shape.bottomCurve.push({
  x: shape.centralSupport[shape.centralSupport.length - 1].x,
  y: shape.centralSupport[shape.centralSupport.length - 1].y - centralPosition,
  id: 11
});

shape.bottomCurve.push({
  x: shape.leftEdge[0].x - topCurveIntensity,
  y: shape.centralSupport[shape.centralSupport.length - 1].y - centralPosition,
  id: 12
});

shape.bottomCurve.push({
  x: shape.leftEdge[0].x,
  y: shape.leftEdge[0].y,
  id: 13
});
/*  */


/* draw paths */
var regularLine = d3.line()
  .x(function(d) {
    return d.x;
  })
  .y(function(d) {
    return d.y;
  });

var curvedLine = d3.line()
  .curve(d3.curveBundle)
  .x(function(d) {
    return d.x;
  })
  .y(function(d) {
    return d.y;
  });

var closedLine = d3.line()
  .curve(d3.curveLinearClosed)
  .x(function(d) {
    return d.x;
  })
  .y(function(d) {
    return d.y;
  });

/*
svg.append('path')
  .datum(shape.centralSupport)
  .attr('d', closedLine)
  .style('fill', 'none')
  .style('stroke', 'black')
  .style('stroke-width', '3px');
*/

svg.append('path')
  .datum(shape.leftEdge)
  .attr('d', curvedLine)
  .style('fill', 'none')
  .style('stroke', '#A0A3A5')
  .style('stroke-width', '3px');

svg.append('path')
  .datum(shape.topCurve)
  .attr('d', curvedLine)
  .style('fill', 'none')
  .style('stroke', '#A0A3A5')
  .style('stroke-width', '3px');

svg.append('path')
  .datum(shape.bottomCurve)
  .attr('d', curvedLine)
  .style('fill', 'none')
  .style('stroke', '#A0A3A5')
  .style('stroke-width', '3px');

svg.append('path')
  .datum(shape.supportJunction)
  .attr('d', regularLine)
  .style('fill', 'none')
  .style('stroke', '#A0A3A5')
  .style('stroke-width', '3px');

var data = '';
svg.selectAll('path')
  .each(function() {
    data += d3.select(this).attr('d');
  });
svg.append('path')
  .attr('d', data)
  .style('fill', 'lightgray')
  .style('stroke', 'black')
  .style('stroke-width', '1px')
  .style('fill-rule', 'evenodd');
/* */
/* draw points used */
svg.selectAll('circles')
  .data(shape.leftEdge)
  .enter()
  .append('circle')
  .attr('cx', function(d) {
    return d.x;
  })
  .attr('cy', function(d) {
    return d.y;
  })
  .attr('r', 3)
  .style('fill', 'black');


svg.selectAll('circles')
  .data(shape.topCurve)
  .enter()
  .append('circle')
  .attr('cx', function(d) {
    return d.x;
  })
  .attr('cy', function(d) {
    return d.y;
  })
  .attr('r', 3)
  .style('fill', 'blue');

svg.selectAll('circles')
  .data(shape.bottomCurve)
  .enter()
  .append('circle')
  .attr('cx', function(d) {
    return d.x;
  })
  .attr('cy', function(d) {
    return d.y;
  })
  .attr('r', 3)
  .style('fill', 'red');
/* */

/* draw labels */
svg.selectAll('circles')
  .data(shape.leftEdge)
  .enter()
  .append('text')
  .attr('x', function(d) {
    return d.x - 16;
  })
  .attr('y', function(d) {
    return d.y + 10;
  })
  .attr('dy', 1)
  .text(function(d) {
    return d.id;
  });

svg.selectAll('circles')
  .data(shape.topCurve)
  .enter()
  .append('text')
  .attr('x', function(d) {
    return d.x;
  })
  .attr('y', function(d) {
    return d.y - 8;
  })
  .attr('dy', 1)
  .text(function(d) {
    return d.id;
  })
  .style('fill', 'blue');


svg.selectAll('circles')
  .data(shape.supportJunction)
  .enter()
  .append('text')
  .attr('x', function(d) {
    return d.x + 16;
  })
  .attr('y', function(d) {
    return d.y - 8;
  })
  .attr('dy', 1)
  .text(function(d) {
    return d.id;
  })
  .style('fill', 'purple');

svg.selectAll('circles')
  .data(shape.bottomCurve)
  .enter()
  .append('text')
  .attr('x', function(d) {
    return d.x - 16;
  })
  .attr('y', function(d) {
    return d.y - 8;
  })
  .attr('dy', 1)
  .text(function(d) {
    return d.id;
  })
  .style('fill', 'red');
/* */
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="svgContainer"></div>

注意:这个 sn-p 使用一些随机值来生成形状(以反映我真正需要的)。

每条路径都由一组坐标定义。他们每个人都使用不同的曲线或直线。

这是单个路径的代码:

g.append('path')
  .datum(foobar)
  .attr('d', d3.line()
    .curve(d3.curveBundle)
    .x(function (d) { return d.x; })
    .y(function (d) { return d.y; }))
  .style('fill', 'none')
  .style('stroke', 'purple')
  .style('stroke-width', '3px');

我尝试连接每个路径的数据,但填充没有正确完成:

var data = '';
svg.selectAll('path')
  .each(function () { data += d3.select(this).attr('d'); });
svg.append('path')
  .attr('d', data)
  .style('fill', 'red')
  .style('stroke', 'black')
  .style('stroke-width', '3px');

在所有用于绘制路径的点的形状下方(每条路径一种颜色)。其中一些在路径之间共享(1 13、5 6、8 9、10 11)

您知道如何填充这些路径的包含(即内部部分)吗?

【问题讨论】:

标签: d3.js svg


【解决方案1】:

这个想法在于将子路径的d 属性连接成一个:

这里是演示:

var width = 400,
    height = 350;

var svg = d3.select('#svgContainer')
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .style('background-color', 'white');

var shape = {
  leftEdge: [],
  topCurve: [],
  supportJunction: [],
  bottomCurve: [],
  centralSupport: [],
  width: 0
};

/* Set path data */
var startX = 200,
  startY = 80,
  centralSupportW = 10,
  centralSupportH = 25;

shape.centralSupport.push({
  x: startX - centralSupportW,
  y: startY - centralSupportH
});
shape.centralSupport.push({
  x: startX + centralSupportW,
  y: startY - centralSupportH
});
shape.centralSupport.push({
  x: startX + centralSupportW,
  y: startY + centralSupportH
});
shape.centralSupport.push({
  x: startX - centralSupportW,
  y: startY + centralSupportH
});

var shapeW = 80,
  shapeH = 60,
  curve = 40,
  intensity = 6;

shape.leftEdge.push({
  x: startX - shapeW + curve,
  y: startY + shapeH,
  id: 1
});
shape.leftEdge.push({
  x: startX - shapeW + curve / intensity,
  y: startY + shapeH,
  id: 2
});
shape.leftEdge.push({
  x: startX - shapeW,
  y: startY,
  id: 3
});
shape.leftEdge.push({
  x: startX - shapeW + curve / intensity,
  y: startY - shapeH,
  id: 4
});
shape.leftEdge.push({
  x: startX - shapeW + curve,
  y: startY - shapeH,
  id: 5
});

var topCurveIntensity = 10;
var centralPosition = 5;

shape.topCurve.push({
  x: shape.leftEdge[shape.leftEdge.length - 1].x,
  y: shape.leftEdge[shape.leftEdge.length - 1].y,
  id: 6
});
shape.topCurve.push({
  x: shape.leftEdge[shape.leftEdge.length - 1].x - topCurveIntensity,
  y: shape.centralSupport[0].y + centralPosition,
  id: 7
});
shape.topCurve.push({
  x: shape.centralSupport[0].x,
  y: shape.centralSupport[0].y + centralPosition,
  id: 8
});

shape.supportJunction.push({
  x: shape.centralSupport[0].x,
  y: shape.centralSupport[0].y + centralPosition,
  id: 9
});
shape.supportJunction.push({
  x: shape.centralSupport[shape.centralSupport.length - 1].x,
  y: shape.centralSupport[shape.centralSupport.length - 1].y - centralPosition,
  id: 10
});

shape.bottomCurve.push({
  x: shape.centralSupport[shape.centralSupport.length - 1].x,
  y: shape.centralSupport[shape.centralSupport.length - 1].y - centralPosition,
  id: 11
});

shape.bottomCurve.push({
  x: shape.leftEdge[0].x - topCurveIntensity,
  y: shape.centralSupport[shape.centralSupport.length - 1].y - centralPosition,
  id: 12
});

shape.bottomCurve.push({
  x: shape.leftEdge[0].x,
  y: shape.leftEdge[0].y,
  id: 13
});

/* draw paths */
var regularLine = d3.line()
  .x(function(d) { return d.x; })
  .y(function(d) { return d.y; });

var curvedLine = d3.line()
  .curve(d3.curveBundle)
  .x(function(d) { return d.x; })
  .y(function(d) { return d.y; });

var closedLine = d3.line()
  .curve(d3.curveLinearClosed)
  .x(function(d) { return d.x; })
  .y(function(d) { return d.y; });

svg.append('path')
  .datum(shape.leftEdge)
  .attr('d', curvedLine)
  .attr("id", "left_edge")
  .style('fill', 'none')
  .style('stroke', 'grey')
  .style('stroke-width', '2px');

svg.append('path')
  .datum(shape.topCurve)
  .attr('d', curvedLine)
  .attr("id", "top_curve")
  .style('fill', 'none')
  .style('stroke', 'grey')
  .style('stroke-width', '2px');

svg.append('path')
  .datum(shape.bottomCurve)
  .attr('d', curvedLine)
  .attr("id", "bottom_curve")
  .style('fill', 'none')
  .style('stroke', 'grey')
  .style('stroke-width', '2px');

svg.append('path')
  .datum(shape.supportJunction)
  .attr('d', regularLine)
  .attr("id", "support_junction")
  .style('fill', 'none')
  .style('stroke', 'grey')
  .style('stroke-width', '1px');

var leftEdge = svg.select("#left_edge")
var topCurve = svg.select("#top_curve");
var supportJunction = svg.select("#support_junction");
var curvedLine = svg.select("#bottom_curve");

// Let's merge all paths together by replacing left edge's d attribute (path)
// with the concatenation of the different sub-paths:
leftEdge
  .attr(
    "d",
    [
      leftEdge.attr("d"),
      topCurve.attr("d").replace("M160,20", ""),
      supportJunction.attr("d"),
      curvedLine.attr("d").replace("M190,100", "")
    ].join(" ")
  )
  .attr("id", "full_shape") // let's rename this shape (as it's not the left edge anymore)
  .style("fill", "lightgray"); // and let's finally fill the shape (our goal!)

// Let's remove the initial sub-sections of the shape as they are not needed anymore:
topCurve.exit().remove();
curvedLine.exit().remove();
supportJunction.exit().remove();

/* draw points used */
svg.selectAll('circles')
  .data(shape.leftEdge)
  .enter()
  .append('circle')
  .attr('cx', function(d) { return d.x; })
  .attr('cy', function(d) { return d.y; })
  .attr('r', 3)
  .style('fill', 'black');

svg.selectAll('circles')
  .data(shape.topCurve)
  .enter()
  .append('circle')
  .attr('cx', function(d) { return d.x; })
  .attr('cy', function(d) { return d.y; })
  .attr('r', 3)
  .style('fill', 'blue');

svg.selectAll('circles')
  .data(shape.bottomCurve)
  .enter()
  .append('circle')
  .attr('cx', function(d) { return d.x; })
  .attr('cy', function(d) { return d.y; })
  .attr('r', 3)
  .style('fill', 'red');

/* draw labels */
svg.selectAll('circles')
  .data(shape.leftEdge)
  .enter()
  .append('text')
  .attr('x', function(d) { return d.x - 16; })
  .attr('y', function(d) { return d.y + 10; })
  .attr('dy', 1)
  .text(function(d) { return d.id; });

svg.selectAll('circles')
  .data(shape.topCurve)
  .enter()
  .append('text')
  .attr('x', function(d) { return d.x; })
  .attr('y', function(d) { return d.y - 8; })
  .attr('dy', 1)
  .text(function(d) { return d.id; })
  .style('fill', 'blue');

svg.selectAll('circles')
  .data(shape.supportJunction)
  .enter()
  .append('text')
  .attr('x', function(d) { return d.x + 16; })
  .attr('y', function(d) { return d.y - 8; })
  .attr('dy', 1)
  .text(function(d) { return d.id; })
  .style('fill', 'purple');

svg.selectAll('circles')
  .data(shape.bottomCurve)
  .enter()
  .append('text')
  .attr('x', function(d) { return d.x - 16; })
  .attr('y', function(d) { return d.y - 8; })
  .attr('dy', 1)
  .text(function(d) { return d.id; })
  .style('fill', 'red');
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="svgContainer"></div>

这里是感兴趣的部分:

var leftEdge = svg.select("#left_edge");
var topCurve = svg.select("#top_curve");
var supportJunction = svg.select("#support_junction");
var curvedLine = svg.select("#bottom_curve");

// Let's merge all paths together by replacing left edge's d attribute (path)
// with the concatenation of the different sub-paths:
leftEdge
  .attr(
    "d",
    leftEdge.attr("d") + " " +
    topCurve.attr("d").replace("M160,20", "") + " " +
    supportJunction.attr("d") + " " +
    curvedLine.attr("d").replace("M190,100", "")
  )
  .attr("id", "full_shape") // let's rename this shape (as it's not the left edge anymore)
  .style("fill", "lightgray"); // and let's finally fill the shape (our goal!)

// Let's remove the initial sub-sections of the shape as they are not needed anymore:
topCurve.exit().remove();
curvedLine.exit().remove();
supportJunction.exit().remove();

让我们详细说明一下我们实际在做什么:

在创建子路径(.attr("id", "#left_edge"))时为每个子路径提供一个 id 后,我们可以轻松地选择它们并检索它们由 d3 生成的d 属性:

svg.select("#left_edge").attr("d")
svg.select("#top_curve").attr("d")
svg.select("#support_junction").attr("d")
svg.select("#bottom_curve").attr("d")

这给了我们这 4 个子路径:

M160,140L155.2777777777778,139.25C150.55555555555557,138.5,141.11111111111111,137,135.44444444444446,127C129.7777777777778,117,127.8888888888889,98.5,127.88888888888891,80C127.8888888888889,61.5,129.7777777777778,43,135.44444444444446,33C141.11111111111111,23,150.55555555555557,21.5,155.2777777777778,20.75L160,20
M160,20L158.95833333333334,26.166666666666668C157.91666666666666,32.333333333333336,155.83333333333334,44.666666666666664,160.83333333333334,51.333333333333336C165.83333333333334,58,177.91666666666666,59,183.95833333333334,59.5L190,60
M190,60L190,100
M190,100L183.95833333333334,100.5C177.91666666666666,101,165.83333333333334,102,160.83333333333334,108.66666666666667C155.83333333333334,115.33333333333333,157.91666666666666,127.66666666666667,158.95833333333334,133.83333333333334L160,140

有了这些 svg 子路径,我们现在可以创建一个新路径,将这 4 个子路径与" " 连接起来:

var wholeShapePath =
  [
    leftEdge.attr("d"),
    topCurve.attr("d").replace("M160,20", ""),
    supportJunction.attr("d"),
    curvedLine.attr("d").replace("M190,100", "")
  ].join(" ");

请注意我如何从子路径的开头删除“moveTo”M 命令(例如 M160,20),因为它具有启动新子路径(这就是为什么你的连接不起作用)。

有了这个代表整个形状的新路径,我们现在可以通过修改其中一个子路径来应用它以使用整个形状的路径(顺便填充内部区域!):

svg.select("#left_edge")
  .attr("d", wholeShapePath)
  .style("fill", "lightgray");

不要忘记丢弃其他不再有用的子路径:

topCurve.exit().remove();
curvedLine.exit().remove();
supportJunction.exit().remove();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-19
    • 1970-01-01
    • 2019-04-05
    • 2019-09-14
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 2019-10-20
    相关资源
    最近更新 更多