为D3JS动画中具有子节点的节点定义初始坐标。

huangapple go评论72阅读模式
英文:

Defining initial coordinates for nodes with children in a D3JS animation

问题

我有SVG节点,我将其定义为包含一个<circle>和一个<text><g>元素。

这里是一个示例HTML:

<g class="nodes">
    <g>
        <circle r="63"></circle>
        <text class="true">Croatia</text>
    </g>
    (...)
</g>

我正在使用d3js(v6)对它们进行动画处理,以实现圆形包装效果,使用力来实现。一切都运行正常,我只是无法定义元素的初始坐标。元素的起始位置是右上角,我希望它们在动画开始时位于SVG区域的中心。

我尝试给我的节点<g>元素添加"x, y"属性,以及在动画开始之前使用transform: translate对它们进行转换,但都没有成功。

我还尝试在将它们转换为节点之前初始化每个对象的"x"和"y"值,如下所示:

arrayOfCountries.forEach((country) => {
    country["x"] = 300;
    country["y"] = 700;
});

var simulation = d3
  .forceSimulation()
  //添加节点
  .nodes(arrayOfCountries);

我已经创建了一个带有以下动画:

simulation.on("tick", tickActions);

并使用以下方式进行了动画处理:

function tickActions() {
    //更新g的变换:
    node.attr("transform", function (d) {
      return "translate(" + [d.x, d.y] + ")";
    });
}

有没有办法确保节点从中心开始向外进行动画处理?

这是我的代码概要(我正在使用Vue JS 3):

renderCountries() {
  // #graph3是我的主SVG
  var svg = d3.select("#graph3");

  //清除先前的地图(如果有的话)
  svg.selectAll("*").remove();
  //创建用于放置力导向图的位置
  let width = +svg.attr("width");
  let height = +svg.attr("height");

  let arrayOfCountries = this.euCountryList.values;

  arrayOfCountries.forEach((country) => {
    country["x"] = 300;
    country["y"] = 700;
  });

 var simulation = d3
   .forceSimulation()
   //添加节点
   .nodes(arrayOfCountries);

   //添加力
   //我们将为每个节点添加一个电荷
   //还要添加一个居中力
   simulation
     .force("charge_force", d3.forceManyBody().strength(-200))
     .force("center_force", d3.forceCenter(width / 2, height / 2))
     .force(
       "x",
       d3
         .forceX()
         .x(function (d) {
         let lon = parseFloat(d[2].replace(",", ".").replace(" ", ""));
         //向右移动20个单位,以避免负数
         let displayLon = (lon + 20) * 22;
              return displayLon;
            })
            .strength(5)
        )
        .force(
          "y",
          d3
            .forceY()
            .y(function (d) {
              let lat = parseFloat(d[1].replace(",", ".").replace(" ", ""));
              let displayLat = height - lat * 10;
              return displayLat;
            })
            .strength(5)
        )
        .force(
          "collide",
          d3
            .forceCollide()
            .strength(0.01)
            .radius(function (d) {
              if (d[14]) {
                let radius = parseFloat(
                  d[14].replace(",", ".").replace(" ", "")
                );
                return radius;
              } else {
                return 50;
              }
            })
            .iterations(35)
        ); //避免圆形重叠的力;

      //添加tick指令
      simulation.on("tick", tickActions);

(...)

//绘制圆圈
var node = svg
  .append("g")
  .attr("class", "nodes")
  .selectAll("circle")
  .data(arrayOfCountries)
  .enter()
  .append("g");

var circles = node
  .append("circle")
  .attr("r", function (d) {
    if (d[14]) {
      let radius = parseFloat(d[14].replace(",", ".").replace(" ", ""));
      //如果半径不是数字,则退出;可能是君主制国家的总统选举,例如西班牙。
      if (isNaN(radius)) {
        return 0;
      }
      if (radius > 35) {
        return radius / 1.1;
      } else {
        //定义最小半径
        return 35;
      }
    } else {
      return 0;
    }
  })
  .attr("fill", "black")
  .attr("filter", "url(#blurMe)")
  .attr("ref", function (d) {
    return d[0];
  })
  .attr("id", function (d) {
    return d[0] + "-euparliament";
  });

// eslint-disable-next-line no-unused-vars
var labels = node
  .append("text")
  .text(function (d) {
    if (d[14] && d[14] != "nodata" && d[14] != "—") {
      return d[3];
    }
  })
  .attr("class", function (d) {
    if (d[14]) {
      let abstentionism = parseFloat(
        d[14].replace(",", ".").replace(" ", "")
      );
      if (abstentionism) {
        if (abstentionism < 35) {
          return "small-country";
        }
      }
    }
    return true;
  })
  .attr("text-anchor", "middle")
  .attr("dominant-baseline", "middle")
  .attr("pointer-events", "none")
  .attr("y", 0)
  .attr("fill", "red");

function tickActions() {
  //更新g的变换:
  node.attr("transform", function (d) {
    return "translate(" + [d.x, d.y] + ")";
  });
}
}
英文:

