Skip to content

ngminhtrung/d3-force

Repository files navigation

d3-force

Module này sử dụng phương pháp "velocity Verlet" để mô phỏng lực tương tác giữa các phần tử. Mô phỏng này được đơn giản hóa: nó giả định bước thời gian không đổi Δt = 1 cho mỗi thay đổi, và đơn vị khối lượng không đổi m = 1 cho tất cả các phần tử. Kết quả là: một lực F tác động lên một phần tử bằng với gia tốc không đổi a nhân với quãng thời gian Δt, and can be simulated simply by adding to the particle’s velocity, which is then added to the particle’s position.

In the domain of information visualization, physical simulations are useful for studying networks and hierarchies!

Force Dragging IIIForce-Directed Tree

You can also simulate circles (disks) with collision, such as for bubble charts or beeswarm plots:

Collision DetectionBeeswarm

You can even use it as a rudimentary physics engine, say to simulate cloth:

Force-Directed Lattice

To use this module, create a simulation for an array of nodes, and compose the desired forces. Then listen for tick events to render the nodes as they update in your preferred graphics system, such as Canvas or SVG.

Installing

If you use NPM, npm install d3-force. Otherwise, download the latest release. You can also load directly from d3js.org, either as a standalone library or as part of D3 4.0. AMD, CommonJS, and vanilla environments are supported. In vanilla, a d3_force global is exported:

<script src="https://d3js.org/d3-collection.v1.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-quadtree.v1.min.js"></script>
<script src="https://d3js.org/d3-timer.v1.min.js"></script>
<script src="https://d3js.org/d3-force.v1.min.js"></script>
<script>

var simulation = d3.forceSimulation(nodes);

</script>

Try d3-force in your browser.

API Reference

Simulation

# d3.forceSimulation([nodes]) <>

Đây là 1 method giúp tạo ra một 1 object lấy tên là "simulation" (nghĩa là "giả lập"). Method kia nhận tham số truyền vào là một mảng các nodes. Lúc này chưa cần truyền vào lực tương tác.

Nếu người dùng không cung cấp mảng các nodes, chương trình sẽ mặc định coi đây là mảng rỗng. Mô phỏng này:

Nếu muốn giả lập chạy thủ công, người dùng cần gọi simulation.stop, rồi gọi tiếp simulation.tick khi cần.

# simulation.restart() <>

Tái khởi động bộ đếm thời gian để chạy lại mô phỏng rồi trả lại . Kết hợp với simulation.alphaTarget hoặc simulation.alpha, cách này được dùng để “hâm nóng lại” mô phỏng khi có tương tác với người dùng, ví dụ:

  • người dùng kéo thả một node
  • người dùng tạm dừng mô phỏng thông qua simulation.stop.

Object "simulation* của d3-force sử dụng bộ đếm timer trong d3-timer. Bộ đếm này mặc định dùng window.requestAnimationFrame(). Nếu viết const timer = d3.timer(callback), thì cứ 1/60 giây 1 lần, trình duyệt sẽ gọi hàm callback truyền vào cho d3.timer().

Sourc code của d3-force trông như bên dưới đây. Vậy cứ 1/60 giây, trình duyệt sẽ gọi hàm step(), hàm step này gọi sự kiện tick.

stepper = d3.timer(step),
event = dispatch("tick", "end");

function step() {
  tick();
  event.call("tick", simulation);
  if (alpha < alphaMin) {
    stepper.stop();
    event.call("end", simulation);
  }
}

Việc gọi simulation.restart() sẽ ứng với việc gọi d3.timer().restart(), giúp dừng bộ timer hiện thời (ví dụ đang ở 39/60), tạo ra 1 timer mới (trở về 1/60).

# simulation.stop() <>

Dừng bộ đếm thời gian của mô phỏng nếu nó đang chạy, và trả về object "mô phỏng". If the timer is already stopped, this method does nothing. This method is useful for running the simulation manually; see simulation.tick.

# simulation.tick() <>

Tăng giá trị hiện tại của alpha by (alphaTarget - alpha) × alphaDecay; then invokes each registered force, passing the new alpha; then decrements each node’s velocity by velocity × velocityDecay; lastly increments each node’s position by velocity.

