Create pointed hashing pattern in D3

I am creating a diagram in D3. One of the requirements is that I have a rectangle, which can be pointed to the left or right side, and this rectangle should have a hashed pattern within it. It’s easy to accomplish this with a fill pattern. The issue is that I need to be able to export to PowerPoint, which does not support these patterns.

Instead, I have created a function that will add the main rectangle shape in a group alongside a set of path strokes that represent the hashed pattern.

The only issue remaining is that the hashed pattern does not follow the pointed sides of the main shape. I need to mask them somehow.

I’m not sure of a good way to do this. I thought about some sort of linear interpolation that scales the X,Y coordinates of the path strokes when they reach the pointed sides, but I haven’t been able to get this to work.

Here is a JSFiddle: https://jsfiddle.net/srose/4wmfsx35/1/. So in this example, the green strokes should not appear outside of the red border of the main shape.

Does anyone know how to achieve this?

<!DOCTYPE html>

<!-- Add a svg area, empty -->
<svg id="area" height=500 width=500></svg>

<script>
  function calculateStripyRectD(
    x,
    y,
    width,
    height,
    stripeWidth,
    isPointedLeft,
    isPointedRight,
    pointWidth
  ) {
    const stripes = [];

    for (let yTop = 0; yTop < height + width; yTop += 5 * stripeWidth) {
      const yBottom = yTop + stripeWidth;

      let stripeTopStartX = yTop > height ? x + (yTop - height) : x;
      let stripeTopStartY = yTop > height ? y + height : y + yTop;
      let stripeTopEndX = yTop > width ? x + width : x + yTop;
      let stripeTopEndY = yTop > width ? y + yTop - width : y;

      let stripe2startX = yBottom > height ? x + (yBottom - height) : x;
      let stripe2startY = yBottom > height ? y + height : y + yBottom;
      let stripe2endX = yBottom > width ? x + width : x + yBottom;
      let stripe2endY = yBottom > width ? y + yBottom - width : y;

      if (isPointedRight) {
        /*  const distanceFromRight = width - stripeTopStartX;
         
         if (distanceFromRight < pointWidth) {
           break;
         } */
      }

      stripes.push(`M ${stripeTopStartX}, ${stripeTopStartY} L ${stripeTopEndX}, ${stripeTopEndY}
    L ${stripe2endX}, ${stripe2endY} L ${stripe2startX}, ${stripe2startY}
    L ${stripeTopStartX}, ${stripeTopStartY}
    `);
    }

    return stripes
      .join(' ')
  }

  function rectangleWithOptionalPoints(
    width,
    height,
    isPointedLeft,
    isPointedRight,
    isStriped,
  ) {
    const pointWidth = 20;
    const stripeWidth = 1;

    let leftX = 0;
    let rightX = width;

    if (isPointedLeft) {
      leftX += pointWidth;
    }

    if (isPointedRight) {
      rightX -= pointWidth;
    }

    const mainPath = `
    M ${leftX - (isPointedLeft ? pointWidth : 0)} ${height / 2}
    L ${leftX} 0
    L ${rightX} 0
    L ${rightX + (isPointedRight ? pointWidth : 0)} ${height / 2}
    L ${rightX} ${height}
    L ${leftX} ${height}
    Z
  `;

    const stripes = isStriped ?
      calculateStripyRectD(
        leftX - pointWidth,
        0,
        width,
        height,
        stripeWidth,
        isPointedLeft,
        isPointedRight,
        pointWidth
      ) :
      undefined;

    return {
      main: mainPath,
      stripes,
    };
  }

  const renderBarGroup = () => {
    const barGroup = svg.append('g')

    barGroup
      .append('path')
      .attr('d', () => {
        const paths = rectangleWithOptionalPoints(
          200,
          25,
          true,
          true,
          true,
        );
        return paths.stripes;
      })
      .style('stroke', 'green');

    barGroup
      .append('path')
      .attr('d', () => {
        const paths = rectangleWithOptionalPoints(
          200,
          25,
          true,
          true,
          true,
        );

        return paths.main;
      })
      .attr('fill', 'none')
      .style('stroke', 'red');
  };

  var svg = d3.select("#area")
  renderBarGroup();

</script>

Thanks!

You can apply a clip-path to the green lines. Here’s how the resulting SVG should look where you define a clipPath equal to your polygon:

<svg id="area" height="500" width="500">
  <defs>
    <clipPath id="polygonClip">
      <path d="
        M 0 12.5
        L 20 0
        L 180 0
        L 200 12.5
        L 180 25
        L 20 25
        Z
      "></path>
    </clipPath>
  </defs>   
    <g><path d="M 0, 0 L 0, 0
    L 1, 0 L 0, 1
    L 0, 0
     M 0, 5 L 5, 0
    L 6, 0 L 0, 6
    L 0, 5
     M 0, 10 L 10, 0
    L 11, 0 L 0, 11
    L 0, 10
     M 0, 15 L 15, 0
    L 16, 0 L 0, 16
    L 0, 15
     M 0, 20 L 20, 0
    L 21, 0 L 0, 21
    L 0, 20
     M 0, 25 L 25, 0
    L 26, 0 L 1, 25
    L 0, 25
     M 5, 25 L 30, 0
    L 31, 0 L 6, 25
    L 5, 25
     M 10, 25 L 35, 0
    L 36, 0 L 11, 25
    L 10, 25
     M 15, 25 L 40, 0
    L 41, 0 L 16, 25
    L 15, 25
     M 20, 25 L 45, 0
    L 46, 0 L 21, 25
    L 20, 25
     M 25, 25 L 50, 0
    L 51, 0 L 26, 25
    L 25, 25
     M 30, 25 L 55, 0
    L 56, 0 L 31, 25
    L 30, 25
     M 35, 25 L 60, 0
    L 61, 0 L 36, 25
    L 35, 25
     M 40, 25 L 65, 0
    L 66, 0 L 41, 25
    L 40, 25
     M 45, 25 L 70, 0
    L 71, 0 L 46, 25
    L 45, 25
     M 50, 25 L 75, 0
    L 76, 0 L 51, 25
    L 50, 25
     M 55, 25 L 80, 0
    L 81, 0 L 56, 25
    L 55, 25
     M 60, 25 L 85, 0
    L 86, 0 L 61, 25
    L 60, 25
     M 65, 25 L 90, 0
    L 91, 0 L 66, 25
    L 65, 25
     M 70, 25 L 95, 0
    L 96, 0 L 71, 25
    L 70, 25
     M 75, 25 L 100, 0
    L 101, 0 L 76, 25
    L 75, 25
     M 80, 25 L 105, 0
    L 106, 0 L 81, 25
    L 80, 25
     M 85, 25 L 110, 0
    L 111, 0 L 86, 25
    L 85, 25
     M 90, 25 L 115, 0
    L 116, 0 L 91, 25
    L 90, 25
     M 95, 25 L 120, 0
    L 121, 0 L 96, 25
    L 95, 25
     M 100, 25 L 125, 0
    L 126, 0 L 101, 25
    L 100, 25
     M 105, 25 L 130, 0
    L 131, 0 L 106, 25
    L 105, 25
     M 110, 25 L 135, 0
    L 136, 0 L 111, 25
    L 110, 25
     M 115, 25 L 140, 0
    L 141, 0 L 116, 25
    L 115, 25
     M 120, 25 L 145, 0
    L 146, 0 L 121, 25
    L 120, 25
     M 125, 25 L 150, 0
    L 151, 0 L 126, 25
    L 125, 25
     M 130, 25 L 155, 0
    L 156, 0 L 131, 25
    L 130, 25
     M 135, 25 L 160, 0
    L 161, 0 L 136, 25
    L 135, 25
     M 140, 25 L 165, 0
    L 166, 0 L 141, 25
    L 140, 25
     M 145, 25 L 170, 0
    L 171, 0 L 146, 25
    L 145, 25
     M 150, 25 L 175, 0
    L 176, 0 L 151, 25
    L 150, 25
     M 155, 25 L 180, 0
    L 181, 0 L 156, 25
    L 155, 25
     M 160, 25 L 185, 0
    L 186, 0 L 161, 25
    L 160, 25
     M 165, 25 L 190, 0
    L 191, 0 L 166, 25
    L 165, 25
     M 170, 25 L 195, 0
    L 196, 0 L 171, 25
    L 170, 25
     M 175, 25 L 200, 0
    L 200, 1 L 176, 25
    L 175, 25
     M 180, 25 L 200, 5
    L 200, 6 L 181, 25
    L 180, 25
     M 185, 25 L 200, 10
    L 200, 11 L 186, 25
    L 185, 25
     M 190, 25 L 200, 15
    L 200, 16 L 191, 25
    L 190, 25
     M 195, 25 L 200, 20
    L 200, 21 L 196, 25
    L 195, 25
    " style="stroke: green;clip-path: url(#polygonClip)"></path><path d="
    M 0 12.5
    L 20 0
    L 180 0
    L 200 12.5
    L 180 25
    L 20 25
    Z
  " fill="none" style="stroke: red;"></path></g></svg>

Leave a Comment