I have SVG nodes that I defined as &lt;g&gt; elements that contain one &lt;circle&gt; and one &lt;text&gt; inside them.

Here's an examples HTML:

&lt;g class=&quot;nodes&quot;&gt;
&lt;g&gt;
&lt;circle r=&quot;63&quot;&gt;&lt;/circle&gt;
&lt;text class=&quot;true&quot;&gt;Croatia&lt;/text&gt;
&lt;/g&gt;
(...)

I am animating them in d3js (v6) as to achieve a circular packing effect, using forces. All is working well, I just am not at all able to define the initial coordinates for my elements. The elements starting position is top right, and I would like them to be in the center of my SVG area when the animation starts.

I have tried giving my node &lt;g&gt; elements "x, y" attributes, as well as transform: translate them before the animation starts, to no avail.

I also tried initializing each object "x" and "y" values, before turning them into nodes, as so:

arrayOfCountries.forEach((country) =&gt; {
country[&quot;x&quot;] = 300;
country[&quot;y&quot;] = 700;
});
var simulation = d3
.forceSimulation()
//add nodes
.nodes(arrayOfCountries);

I have created an animations with:

simulation.on(&quot;tick&quot;, tickActions);

and animated it using:

function tickActions() {
//update g transform:
node.attr(&quot;transform&quot;, function (d) {
return &quot;translate(&quot; + [d.x, d.y] + &quot;)&quot;;
});
}

Any idea how I could make sure the nodes start animating from center outwards?

Here's an outline of my code (I'm using Vue JS 3):

