I am trying to create svg paths with d3
and trying to understand how can I ask d3
to create paths based on separate data.
For example, here I have fixed data for path Value
and two separate sets as index1 and index2
. I want to create paths based on these, meaning I want to create 11 paths altogether, whereas the code below creates only 8 paths.
const width = 1280;
height = 720;
svg = d3.select('svg')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.attr('viewBox', `0 0 ${width} ${height}`)
rect = svg.append('rect')
.attr('x', '0')
.attr('y', '0')
.attr('width', `${width}`)
.attr('height', `${height}`)
.attr('fill', `#EFEFEF`)
//src
dataHorizontal = [
{ x: 0, y: 0 },
{ x: 1280, y: 0 }
];
pathVal = d3.line()
.x(d => d.x)
.y(d => d.y)
(dataHorizontal);
index1 = [1, 2, 3];
index2 = [4, 5, 6, 7, 8, 9, 10, 11]
multiplier = 60;
//based on index1
svg.selectAll('path')
.data(index1)
.join('path')
.attr('class', (d) => `ln${d}`)
.attr('d', pathVal)
.attr('stroke', () => { return `hsla(${Math.random() * 360} , ${((Math.random() Math.random()/2)*100).toFixed(2)}%, 50%, 1)`; })
.style('transform', (d, i) => { return `translateY(${d*multiplier}px)` })
.attr('stroke-width', '2')
//based on index2
svg.selectAll('path')
.data(index2)
.join('path')
.attr('class', (d) => `ln${d}`)
.attr('d', pathVal)
.attr('stroke', () => { return `hsla(${Math.random() * 360} , ${((Math.random() Math.random()/2)*100).toFixed(2)}%, 50%, 1)`; })
.style('transform', (d, i) => { return `translateY(${d*multiplier}px)` })
.attr('stroke-width', '2')
<!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>
<link rel="stylesheet" href="style.css"></link>
<div id="container" ></div>
<svg>
</svg>
<!--d3 script-->
<script src="prod.js"></script>
</body>
</html>
My best guess is, d3
is probably overwriting the first 3 paths from index1
when it starts working on path2
as the first 3 paths were already available in the DOM.
How can I ask d3
to ignore the first 3 paths that are available in the DOM and start from scratch for index2
? I don't want to combine index1 and index 2. What is a d3 way to reach the desired output?
I know I can do this, but is this the only way?
const width = 1280;
height = 720;
svg = d3.select('svg')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.attr('viewBox', `0 0 ${width} ${height}`)
rect = svg.append('rect')
.attr('x', '0')
.attr('y', '0')
.attr('width', `${width}`)
.attr('height', `${height}`)
.attr('fill', `#EFEFEF`)
//src
dataHorizontal = [
{ x: 0, y: 0 },
{ x: 1280, y: 0 }
];
pathVal = d3.line()
.x(d => d.x)
.y(d => d.y)
(dataHorizontal);
index1 = [1, 2, 3];
index2 = [4, 5, 6, 7, 8, 9, 10, 11]
multiplier = 60;
//based on index1
svg.selectAll('path')
.data(index1)
.join('path')
.attr('class', (d) => `ln${d}`)
.attr('d', pathVal)
.attr('stroke', () => { return `hsla(${Math.random() * 360} , ${((Math.random() Math.random()/2)*100).toFixed(2)}%, 50%, 1)`; })
.style('transform', (d, i) => { return `translateY(${d*multiplier}px)` })
.attr('stroke-width', '2')
//based on index2
index2.forEach(
(a) => {
svg.append('path')
.attr('class', () => `ln${a}`)
.attr('d', pathVal)
.attr('stroke', () => { return `hsla(${Math.random() * 360} , ${((Math.random() Math.random()/2)*100).toFixed(2)}%, 50%, 1)`; })
.style('transform', () => { return `translateY(${a*multiplier}px)` })
.attr('stroke-width', '2')
}
)
<!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>
<link rel="stylesheet" href="style.css"></link>
<div id="container" ></div>
<svg>
</svg>
<!--d3 script-->
<script src="prod.js"></script>
</body>
</html>
CodePudding user response:
It is important for you to understand what does selection.join() mean in d3. Read this post: https://observablehq.com/@d3/selection-join
It is explained well in above post-If the joining selection isn’t empty—as on subsequent iterations of the loop above— selection.join appends entering elements and removes exiting elements to match the data!
You pass index1 as data and when you pass index2 as another set of data, join will remove the exiting elements. And only for entering elements, it will create a path. In case you want to create lines for both index1 and index2, combine the data and draw with join.
const width = 1280;
height = 720;
svg = d3.select('svg')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.attr('viewBox', `0 0 ${width} ${height}`)
rect = svg.append('rect')
.attr('x', '0')
.attr('y', '0')
.attr('width', `${width}`)
.attr('height', `${height}`)
.attr('fill', `#EFEFEF`)
//src
dataHorizontal = [
{ x: 0, y: 0 },
{ x: 1280, y: 0 }
];
pathVal = d3.line()
.x(d => d.x)
.y(d => d.y)
(dataHorizontal);
index1 = [1, 2, 3];
index2 = [4, 5, 6, 7, 8, 9, 10, 11]
multiplier = 60;
//based on index2
svg.selectAll('path')
.data([...index1,...index2])
.join('path')
.attr('class', (d) => `ln${d}`)
.attr('d', pathVal)
.attr('stroke', () => { return `hsla(${Math.random() * 360} , ${((Math.random() Math.random()/2)*100).toFixed(2)}%, 50%, 1)`; })
.style('transform', (d, i) => { return `translateY(${d*multiplier}px)` })
.attr('stroke-width', '2')
<!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>
<link rel="stylesheet" href="style.css"></link>
<div id="container" ></div>
<svg>
</svg>
<!--d3 script-->
<script src="prod.js"></script>
</body>
</html>