How to draw SVG circles along a curved line in d3 js?

I am coding a software in which I draw several svg circles next to each other, which the user can adjust the position by changing the coordinates of the line. The desired output is

enter image description here

Right now, the circles are in the wrong space, causing the circles to move to the left.

enter image description here

code

function curve(val){
        var w = d3.select("#new_row_1").attr("width");

        var 
        numLines = 1,
        lineSpacing = 18,
        parabDepth = -30;

        var row_spacing = 18;

        if(val == 0){
            parabDepth = -18;
        }
        else if(val == 1){
            parabDepth = 0;
        }
        else if(val == 2){
            parabDepth = 20;
        }
        else if(val == 3){
            parabDepth = 30;
        }
        else if(val == 4){
            parabDepth = 40;
        }
        else if(val == 5){
            parabDepth = 50;
            // row_spacing = 18.41;
        }
        else if(val == 6){
            // row_spacing = 18.5;
            parabDepth = 60;
        }
        else if(val == 7){
            // row_spacing = 18.6;
            parabDepth = 70;
        }
        else if(val == 8){
            // row_spacing = 18.62;
            parabDepth = 80;
        }
        else if(val == 9){
            parabDepth = 90;
        }
        else if(val == 10){
            parabDepth = 100;
        }
        else if(val == 11){
            parabDepth = 110;
        }
        else if(val == 12){
            parabDepth = 120;
        }
        else if(val == 13){
            parabDepth = 130;
        }
        else if(val == 14){
            parabDepth = 140;
        }
        else if(val == 15){
            parabDepth = 150;
            row_spacing = 20;
        }

        width = w ;
        
        var curveData = [];

        curveData.push([0,0]);
        curveData.push([width/3 * 1, lineSpacing + parabDepth ]);
        curveData.push([width/3 * 2, lineSpacing + parabDepth ]);
        curveData.push([width/3 * 3, 1]);

        var line = d3.line()
            .x(function(d) {
              return d[0];
            })
            .y(function(d) {
              return d[1] + 8;
            })
            .curve(d3.curveCardinal);

        var svg = d3.select("#new_row_1").attr("height", (numLines * lineSpacing) + lineSpacing + parabDepth + row_spacing).attr("width", width);

        var g = svg.selectAll(".line")
            .data(d3.range(numLines))
            .enter()
            .append("g")
            .attr("class", "line")
            .attr("transform", function(d){
              return "translate(7," + (d*lineSpacing) + ")";
            });
        
        var path = g.append("path")
            .attr("d", line(curveData))
            .style("fill", "none")
            .style("stroke", "pink")
            .style("stroke-width","4")
            .each(function(){
                var g = d3.select(this.parentNode),
                    self = d3.select(this),
                    pathLength = width;
                  
                g.selectAll("circle")
                    .data(d3.range(1, width, row_spacing))
                    .enter()
                    .append("circle")
                    .attr("transform", (d,i) => {
                        var p = this.getPointAtLength(d);
                        return "translate(" + p.x + "," + p.y + ")";
                    })
                    .attr("r", 7)
                    .style("fill", function(d,i){
                        if(i == quant_col2 - 1){
                            return "red";
                        }else if(i == quant_col2/2){
                            return "yellow";
                        }
                        else if(i == quant_col2 - 2){
                            return "green";
                        }
                        else{
                            return "white";
                        }
                    })
                    .attr("stroke","black")
                    .attr("stroke-width","1");
            });
}

setTimeout(function(){
    curve(1)
},2000)
      

<script src="https://d3js.org/d3.v4.min.js"></script>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<svg width="252" height="18" id="new_row_1" class="new_row" style="top: 150px; left: 302px; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"><g><circle cx="8" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="3" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">1</text></g><g><circle cx="26" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="21" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">2</text></g><g><circle cx="44" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="39" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">3</text></g><g><circle cx="62" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="57" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">4</text></g><g><circle cx="80" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="75" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">5</text></g><g><circle cx="98" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="93" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">6</text></g><g><circle cx="116" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="111" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">7</text></g><g><circle cx="134" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="129" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">8</text></g><g><circle cx="152" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="147" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">9</text></g><g><circle cx="170" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="165" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">10</text></g><g><circle cx="188" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="183" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">11</text></g><g><circle cx="206" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="201" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">12</text></g><g><circle cx="224" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="219" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">13</text></g><g><circle cx="242" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="237" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">14</text><g class="label" style="display: none;"><rect x="126" y="1" width="14" height="14" style="fill: black;"></rect><text dx="130" dy="11" style="font-size: 9px; font-weight: 400; fill: white;">A</text></g></g></svg>
      

Run codeHide result


