I am working with a svg element & the initial markup is following
<!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" height="400" width="450">
<path class="line1" d="M 100 350 l 150 -300" stroke="red" stroke-width="3" fill="none" />
<path class="line2" d="M 250 50 l 150 300" stroke="blue" stroke-width="3" fill="none" />
<path class="line3" d="M 175 200 l 150 0" stroke="green" stroke-width="3" fill="none" />
<path class="line4" d="M 100 350 q 150 -300 300 0" stroke="magenta" stroke-width="5" fill="none" />
</svg>
</body>
</html>
I want to call getTotalLength on each path
and set that as CSS custom properties for each path
.
By using vanilla, I can do this
document.querySelectorAll("[class^='line']")
.forEach(
(a, i) => {
a.style.setProperty('--pathLength', a.getTotalLength());
}
);
which gives me
I was wondering, how can I replicate this in d3
. So far, I tried this which is doing the job but I am doing the same selection twice.
d3.selectAll("[class^='line']")
.style('--pathLength', (d, i) => {
const dataset = d3.selectAll("[class^='line']");
return `${dataset['_groups'][0][i].getTotalLength()}`
});
Is there a better way to achieve this?
The full code is following
<!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" height="400" width="450">
<path class="line1" d="M 100 350 l 150 -300" stroke="red" stroke-width="3" fill="none" />
<path class="line2" d="M 250 50 l 150 300" stroke="blue" stroke-width="3" fill="none" />
<path class="line3" d="M 175 200 l 150 0" stroke="green" stroke-width="3" fill="none" />
<path class="line4" d="M 100 350 q 150 -300 300 0" stroke="magenta" stroke-width="5" fill="none" />
</svg>
<script type="text/javascript">
<!--vanilla-->
document.querySelectorAll("[class^='line']")
.forEach(
(a, i) => {
a.style.setProperty('--pathLengthVanilla', a.getTotalLength());
}
);
<!--d3-->
d3.selectAll("[class^='line']")
.style('--pathLengthD3', (d, i) => {
const dataset = d3.selectAll("[class^='line']");
return `${dataset['_groups'][0][i].getTotalLength()}`
});
</script>
</body>
</html>
In D3, the selection.style
method takes a property name ("--pathLength"
) and a callback, which is invoked with the element as the value of this
, so you can return this.getTotalLength()
:
d3.selectAll("[class^='line']")
.style("--pathLength", function (d, i) {
return this.getTotalLength();
});
Note that, for the value of this
to be the element, you can’t use arrow functions , which don’t get their own this
. That is, you can’t write (d, i) => { ... }
, like you did in your example; you have to write function(d, i) { ... }
.
Here’s an example:
Smpa01 asks in the forum: I want to call getTotalLength on each path and set that as CSS custom properties for each path. By using vanilla, I can do this: I was wondering, how can I replicate this in d3. In D3, you can do something very similar. The...
1 Like
@tophtucker many thanks for this. However, I have a follow-up question. I am new to d3 and currently experimenting with d3 API.
I have conducted the following experiment to get more clarification on use of d
and this
<!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" height="400" width="450">
<path class="line1" d="M 100 350 l 150 -300" stroke="red" stroke-width="3" fill="none" />
<path class="line2" d="M 250 50 l 150 300" stroke="blue" stroke-width="3" fill="none" />
<path class="line3" d="M 175 200 l 150 0" stroke="green" stroke-width="3" fill="none" />
<path class="line4" d="M 100 350 q 150 -300 300 0" stroke="magenta" stroke-width="5" fill="none" />
</svg>
<script type="text/javascript">
/*vanilla*/
document.querySelectorAll("[class^='line']")
.forEach(
(a, i) => {
a.style.setProperty('--pathLengthVanilla', a.getTotalLength());
}
);
/*d3*/
d3.selectAll("[class^='line']")
.style('--pathLengthD3', (d, i) => {
const dataset = d3.selectAll("[class^='line']");
return `${dataset['_groups'][0][i].getTotalLength()}`
});
/*perform dynamic transform on the existing element by selecting first*/
d3.selectAll("[class^='line']").attr('transform', function(d, i) {
const attributeX = parseFloat(this.getAttribute('d').match(/(?<=M )\d+/gm)) / 10;
return `translate(${attributeX})`
})
/*create data driven circle and dynamic transform on the new element based on the dataset*/
d3.select('svg')
.selectAll('circle')
.data(d3.selectAll("[class^='line']"))
.enter()
.append('circle')
.attr('cx', '0')
.attr('cy', (d, i) => {
return '5'
})
.attr('r', '5')
.attr('transform', (d, i) => {
const attributeX = parseFloat(d.getAttribute('d').match(/(?<=M )\d+/gm)) / 10
return `translate(${attributeX})`
}
)
</script>
</body>
</html>
As you can notice, line 38 requires this
and line 55 requires d
Is it kindly possible for you to shed some light on this as to when to use this
and when to use
d
.
From this example, it is explanatory that this
is required for modifying existing elements and d
is used for the new data-driven element.
Since I am new to d3, I am not sure if this is the correct inference ? I would love to hear some explanation from you.
I can never keep this straight myself so I often myself using a console.log
to examine those things. For example, you might type:
.attr('some_attribute', function(a,b) {
console.log([a,b,this])
...
}
That way, you can simply examine the contents of those variables and decide how to act accordingly.
1 Like