This method does not dispatch events; events are only dispatched by the internal timer when the simulation is started automatically upon creation or by calling simulation.restart. The natural number of ticks when the simulation is started is ⌈log(alphaMin) / log(1 - alphaDecay)⌉; by default, this is 300.

This method can be used in conjunction with simulation.stop to compute a static force layout. For large graphs, static layouts should be computed in a web worker to avoid freezing the user interface.

# simulation.nodes([nodes]) <>

If nodes is specified, sets the simulation’s nodes to the specified array of objects, initializing their positions and velocities if necessary, and then re-initializes any bound forces; returns the simulation. If nodes is not specified, returns the simulation’s array of nodes as specified to the constructor.

Mỗi node phải là một object, và object này sẽ được thêm các thuộc tính sau trong quá trình mô phỏng:

  • index - the node’s zero-based index into nodes
  • x - vị trí hiện tại theo trục x
  • y - vị trí hiện tại theo trục y
  • vx - tốc độ hiện tại theo trục x
  • vy - tốc độ hiện tại theo trục y

Vị trí ⟨x,y⟩ và vận tốc ⟨vx,vy⟩ có thể lần lượt bị thay đổi bởi lực tương tác và mô phỏng. Nếu vx hoặc vy là NaN, thì vận tốc ban đầu được đặt là ⟨0,0⟩. If either x or y is NaN, the position is initialized in a phyllotaxis arrangement, so chosen to ensure a deterministic, uniform distribution around the origin.

Để cố định một node vào một ví trí nào đấy, người dùng thay đổi hai thuộc tính:

  • fx - vị trí được cố định trên trục x
  • fy - vị trí được cố định trên trục y

Tại cuối mỗi tick, khi hết các lực tương tác, node nào có giá trị của - node.fx khác không thì:

  • node.x sẽ bị reset về giá trị của node.fx, còn
  • node.vx bị cho thành 0;
  • Tương tự với node.fy, node.y, và node.vy.

Để "cởi trói" cho một node vốn bị gắn cố định lúc trước, đặt node.fx và node.fy thành null, hoặc đơn thuần là xóa hai thuộc tính này.

Nếu thêm hoặc xóa phần tử trong mảng chứa node ban đầu, thì method trên cần phải được gọi lại (kèm theo mảng đã cập nhật mới) để thông báo cho mô phỏng biết, cũng như áp lại lực giữa các phần tử. The simulation does not make a defensive copy of the specified array.

# simulation.alpha([alpha]) <>

Nếu muốn tùy biến alpha, thì hãy đặt giá trị của nó trong khoảng [0,1] and returns this simulation. If alpha is not specified, returns the current alpha value, which defaults to 1.

Ghi chú: Theo bài này, thì:

  • "alpha" là một số nằm trong khoảng [0, 1], đại diện cho tiến độ hiện tại của simulation (đã giả lập xong chưa, được 10%, 50% hay 100%?)
  • Khi simulation bắt đầu, "alpha" được gán bằng 1, và giá trị này sẽ giảm xuống với tốc độ nhất định. Tốc độ giảm này nhanh hay chậm thì phụ thuộc vào "alphaDecay". "alpha" sẽ giảm cho đến "alphaMin".
  • Ngay khi giá trị của "alpha" nhỏ hơn "alphaMin", simulation sẽ dừng.
  • Giá trị mặc định của "alphaMin" là 0.001.
  • "alphaTarget" tái định nghĩa điểm dừng (đường tiêm cận), như vậy, thay vì giảm chầm chậm xuống 0, nó sẽ giảm xuống "alphaTarget=0.2".
  • "alphaTarget" có một vài chỗ hữu dụng, ví dụ khi cần kéo thả node, ta có thể thiết lập "alphaTarget" để simulation chạy liên tục tại một rate nào đó nhằm có một chuyển động mượt mà.

# simulation.alphaMin([min]) <>

If min is specified, sets the minimum alpha to the specified number in the range [0,1] and returns this simulation. If min is not specified, returns the current minimum alpha value, which defaults to 0.001. The simulation’s internal timer stops when the current alpha is less than the minimum alpha. The default alpha decay rate of ~0.0228 corresponds to 300 iterations.

# simulation.alphaDecay([decay]) <>

If decay is specified, sets the alpha decay rate to the specified number in the range [0,1] and returns this simulation. If decay is not specified, returns the current alpha decay rate, which defaults to 0.0228… = 1 - pow(0.001, 1 / 300) where 0.001 is the default minimum alpha.