The value will be controlled by the user, that is, it will change dynamically. I want to distribute them evenly along the line. How can I do that?

+3


source to share


2 answers


You use getPointAtLength()

to find the X, Y coordinates of a point at a distance along the curve. Surely wrong. You want to find the Y coordinate for a given X.

There is no built-in SVG function for this. You will have to calculate this yourself. To do this, you need a polynomial form of the Bezier equation, which is not trivial.



Since you are only doing a parabola, you are better off avoiding Bézier curves and just calculate your curve with the usual simple quadratic equation (ax ^ 2 + bx + c).

Also: if you decide to stick with Bézier curves, then you should really use Quadratic Bézier and not cbic. Quadratic beziers have the property that they always form a section of a parabola.

+2


source


Instead of using, width

you should use getTotalLength()

for string size:

pathLength = this.getTotalLength();

      

Here's a demo with this change:



function curve(val) {
  var w = d3.select("#new_row_1").attr("width");

  var
    numLines = 1,
    lineSpacing = 18,
    parabDepth = -30;

  var row_spacing = 18;

  if (val == 0) {
    parabDepth = -18;
  } else if (val == 1) {
    parabDepth = 0;
  } else if (val == 15) {
    parabDepth = 150;
    row_spacing = 20;
  } else {
    parabDepth = val * 10;
  }

  width = w;

  var curveData = [];

  curveData.push([0, 0]);
  curveData.push([width / 3 * 1, lineSpacing + parabDepth]);
  curveData.push([width / 3 * 2, lineSpacing + parabDepth]);
  curveData.push([width / 3 * 3 - 8, - 8]);

  var line = d3.line()
    .x(function(d) {
      return d[0];
    })
    .y(function(d) {
      return d[1];
    })
    .curve(d3.curveCardinal);

  var svg = d3.select("#new_row_1").attr("height", (numLines * lineSpacing) + lineSpacing + parabDepth + row_spacing).attr("width", width);

  var g = svg.selectAll(".line")
    .data(d3.range(numLines))
    .enter()
    .append("g")
    .attr("class", "line")
    .attr("transform", function(d) {
      return "translate(7," + (d * lineSpacing + 7) + ")";
    });

  var path = g.append("path")
    .attr("d", line(curveData))
    .style("fill", "none")
    .style("stroke", "pink")
    .style("stroke-width", "4")
    .each(function() {
      var g = d3.select(this.parentNode),
        self = d3.select(this),
        pathLength = this.getTotalLength();

      g.selectAll("circle")
        .data(d3.range(1, pathLength, row_spacing))
        .enter()
        .append("circle")
        .attr("transform", (d, i) => {
          var p = this.getPointAtLength(d);
          return "translate(" + p.x + "," + p.y + ")";
        })
        .attr("r", 7)
        .style("fill", "darkslategray")
        .attr("stroke", "black")
        .attr("stroke-width", "1");
    });
}

setTimeout(function() {
  curve(5)
}, 500)
      

<script src="https://d3js.org/d3.v4.min.js"></script>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<svg width="252" height="18" id="new_row_1" class="new_row" style="top: 150px; left: 302px; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"><g><circle cx="8" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="3" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">1</text></g><g><circle cx="26" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="21" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">2</text></g><g><circle cx="44" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="39" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">3</text></g><g><circle cx="62" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="57" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">4</text></g><g><circle cx="80" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="75" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">5</text></g><g><circle cx="98" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="93" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">6</text></g><g><circle cx="116" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="111" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">7</text></g><g><circle cx="134" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="129" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">8</text></g><g><circle cx="152" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="147" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">9</text></g><g><circle cx="170" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="165" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">10</text></g><g><circle cx="188" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="183" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">11</text></g><g><circle cx="206" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="201" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">12</text></g><g><circle cx="224" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="219" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">13</text></g><g><circle cx="242" cy="8" r="7" style="fill: white; stroke: black; stroke-width: 1;"></circle><text dx="237" dy="11" class="label" style="font-size: 10px; font-weight: 500; display: none;">14</text><g class="label" style="display: none;"><rect x="126" y="1" width="14" height="14" style="fill: black;"></rect><text dx="130" dy="11" style="font-size: 9px; font-weight: 400; fill: white;">A</text></g></g></svg>
      

Run codeHide result


Alternatively, if you want to use width

, you shouldn't use getPointAtLength()

, as Lobe explains in his answer , because in Euclidean geometry, a straight line is shorter than any other geometric shape going from point A to point B.

Also, you have two problems: 1. There are many magic numbers all over the world, and 2. you can develop better mathematics for getting an arc. I think you should get rid of this code and write a completely new, different approach.

+1


source







All Articles