Home > Software engineering >  Javascript to generate dynamic clip-path for svg
Javascript to generate dynamic clip-path for svg

Time:05-19

I am working with a svg element which is as follows

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>


<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">


    <rect  width="1280" height="720" fill="none" stroke="red"></rect>
    <rect  x="70" y="70" width="1160" height="600" fill="none" stroke="green"></rect>

    <g  style="transform: translate(70px, 70px);">
        <g >
            <g  fill="none" font-size="10" font-family="sans-serif">
                <g  opacity="1" transform="translate(0,411.7647058823529)">
                    <line stroke="currentColor" x2="1160"></line>
                    <text fill="currentColor" x="0" dy="0.32em">20%</text>
                </g>
                <g  opacity="1" transform="translate(0,223.52941176470583)">
                    <line stroke="currentColor" x2="1160"></line>
                    <text fill="currentColor" x="0" dy="0.32em">40%</text>
                </g>
                <g  opacity="1" transform="translate(0,35.29411764705883)">
                    <line stroke="currentColor" x2="1160"></line>
                    <text fill="currentColor" x="0" dy="0.32em">60%</text>
                </g>
            </g>
        </g>
    </g>
</svg>
</body>
<script type="text/javascript"></script>
</html>

With javascript, I intend to generate a svg which will show each yAxis grid line after the text. e.g. Intended

To programmatically generate this, my approach was to generate a clipPath with a rect that starts at x=text.x text.width and same y,width as the lines and height=text.height and have it apply on each line.

This is what I tried so far

const svgns = 'http://www.w3.org/2000/svg';

document.querySelectorAll('.tick>line').forEach(
    (a, i) => {
        const defs = document.createElementNS(svgns, 'defs');
        document.querySelector('svg').appendChild(defs);
        const clipPath = document.createElementNS(svgns, 'clipPath');
        clipPath.setAttribute('id', `clip${i}`);
        defs.appendChild(clipPath);
        const data = document.querySelectorAll('.tick>text');
        const rect = document.createElementNS(svgns, 'rect')
        rect.setAttribute('x', `${data[i].getBoundingClientRect().x data[i].getBoundingClientRect().width}`);
        rect.setAttribute('y', `${a.getBoundingClientRect().y}`);
        rect.setAttribute('height', `${data[i].getBoundingClientRect().height}`)
        rect.setAttribute('width', `${a.getBoundingClientRect().width}`)
        clipPath.appendChild(rect);
        a.style.setProperty('clip-path', `url(#clip${i})`)


    }
)
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>


<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">


    <rect  width="1280" height="720" fill="none" stroke="red"></rect>
    <rect  x="70" y="70" width="1160" height="600" fill="none" stroke="green"></rect>

    <g  style="transform: translate(70px, 70px);">
        <g >
            <g  fill="none" font-size="10" font-family="sans-serif">
                <g  opacity="1" transform="translate(0,411.7647058823529)">
                    <line stroke="currentColor" x2="1160"></line>
                    <text fill="currentColor" x="0" dy="0.32em">20%</text>
                </g>
                <g  opacity="1" transform="translate(0,223.52941176470583)">
                    <line stroke="currentColor" x2="1160"></line>
                    <text fill="currentColor" x="0" dy="0.32em">40%</text>
                </g>
                <g  opacity="1" transform="translate(0,35.29411764705883)">
                    <line stroke="currentColor" x2="1160"></line>
                    <text fill="currentColor" x="0" dy="0.32em">60%</text>
                </g>
            </g>
        </g>
    </g>
</svg>
</script>
</html>

I can't figure out what I am doing wrong in this. it does not return what I was intending. Also, is there a built-in method currentyl exists in javascript that does union/combine/intersect/subtract operations of svg elements?

CodePudding user response:

This worked for me

const svgns = 'http://www.w3.org/2000/svg';

document.querySelectorAll('.tick>line').forEach(
    (a,i)=>{
        const defs = document.createElementNS(svgns, 'defs');
        document.querySelector('svg').appendChild(defs);
        const clipPath = document.createElementNS(svgns, 'clipPath');
        clipPath.setAttribute('id',`clip${i}`);
        defs.appendChild(clipPath);
        const data = document.querySelectorAll('.tick>text');
        const rect = document.createElementNS(svgns, 'rect')
        rect.setAttribute('x', `${data[i].getComputedTextLength()}`);
        //rect.setAttribute('y', `${a.getBoundingClientRect().y}`);
        rect.setAttribute('height', `${data[i].getBBox().height}`)
        rect.setAttribute('width', `${a.x2.baseVal.value-a.x1.baseVal.value}`)
        clipPath.appendChild(rect);
        a.style.setProperty('clip-path',`url(#clip${i})`)

        
            }
        )

const svgns = 'http://www.w3.org/2000/svg';

    document.querySelectorAll('.tick>line').forEach(
        (a,i)=>{
            const defs = document.createElementNS(svgns, 'defs');
            document.querySelector('svg').appendChild(defs);
            const clipPath = document.createElementNS(svgns, 'clipPath');
            clipPath.setAttribute('id',`clip${i}`);
            defs.appendChild(clipPath);
            const data = document.querySelectorAll('.tick>text');
            const rect = document.createElementNS(svgns, 'rect')
            rect.setAttribute('x', `${data[i].getComputedTextLength()}`);
            //rect.setAttribute('y', `${a.getBoundingClientRect().y}`);
            rect.setAttribute('height', `${data[i].getBBox().height}`)
            rect.setAttribute('width', `${a.x2.baseVal.value-a.x1.baseVal.value}`)
            clipPath.appendChild(rect);
            a.style.setProperty('clip-path',`url(#clip${i})`)

            
                }
            )
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>


<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">


    <rect  width="1280" height="720" fill="none" stroke="red"></rect>
    <rect  x="70" y="70" width="1160" height="600" fill="none" stroke="green"></rect>

    <g  style="transform: translate(70px, 70px);">
        <g >
            <g  fill="none" font-size="10" font-family="sans-serif">
                <g  opacity="1" transform="translate(0,411.7647058823529)">
                    <line stroke="currentColor" x2="1160"></line>
                    <text fill="currentColor" x="0" dy="0.32em">20%</text>
                </g>
                <g  opacity="1" transform="translate(0,223.52941176470583)">
                    <line stroke="currentColor" x2="1160"></line>
                    <text fill="currentColor" x="0" dy="0.32em">40%</text>
                </g>
                <g  opacity="1" transform="translate(0,35.29411764705883)">
                    <line stroke="currentColor" x2="1160"></line>
                    <text fill="currentColor" x="0" dy="0.32em">60%</text>
                </g>
            </g>
        </g>
    </g>
</svg>
</body>

</html>

d3 equivalent

const clipRectData =d3.selectAll('.tick>text').nodes();
d3.select('svg')
        .selectAll('defs')
        .data(d3.selectAll('.tick>line'))
        .join('defs')
        .append('clipPath')
        .attr('id', (d, i) => { return `clip${i}` })
        .append('rect')
        .attr('x', (d, i) => {
            return `${clipRectData[i].getComputedTextLength()}`
        })
        .attr('height', (d, i) => {
            return `${clipRectData[i].getBBox().height}`
        })
        .attr('width', (d, i) => {
            return `${d.x2.baseVal.value-d.x1.baseVal.value}`
        })

    d3.selectAll('.tick>line').style('clip-path', (d, i) => {
            return `url(#clip${i})`
        })
  • Related