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>