The alpha decay rate determines how quickly the current alpha interpolates towards the desired target alpha; since the default target alpha is zero, by default this controls how quickly the simulation cools. Higher decay rates cause the simulation to stabilize more quickly, but risk getting stuck in a local minimum; lower values cause the simulation to take longer to run, but typically converge on a better layout. To have the simulation run forever at the current alpha, set the decay rate to zero; alternatively, set a target alpha greater than the minimum alpha.

# simulation.alphaTarget([target]) <>

If target is specified, sets the current target alpha to the specified number in the range [0,1] and returns this simulation. If target is not specified, returns the current target alpha value, which defaults to 0.

# simulation.velocityDecay([decay]) <>

If decay is specified, sets the velocity decay factor to the specified number in the range [0,1] and returns this simulation. If decay is not specified, returns the current velocity decay factor, which defaults to 0.4. The decay factor is akin to atmospheric friction; after the application of any forces during a tick, each node’s velocity is multiplied by 1 - decay. As with lowering the alpha decay rate, less velocity decay may converge on a better solution, but risks numerical instabilities and oscillation.

# simulation.force(name[, force]) <>

If force is specified, assigns the force for the specified name and returns this simulation. If force is not specified, returns the force with the specified name, or undefined if there is no such force. (By default, new simulations have no forces.) For example, to create a new simulation to layout a graph, you might say:

var simulation = d3.forceSimulation(nodes)
    .force("charge", d3.forceManyBody())
    .force("link", d3.forceLink(links))
    .force("center", d3.forceCenter());

To remove the force with the given name, pass null as the force. For example, to remove the charge force:

simulation.force("charge", null);

# simulation.find(x, y[, radius]) <>

Returns the node closest to the position ⟨x,y⟩ with the given search radius. If radius is not specified, it defaults to infinity. If there is no node within the search area, returns undefined.

# simulation.on(typenames, [listener]) <>

If listener is specified, sets the event listener for the specified typenames and returns this simulation. If an event listener was already registered for the same type and name, the existing listener is removed before the new listener is added. If listener is null, removes the current event listeners for the specified typenames, if any. If listener is not specified, returns the first currently-assigned listener matching the specified typenames, if any. When a specified event is dispatched, each listener will be invoked with the this context as the simulation.

The typenames is a string containing one or more typename separated by whitespace. Each typename is a type, optionally followed by a period (.) and a name, such as tick.foo and tick.bar; the name allows multiple listeners to be registered for the same type. The type must be one of the following:

  • tick - after each tick of the simulation’s internal timer.
  • end - after the simulation’s timer stops when alpha < alphaMin.

Note that tick events are not dispatched when simulation.tick is called manually; events are only dispatched by the internal timer and are intended for interactive rendering of the simulation. To affect the simulation, register forces instead of modifying nodes’ positions or velocities inside a tick event listener.

See dispatch.on for details.

Forces

A force is simply a function that modifies nodes’ positions or velocities; in this context, a force can apply a classical physical force such as electrical charge or gravity, or it can resolve a geometric constraint, such as keeping nodes within a bounding box or keeping linked nodes a fixed distance apart. For example, a simple positioning force that moves nodes towards the origin ⟨0,0⟩ might be implemented as:

function force(alpha) {
  for (var i = 0, n = nodes.length, node, k = alpha * 0.1; i < n; ++i) {
    node = nodes[i];
    node.vx -= node.x * k;
    node.vy -= node.y * k;
  }
}

Forces typically read the node’s current position ⟨x,y⟩ and then add to (or subtract from) the node’s velocity ⟨vx,vy⟩. However, forces may also “peek ahead” to the anticipated next position of the node, ⟨x + vx,y + vy⟩; this is necessary for resolving geometric constraints through iterative relaxation. Forces may also modify the position directly, which is sometimes useful to avoid adding energy to the simulation, such as when recentering the simulation in the viewport.

Simulations typically compose multiple forces as desired. This module provides several for your enjoyment:

Forces may optionally implement force.initialize to receive the simulation’s array of nodes.

# force(alpha) <>

Applies this force, optionally observing the specified alpha. Typically, the force is applied to the array of nodes previously passed to force.initialize, however, some forces may apply to a subset of nodes, or behave differently. For example, d3.forceLink applies to the source and target of each link.

