我需要有連接節點的聚集氣泡。
集群正在兩個層面形成:
- 根據 prop 水平拆分,這樣就有 3 個 diff 堆疊
- 在每個堆疊中組合相似型別的節點
為了達到同樣的效果,我將兩個 observablehq 樣本的解決方案結合起來:
- 將節點水平拆分(堆疊)
- 在堆疊中組合節點
以下是解決方案:
jsFiddle 鏈接:jsFiddle 解決方案
const m = 10 // number of groups;
const l = 3
const [width, height] = [1000, 600];
const color = d3.scaleOrdinal(d3.range(m), d3.schemeCategory10);
function renderChart(dataSource) {
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
const pack = () => d3.pack()
.size([width, height])
.padding(1)
(d3.hierarchy(dataSource.nodes)
.sum(d => d.value));
const nodes = pack().leaves();
const simulation = d3.forceSimulation(nodes)
.force("x", d3.forceX(d => {
if (d.data.level === 1) {
return width / 3 - 100; // width/5 - 50 - 400;
}
return width / 3 * d.data.level; // width/5 * d.group - 400;
}).strength(0.95))
.force("y", d3.forceY(height / 2).strength(0.01))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("cluster", forceCluster())
.force("collide", forceCollide());
const node = svg.append("g")
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", d => color(d.data.group));
node.transition()
.attrTween("r", d => {
const i = d3.interpolate(0, d.r);
return t => d.r = i(t);
});
// links
const links = dataSource.links.map(d => Object.create(d));
const link = svg.append("g")
.selectAll("line")
.data(links)
.join("line")
.classed('link', true)
.style('stroke', '#999')
.style("stroke-opacity", 0.75);
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
simulation
.nodes(nodes)
.on("tick", ticked);
simulation
.force("link", d3.forceLink().id(function (d) { return d.data.id; }).strength(0.3))
.force("link").links(links);
}
d3.json("https://gist.githubusercontent.com/ravengao/a548cc4a7dd38f1afe0fe4b31ba8901b/raw/fd86a4e94e31ca2497ee78e1006dbed798f62e67/sample_data_supp%3E0.4_new.json").then((d) => {
const dataSource = {
links: d.links,
nodes: d.nodes.map(n => {
n.group = Math.random() * m | 0;
n.value = -Math.log(Math.random())
return n;
})
};
dataSource.nodes = ({
children: Array.from(
d3.group(
dataSource.nodes,
d => d.group
),
([, children]) => ({
children
})
)
});
renderChart(dataSource);
});
function forceCluster() {
const strength = 0.2;
let nodes;
function force(alpha) {
const centroids = d3.rollup(nodes, centroid, d => d.data.group);
const l = alpha * strength;
for (const d of nodes) {
const {
x: cx,
y: cy
} = centroids.get(d.data.group);
d.vx -= (d.x - cx) * l;
d.vy -= (d.y - cy) * l;
}
}
force.initialize = _ => nodes = _;
return force;
}
function forceCollide() {
const alpha = 0.4; // fixed for greater rigidity!
const padding1 = 2; // separation between same-color nodes
const padding2 = 6; // separation between different-color nodes
let nodes;
let maxRadius;
function force() {
const quadtree = d3.quadtree(nodes, d => d.x, d => d.y);
for (const d of nodes) {
const r = d.r maxRadius;
const nx1 = d.x - r,
ny1 = d.y - r;
const nx2 = d.x r,
ny2 = d.y r;
quadtree.visit((q, x1, y1, x2, y2) => {
if (!q.length)
do {
if (q.data !== d) {
const r = d.r q.data.r (d.data.group === q.data.data.group ? padding1 : padding2);
let x = d.x - q.data.x,
y = d.y - q.data.y,
l = Math.hypot(x, y);
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l, d.y -= y *= l;
q.data.x = x, q.data.y = y;
}
}
} while (q = q.next);
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
}
}
force.initialize = _ => maxRadius = d3.max(nodes = _, d => d.r) Math.max(padding1, padding2);
return force;
}
function centroid(nodes) {
let x = 0;
let y = 0;
let z = 0;
for (const d of nodes) {
let k = d.r ** 2;
x = d.x * k;
y = d.y * k;
z = k;
}
return {
x: x / z,
y: y / z
};
}
function generateUUID() {
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
問題:在添加鏈接之前,這兩種型別的分組/集群都可以完美運行。但只要嘗試添加黑白鏈接。節點,分組失敗。
嘗試評論第 81、82 和 83 行,分組作業正常。但啟用后,分組會丟失。
期待提供同樣的幫助。
謝謝,馬尼什
uj5u.com熱心網友回復:
您有一個預期的結果:存在相互競爭的力量,最終結果是對節點進行分組的力量和鏈接節點的力量之間的折衷。這些力將節點拉向相互競爭的方向,最終結果既不是針對組優化,也不是針對鏈接優化,而是兩者的某種組合。
簡單的解決方案是消除與鏈接力相關的任何強度。這將導致不會對鏈接節點施加任何力,從而將它們留在原地以通過組合它們的力來定位:
.force("link").links(links).strength(0);
一個需要更多作業來重構代碼但效率更高的解決方案是完全洗掉鏈接力,只需添加連接點的線,然后在每個滴答聲中更新這些線。
下面介紹第一種解決方案。
顯示代碼片段
const m = 10 // number of groups;
const l = 3
const [width, height] = [1000, 600];
const color = d3.scaleOrdinal(d3.range(m), d3.schemeCategory10);
function renderChart(dataSource) {
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
const pack = () => d3.pack()
.size([width, height])
.padding(1)
(d3.hierarchy(dataSource.nodes)
.sum(d => d.value));
const nodes = pack().leaves();
const simulation = d3.forceSimulation(nodes)
.force("x", d3.forceX(d => {
if (d.data.level === 1) {
return width / 3 - 100; // width/5 - 50 - 400;
}
return width / 3 * d.data.level; // width/5 * d.group - 400;
}).strength(0.95))
.force("y", d3.forceY(height / 2).strength(0.01))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("cluster", forceCluster())
.force("collide", forceCollide());
const node = svg.append("g")
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", d => color(d.data.group));
node.transition()
.attrTween("r", d => {
const i = d3.interpolate(0, d.r);
return t => d.r = i(t);
});
// links
const links = dataSource.links.map(d => Object.create(d));
const link = svg.append("g")
.selectAll("line")
.data(links)
.join("line")
.classed('link', true)
.style('stroke', '#999')
.style("stroke-opacity", 0.75);
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
simulation
.nodes(nodes)
.on("tick", ticked);
simulation
.force("link", d3.forceLink().id(function (d) { return d.data.id; }).strength(0.3))
.force("link").links(links).strength(0);
}
d3.json("https://gist.githubusercontent.com/ravengao/a548cc4a7dd38f1afe0fe4b31ba8901b/raw/fd86a4e94e31ca2497ee78e1006dbed798f62e67/sample_data_supp%3E0.4_new.json").then((d) => {
const dataSource = {
links: d.links,
nodes: d.nodes.map(n => {
n.group = Math.random() * m | 0;
n.value = -Math.log(Math.random())
return n;
})
};
dataSource.nodes = ({
children: Array.from(
d3.group(
dataSource.nodes,
d => d.group
),
([, children]) => ({
children
})
)
});
renderChart(dataSource);
});
function forceCluster() {
const strength = 0.2;
let nodes;
function force(alpha) {
const centroids = d3.rollup(nodes, centroid, d => d.data.group);
const l = alpha * strength;
for (const d of nodes) {
const {
x: cx,
y: cy
} = centroids.get(d.data.group);
d.vx -= (d.x - cx) * l;
d.vy -= (d.y - cy) * l;
}
}
force.initialize = _ => nodes = _;
return force;
}
function forceCollide() {
const alpha = 0.4; // fixed for greater rigidity!
const padding1 = 2; // separation between same-color nodes
const padding2 = 6; // separation between different-color nodes
let nodes;
let maxRadius;
function force() {
const quadtree = d3.quadtree(nodes, d => d.x, d => d.y);
for (const d of nodes) {
const r = d.r maxRadius;
const nx1 = d.x - r,
ny1 = d.y - r;
const nx2 = d.x r,
ny2 = d.y r;
quadtree.visit((q, x1, y1, x2, y2) => {
if (!q.length)
do {
if (q.data !== d) {
const r = d.r q.data.r (d.data.group === q.data.data.group ? padding1 : padding2);
let x = d.x - q.data.x,
y = d.y - q.data.y,
l = Math.hypot(x, y);
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l, d.y -= y *= l;
q.data.x = x, q.data.y = y;
}
}
} while (q = q.next);
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
}
}
force.initialize = _ => maxRadius = d3.max(nodes = _, d => d.r) Math.max(padding1, padding2);
return force;
}
function centroid(nodes) {
let x = 0;
let y = 0;
let z = 0;
for (const d of nodes) {
let k = d.r ** 2;
x = d.x * k;
y = d.y * k;
z = k;
}
return {
x: x / z,
y: y / z
};
}
function generateUUID() {
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
要執行第二種方法,我們洗掉鏈接力,但我們需要復制其中的一部分:力布局的初始化將每行的源和目標屬性從 ID 轉換為節點陣列中的實際專案,我們需要這樣做我們自己:
// Map for ease:
const map = new Map(nodes.map(n=>[n.data.id,n]));
const link = svg.append("g")
...
.each(function(d) {
// replace source and target ID with actual nodes:
d.source = map.get(d.source);
d.target = map.get(d.target);
})
注意,我們不理會tick函式,因為我們仍然需要更新渲染每個tick的鏈接,但是現在我們沒有計算每個鏈接在其源/目標節點上執行的拉力的強制布局開銷:
顯示代碼片段
const m = 10 // number of groups;
const l = 3
const [width, height] = [1000, 600];
const color = d3.scaleOrdinal(d3.range(m), d3.schemeCategory10);
function renderChart(dataSource) {
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
const pack = () => d3.pack()
.size([width, height])
.padding(1)
(d3.hierarchy(dataSource.nodes)
.sum(d => d.value));
const nodes = pack().leaves();
const simulation = d3.forceSimulation(nodes)
.force("x", d3.forceX(d => {
if (d.data.level === 1) {
return width / 3 - 100; // width/5 - 50 - 400;
}
return width / 3 * d.data.level; // width/5 * d.group - 400;
}).strength(0.95))
.force("y", d3.forceY(height / 2).strength(0.01))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("cluster", forceCluster())
.force("collide", forceCollide());
const node = svg.append("g")
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", d => color(d.data.group));
node.transition()
.attrTween("r", d => {
const i = d3.interpolate(0, d.r);
return t => d.r = i(t);
});
// Map for ease later:
const map = new Map(nodes.map(n=>[n.data.id,n]));
// links
const links = dataSource.links.map(d => Object.create(d));
const link = svg.append("g")
.lower()
.selectAll("line")
.data(links)
.join("line")
.classed('link', true)
.style('stroke', '#999')
.style("stroke-opacity", 0.75)
.each(function(d) {
// replace source and target ID with actual nodes:
d.source = map.get(d.source);
d.target = map.get(d.target);
})
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
simulation
.nodes(nodes)
.on("tick", ticked);
// simulation
// .force("link", d3.forceLink().id(function (d) { return d.data.id; }).strength(0.3))
// .force("link").links(links).strength(0);
}
d3.json("https://gist.githubusercontent.com/ravengao/a548cc4a7dd38f1afe0fe4b31ba8901b/raw/fd86a4e94e31ca2497ee78e1006dbed798f62e67/sample_data_supp%3E0.4_new.json").then((d) => {
const dataSource = {
links: d.links,
nodes: d.nodes.map(n => {
n.group = Math.random() * m | 0;
n.value = -Math.log(Math.random())
return n;
})
};
dataSource.nodes = ({
children: Array.from(
d3.group(
dataSource.nodes,
d => d.group
),
([, children]) => ({
children
})
)
});
renderChart(dataSource);
});
function forceCluster() {
const strength = 0.2;
let nodes;
function force(alpha) {
const centroids = d3.rollup(nodes, centroid, d => d.data.group);
const l = alpha * strength;
for (const d of nodes) {
const {
x: cx,
y: cy
} = centroids.get(d.data.group);
d.vx -= (d.x - cx) * l;
d.vy -= (d.y - cy) * l;
}
}
force.initialize = _ => nodes = _;
return force;
}
function forceCollide() {
const alpha = 0.4; // fixed for greater rigidity!
const padding1 = 2; // separation between same-color nodes
const padding2 = 6; // separation between different-color nodes
let nodes;
let maxRadius;
function force() {
const quadtree = d3.quadtree(nodes, d => d.x, d => d.y);
for (const d of nodes) {
const r = d.r maxRadius;
const nx1 = d.x - r,
ny1 = d.y - r;
const nx2 = d.x r,
ny2 = d.y r;
quadtree.visit((q, x1, y1, x2, y2) => {
if (!q.length)
do {
if (q.data !== d) {
const r = d.r q.data.r (d.data.group === q.data.data.group ? padding1 : padding2);
let x = d.x - q.data.x,
y = d.y - q.data.y,
l = Math.hypot(x, y);
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l, d.y -= y *= l;
q.data.x = x, q.data.y = y;
}
}
} while (q = q.next);
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
}
}
force.initialize = _ => maxRadius = d3.max(nodes = _, d => d.r) Math.max(padding1, padding2);
return force;
}
function centroid(nodes) {
let x = 0;
let y = 0;
let z = 0;
for (const d of nodes) {
let k = d.r ** 2;
x = d.x * k;
y = d.y * k;
z = k;
}
return {
x: x / z,
y: y / z
};
}
function generateUUID() {
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/529621.html
上一篇:d3choropleth-從osm/overpassapixml檔案繪圖的問題
下一篇:SeleniumWeb驅動程式(driver.find_element(By.XPATH,''))不作業Python