renderCountries() {
// #graph3 is my main SVG
var svg = d3.select(&quot;#graph3&quot;);
// Clear the previous map, if any
svg.selectAll(&quot;*&quot;).remove();
// create somewhere to put the force directed graph
let width = +svg.attr(&quot;width&quot;);
let height = +svg.attr(&quot;height&quot;);
let arrayOfCountries = this.euCountryList.values;
arrayOfCountries.forEach((country) =&gt; {
country[&quot;x&quot;] = 300;
country[&quot;y&quot;] = 700;
});
var simulation = d3
.forceSimulation()
//add nodes
.nodes(arrayOfCountries);
// add forces
// we&#39;re going to add a charge to each node
// also going to add a centering force
simulation
.force(&quot;charge_force&quot;, d3.forceManyBody().strength(-200))
.force(&quot;center_force&quot;, d3.forceCenter(width / 2, height / 2))
.force(
&quot;x&quot;,
d3
.forceX()
.x(function (d) {
let lon = parseFloat(d[2].replace(&quot;,&quot;, &quot;.&quot;).replace(&quot; &quot;, &quot;&quot;));
// shift 20 units so as to avoid negative numbers
let displayLon = (lon + 20) * 22;
return displayLon;
})
.strength(5)
)
.force(
&quot;y&quot;,
d3
.forceY()
.y(function (d) {
let lat = parseFloat(d[1].replace(&quot;,&quot;, &quot;.&quot;).replace(&quot; &quot;, &quot;&quot;));
let displayLat = height - lat * 10;
// console.log(&quot;LAT: &quot; + displayLat);
return displayLat;
})
.strength(5)
)
.force(
&quot;collide&quot;,
d3
.forceCollide()
.strength(0.01)
.radius(function (d) {
if (d[14]) {
let radius = parseFloat(
d[14].replace(&quot;,&quot;, &quot;.&quot;).replace(&quot; &quot;, &quot;&quot;)
);
return radius;
} else {
return 50;
}
})
.iterations(35)
); // Force that avoids circle overlapping;
// add tick instructions
simulation.on(&quot;tick&quot;, tickActions);
(...)
// draw circles
var node = svg
.append(&quot;g&quot;)
.attr(&quot;class&quot;, &quot;nodes&quot;)
.selectAll(&quot;circle&quot;)
.data(arrayOfCountries)
.enter()
.append(&quot;g&quot;);
var circles = node
.append(&quot;circle&quot;)
.attr(&quot;r&quot;, function (d) {
if (d[14]) {
let radius = parseFloat(d[14].replace(&quot;,&quot;, &quot;.&quot;).replace(&quot; &quot;, &quot;&quot;));
// Bail if radius is Not A Number; possibly a presedential election in a monarchy, eg, Spain.
if (isNaN(radius)) {
return 0;
}
if (radius &gt; 35) {
return radius / 1.1;
} else {
// define a minimum radius
return 35;
}
// No data for abstention in country. Hide country in map.
} else {
return 0;
}
})
.attr(&quot;fill&quot;, &quot;black&quot;)
.attr(&quot;filter&quot;, &quot;url(#blurMe)&quot;)
.attr(&quot;ref&quot;, function (d) {
return d[0];
})
// Access country&#39;s caption using Vue template Refs
.attr(&quot;id&quot;, function (d) {
return d[0] + &quot;-euparliament&quot;;
});
// eslint-disable-next-line no-unused-vars
var labels = node
.append(&quot;text&quot;)
.text(function (d) {
if (d[14] &amp;&amp; d[14] != &quot;nodata&quot; &amp;&amp; d[14] != &quot;—&quot;) {
return d[3];
}
})
.attr(&quot;class&quot;, function (d) {
if (d[14]) {
let abstentionism = parseFloat(
d[14].replace(&quot;,&quot;, &quot;.&quot;).replace(&quot; &quot;, &quot;&quot;)
);
if (abstentionism) {
if (abstentionism &lt; 35) {
return &quot;small-country&quot;;
// return short name
}
}
}
return true;
})
.attr(&quot;text-anchor&quot;, &quot;middle&quot;)
.attr(&quot;dominant-baseline&quot;, &quot;middle&quot;)
.attr(&quot;pointer-events&quot;, &quot;none&quot;)
// .attr(&quot;x&quot;, function (d) {
//   return -(d.abstentionism / 2);
// })
.attr(&quot;y&quot;, 0)
.attr(&quot;fill&quot;, &quot;red&quot;);
function tickActions() {
//update g transform:
node.attr(&quot;transform&quot;, function (d) {
return &quot;translate(&quot; + [d.x, d.y] + &quot;)&quot;;
});
}
}

答案1

得分: 1

你说过

> 我尝试过给予"x, y"属性

但不太清楚你在哪里做了这个。通常,节点应该是一个对象列表。力模拟会重置这些对象的x/y属性,但如果愿意,你可以将它们设置为一些初始位置。

通常,我会随机设置初始位置,而不是所有节点的默认左上角位置。在SVG中以这种方式创建n个节点,宽度为w,高度为h的代码可能如下所示:

let nodes = d3.range(n).map((i) => ({
  id: i,
  x: d3.randomUniform(0, w)(),
  y: d3.randomUniform(0, h)()
}));

如果你更喜欢从中间开始节点,只需这样做

x: w/2,
y: h/2,

如果你喜欢,你可以在此Observable笔记本中查看它的实际效果。

英文:

You say that

> I have tried giving "x, y" attributes

but it's not exactly clear where you've done that.

Typically, the nodes should be a list of objects. The force simulation will reset any x/y properties of those objects, but you can set them to some initial position if we like.

Often, I set the initial positions randomly, rather than the default upper left position for all nodes. The code to create n nodes inside an SVG with width w and height h in this fashion might look something like so:

  let nodes = d3.range(n).map((i) =&gt; ({
id: i,
x: d3.randomUniform(0, w)(),
y: d3.randomUniform(0, h)()
}));

If you prefer to start the nodes all from the middle, you just do

x: w/2,
y: h/2,

You can see this in action in this Observable notebook, if you like.

huangapple
  • 本文由 发表于 2023年6月6日 00:37:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76408426.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定