# force.initialize(nodes) <>

Assigns the array of nodes to this force. This method is called when a force is bound to a simulation via simulation.force and when the simulation’s nodes change via simulation.nodes. A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force.

Centering

The centering force translates nodes uniformly so that the mean position of all nodes (the center of mass if all nodes have equal weight) is at the given position ⟨x,y⟩. This force modifies the positions of nodes on each application; it does not modify velocities, as doing so would typically cause the nodes to overshoot and oscillate around the desired center. This force helps keeps nodes in the center of the viewport, and unlike the positioning force, it does not distort their relative positions.

# d3.forceCenter([x, y]) <>

Creates a new centering force with the specified x- and y- coordinates. If x and y are not specified, they default to ⟨0,0⟩.

# center.x([x]) <>

If x is specified, sets the x-coordinate of the centering position to the specified number and returns this force. If x is not specified, returns the current x-coordinate, which defaults to zero.

# center.y([y]) <>

If y is specified, sets the y-coordinate of the centering position to the specified number and returns this force. If y is not specified, returns the current y-coordinate, which defaults to zero.

Collision

The collision force treats nodes as circles with a given radius, rather than points, and prevents nodes from overlapping. More formally, two nodes a and b are separated so that the distance between a and b is at least radius(a) + radius(b). To reduce jitter, this is by default a “soft” constraint with a configurable strength and iteration count.

# d3.forceCollide([radius]) <>

Creates a new circle collision force with the specified radius. If radius is not specified, it defaults to the constant one for all nodes.

# collide.radius([radius]) <>

If radius is specified, sets the radius accessor to the specified number or function, re-evaluates the radius accessor for each node, and returns this force. If radius is not specified, returns the current radius accessor, which defaults to:

function radius() {
  return 1;
}

The radius accessor is invoked for each node in the simulation, being passed the node and its zero-based index. The resulting number is then stored internally, such that the radius of each node is only recomputed when the force is initialized or when this method is called with a new radius, and not on every application of the force.

# collide.strength([strength]) <>

If strength is specified, sets the force strength to the specified number in the range [0,1] and returns this force. If strength is not specified, returns the current strength which defaults to 0.7.

Overlapping nodes are resolved through iterative relaxation. For each node, the other nodes that are anticipated to overlap at the next tick (using the anticipated positions ⟨x + vx,y + vy⟩) are determined; the node’s velocity is then modified to push the node out of each overlapping node. The change in velocity is dampened by the force’s strength such that the resolution of simultaneous overlaps can be blended together to find a stable solution.

# collide.iterations([iterations]) <>

If iterations is specified, sets the number of iterations per application to the specified number and returns this force. If iterations is not specified, returns the current iteration count which defaults to 1. Increasing the number of iterations greatly increases the rigidity of the constraint and avoids partial overlap of nodes, but also increases the runtime cost to evaluate the force.

Links

The link force pushes linked nodes together or apart according to the desired link distance. The strength of the force is proportional to the difference between the linked nodes’ distance and the target distance, similar to a spring force.

# d3.forceLink([links]) <>

Creates a new link force with the specified links and default parameters. If links is not specified, it defaults to the empty array.

# link.links([links]) <>

If links is specified, sets the array of links associated with this force, recomputes the distance and strength parameters for each link, and returns this force. If links is not specified, returns the current array of links, which defaults to the empty array.

Each link is an object with the following properties:

  • source - the link’s source node; see simulation.nodes
  • target - the link’s target node; see simulation.nodes
  • index - the zero-based index into links, assigned by this method

For convenience, a link’s source and target properties may be initialized using numeric or string identifiers rather than object references; see link.id. When the link force is initialized (or re-initialized, as when the nodes or links change), any link.source or link.target property which is not an object is replaced by an object reference to the corresponding node with the given identifier.

If the specified array of links is modified, such as when links are added to or removed from the simulation, this method must be called again with the new (or changed) array to notify the force of the change; the force does not make a defensive copy of the specified array.

# link.id([id]) <>

If id is specified, sets the node id accessor to the specified function and returns this force. If id is not specified, returns the current node id accessor, which defaults to the numeric node.index:

function id(d) {
  return d.index;
}

The default id accessor allows each link’s source and target to be specified as a zero-based index into the nodes array. For example:

var nodes = [
  {"id": "Alice"},
  {"id": "Bob"},
  {"id": "Carol"}
];

var links = [
  {"source": 0, "target": 1}, // Alice → Bob
  {"source": 1, "target": 2} // Bob → Carol
];

Now consider a different id accessor that returns a string:

function id(d) {
  return d.id;
}

With this accessor, you can use named sources and targets:

var nodes = [
  {"id": "Alice"},
  {"id": "Bob"},
  {"id": "Carol"}
];

var links = [
  {"source": "Alice", "target": "Bob"},
  {"source": "Bob", "target": "Carol"}
];

This is particularly useful when representing graphs in JSON, as JSON does not allow references. See this example.

The id accessor is invoked for each node whenever the force is initialized, as when the nodes or links change, being passed the node and its zero-based index.

# link.distance([distance]) <>

If distance is specified, sets the distance accessor to the specified number or function, re-evaluates the distance accessor for each link, and returns this force. If distance is not specified, returns the current distance accessor, which defaults to:

function distance() {
  return 30;
}

The distance accessor is invoked for each link, being passed the link and its zero-based index. The resulting number is then stored internally, such that the distance of each link is only recomputed when the force is initialized or when this method is called with a new distance, and not on every application of the force.

# link.strength([strength]) <>

If strength is specified, sets the strength accessor to the specified number or function, re-evaluates the strength accessor for each link, and returns this force. If strength is not specified, returns the current strength accessor, which defaults to:

function strength(link) {
  return 1 / Math.min(count(link.source), count(link.target));
}

Where count(node) is a function that returns the number of links with the given node as a source or target. This default was chosen because it automatically reduces the strength of links connected to heavily-connected nodes, improving stability.

The strength accessor is invoked for each link, being passed the link and its zero-based index. The resulting number is then stored internally, such that the strength of each link is only recomputed when the force is initialized or when this method is called with a new strength, and not on every application of the force.

# link.iterations([iterations]) <>

If iterations is specified, sets the number of iterations per application to the specified number and returns this force. If iterations is not specified, returns the current iteration count which defaults to 1. Increasing the number of iterations greatly increases the rigidity of the constraint and is useful for complex structures such as lattices, but also increases the runtime cost to evaluate the force.

Many-Body

The many-body (or n-body) force applies mutually amongst all nodes. It can be used to simulate gravity (attraction) if the strength is positive, or electrostatic charge (repulsion) if the strength is negative. This implementation uses quadtrees and the Barnes–Hut approximation to greatly improve performance; the accuracy can be customized using the theta parameter.

Unlike links, which only affect two linked nodes, the charge force is global: every node affects every other node, even if they are on disconnected subgraphs.

# d3.forceManyBody() <>

Creates a new many-body force with the default parameters.

# manyBody.strength([strength]) <>

If strength is specified, sets the strength accessor to the specified number or function, re-evaluates the strength accessor for each node, and returns this force. A positive value causes nodes to attract each other, similar to gravity, while a negative value causes nodes to repel each other, similar to electrostatic charge. If strength is not specified, returns the current strength accessor, which defaults to:

function strength() {
  return -30;
}

The strength accessor is invoked for each node in the simulation, being passed the node and its zero-based index. The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or when this method is called with a new strength, and not on every application of the force.

# manyBody.theta([theta]) <>

If theta is specified, sets the Barnes–Hut approximation criterion to the specified number and returns this force. If theta is not specified, returns the current value, which defaults to 0.9.

To accelerate computation, this force implements the Barnes–Hut approximation which takes O(n log n) per application where n is the number of nodes. For each application, a quadtree stores the current node positions; then for each node, the combined force of all other nodes on the given node is computed. For a cluster of nodes that is far away, the charge force can be approximated by treating the cluster as a single, larger node. The theta parameter determines the accuracy of the approximation: if the ratio w / l of the width w of the quadtree cell to the distance l from the node to the cell’s center of mass is less than theta, all nodes in the given cell are treated as a single node rather than individually.

# manyBody.distanceMin([distance]) <>

If distance is specified, sets the minimum distance between nodes over which this force is considered. If distance is not specified, returns the current minimum distance, which defaults to 1. A minimum distance establishes an upper bound on the strength of the force between two nearby nodes, avoiding instability. In particular, it avoids an infinitely-strong force if two nodes are exactly coincident; in this case, the direction of the force is random.

# manyBody.distanceMax([distance]) <>

If distance is specified, sets the maximum distance between nodes over which this force is considered. If distance is not specified, returns the current maximum distance, which defaults to infinity. Specifying a finite maximum distance improves performance and produces a more localized layout.

Positioning

The x- and y-positioning forces push nodes towards a desired position along the given dimension with a configurable strength. The radial force is similar, except it pushes nodes towards the closest point on a given circle. The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position. While these forces can be used to position individual nodes, they are intended primarily for global forces that apply to all (or most) nodes.

# d3.forceX([x]) <>

Creates a new positioning force along the x-axis towards the given position x. If x is not specified, it defaults to 0.

# x.strength([strength]) <>

If strength is specified, sets the strength accessor to the specified number or function, re-evaluates the strength accessor for each node, and returns this force. The strength determines how much to increment the node’s x-velocity: (x - node.x) × strength. For example, a value of 0.1 indicates that the node should move a tenth of the way from its current x-position to the target x-position with each application. Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints. A value outside the range [0,1] is not recommended.

If strength is not specified, returns the current strength accessor, which defaults to:

function strength() {
  return 0.1;
}

The strength accessor is invoked for each node in the simulation, being passed the node and its zero-based index. The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or when this method is called with a new strength, and not on every application of the force.

# x.x([x]) <>

If x is specified, sets the x-coordinate accessor to the specified number or function, re-evaluates the x-accessor for each node, and returns this force. If x is not specified, returns the current x-accessor, which defaults to:

function x() {
  return 0;
}

The x-accessor is invoked for each node in the simulation, being passed the node and its zero-based index. The resulting number is then stored internally, such that the target x-coordinate of each node is only recomputed when the force is initialized or when this method is called with a new x, and not on every application of the force.

# d3.forceY([y]) <>

Creates a new positioning force along the y-axis towards the given position y. If y is not specified, it defaults to 0.

# y.strength([strength]) <>

If strength is specified, sets the strength accessor to the specified number or function, re-evaluates the strength accessor for each node, and returns this force. The strength determines how much to increment the node’s y-velocity: (y - node.y) × strength. For example, a value of 0.1 indicates that the node should move a tenth of the way from its current y-position to the target y-position with each application. Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints. A value outside the range [0,1] is not recommended.

If strength is not specified, returns the current strength accessor, which defaults to:

function strength() {
  return 0.1;
}

The strength accessor is invoked for each node in the simulation, being passed the node and its zero-based index. The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or when this method is called with a new strength, and not on every application of the force.

# y.y([y]) <>

If y is specified, sets the y-coordinate accessor to the specified number or function, re-evaluates the y-accessor for each node, and returns this force. If y is not specified, returns the current y-accessor, which defaults to:

function y() {
  return 0;
}

The y-accessor is invoked for each node in the simulation, being passed the node and its zero-based index. The resulting number is then stored internally, such that the target y-coordinate of each node is only recomputed when the force is initialized or when this method is called with a new y, and not on every application of the force.

# d3.forceRadial(radius[, x][, y]) <>

Radial Force

Creates a new positioning force towards a circle of the specified radius centered at ⟨x,y⟩. If x and y are not specified, they default to ⟨0,0⟩.

# radial.strength([strength]) <>

If strength is specified, sets the strength accessor to the specified number or function, re-evaluates the strength accessor for each node, and returns this force. The strength determines how much to increment the node’s x- and y-velocity. For example, a value of 0.1 indicates that the node should move a tenth of the way from its current position to the closest point on the circle with each application. Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints. A value outside the range [0,1] is not recommended.

If strength is not specified, returns the current strength accessor, which defaults to:

function strength() {
  return 0.1;
}

The strength accessor is invoked for each node in the simulation, being passed the node and its zero-based index. The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or when this method is called with a new strength, and not on every application of the force.

# radial.radius([radius]) <>

If radius is specified, sets the circle radius to the specified number or function, re-evaluates the radius accessor for each node, and returns this force. If radius is not specified, returns the current radius accessor.

The radius accessor is invoked for each node in the simulation, being passed the node and its zero-based index. The resulting number is then stored internally, such that the target radius of each node is only recomputed when the force is initialized or when this method is called with a new radius, and not on every application of the force.

# radial.x([x]) <>

If x is specified, sets the x-coordinate of the circle center to the specified number and returns this force. If x is not specified, returns the current x-coordinate of the center, which defaults to zero.

# radial.y([y]) <>

If y is specified, sets the y-coordinate of the circle center to the specified number and returns this force. If y is not specified, returns the current y-coordinate of the center, which defaults to zero.

Ghi chú riêng

Eric Socolfsky - Forcing Functions: Inside D3.v4 forces and layout transitions

Một module d3-force phải trả về 1 function.

  • function là public API cho force
  • người dùng có thể nối thêm các function khác vào phía sau để thay đổi force.
  • function này được D3 gọi tự động tại mỗi "tick" của forceSimulation nếu force được thêm vào thông qua forceSimulation.force().
let simulation = d3.forceSimulation()
 .force('fancyForce', fancyForceModule());

force() là một function sẽ:

  • trả về giá trị hiện tại nếu không có tham số truyền vào
  • set giá trị mới và trả về roce nếu có tham số truyền vào
/*
force.strength = function(_) {
  return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
};
*/

force.strength = function (_) {
  if (arguments.length) {
    // Nếu có tham số truyền vào (tương đương việc arguments.length = 1, ứng với true)
    if (typeof _ === "function") {
      // Nếu tham số truyền vào là 1 function thì lưu nó lại
      strength = _;
    } else {
      // Nếu nó là một constant, thì biến nó thành 1 number
      // và bọc nó lại bên trong 1 function sẽ trả về chính số đấy
      strength = constant(+_);
    }

    // khởi tạo lại
    initialize();

    // cho phép function chaining
    return force;
  } else {
    // nếu không có tham số truyền vào 
    // thì trả về giá trị hiện tại
    return strength;
  }
};
force: function(name, _) {
  return arguments.length > 1 ? ((_ == null ? forces.remove(name) : forces.set(name, initializeForce(_))), simulation) : forces.get(name);
},

if (arguments.length > 1) {
  // Nếu có 2 tham số truyền vào (tương đương việc arguments.length bằng 2)
  if (_ == null) {
    // Nếu force-module bằng null, thì remove module này ra khỏi force
    forces.remove(name);
  } else {
    forces.set(name, initializeForce(_))
  }

  return simulation;
} else {
  // nếu không có đủ 2 tham số
  return forces.get(name);
}

Để đọc hiểu source code của d3-force, cần hiểu:

Ba (03) dependencies

  • d3-dispatch giúp đăng ký 2 sự kiện mang tên "tick" và "end" vào hệ thống.

    • Đầu tiên là tạo 2 sự kiện nói trên event = dispatch("tick", "end");. - Sau đó đăng ký mỗi event với 1 hàm callback, ví dụ event.on("tick", callback1), hoặc event.on("end", callback2). Hàm "callback1" hoặc "callback2" là do người dùng định nghĩa, ví dụ simulation.on("tick", tickEventHandler), thì tickEventHandler là 1 callback.
    • Cuối cùng khi cần thì gọi sự kiện đấy ra: event.call("tick", simulation), hoặc event.call("end", simulation). Ở đây sử dụng cơ chế của function.call.
  • d3-collection chỉ là một thư viện nhỏ mà Mike Bostock viết ra để xử lý các objects trong D3js một cách dễ dàng hơn việc dùng JavaScript thuần.

  • d3-timer là một bộ đếm thời gian mà object "simulation* của d3-force sử dụng. Bộ đếm này mặc định dùng window.requestAnimationFrame(). Nếu viết const timer = d3.timer(callback), thì cứ 1/60 giây 1 lần, trình duyệt sẽ gọi hàm callback truyền vào cho d3.timer().

Simulation

Force Module

Source code phần d3-force của D3.js

# Tên file Phân loại Mô tả Ghi chú
1 center.js Force Module Tạo các lực hướng tâm của svg
2 collide.js Force Module Tạo các lực giúp nodes không chạm nhau
3 constant.js Utils
4 jiggle.js Utils
5 link.js Force Module
6 manyBody.js Force Module
7 radial.js Force Module
8 simulation.js Simulation Module
9 x.js Force Module
10 y.js Force Module

Tham khảo:

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published