我目前正在開發互動式 D3.js Treemap,受到https://observablehq.com/@d3/zoomable-treemap的強烈啟發。但是,正如您在 https://jsfiddle.net/CharlotteAndre/rjy2pb4x/4/中看到的那樣,我對“Maxillopoda”組的組成部分的內部矩形的大小有疑問,最后一個“Halopitilus”組大于許多組即使我的值為 1。問題可能來自此函式
function position(group, root) {
group.selectAll("g")
.attr("transform", d => d === root ? `translate(0,-30)` : `translate(${x(d.x0)},${y(d.y0)})`)
.select("rect")
.attr("width", d => d === root ? width : x(d.x1) - x(d.x0))
.attr("height", d => d === root ? 30 : y(d.y1) - y(d.y0));
}
uj5u.com熱心網友回復:
我發現使用節點值和排序函式構建樹形圖存在問題
讓 treemap = data => d3.treemap() .tile(tile)(d3.hierarchy(data) .sum(d => d.value) .sort((a, b) => b.value - a.value ))
當您根據值的總和進行排序時,不要將 0 值分配給父節點。我更改了您的資料以洗掉與父母相關的值。您分配子值的總和或不分配。這種邏輯會打亂你的層次結構。運行下面的代碼,讓我知道這是否足以滿足您的需求。我更改了寬度和高度以便更好地查看。
let data = {
"name": "Crustacea",
"children": [
{
"name": "Maxillopoda",
"children": [
{
"name": "Maxillopoda_X",
"children": [
{
"name": "Maxillopoda_X_sp.",
"value": 448
}
]
},
{
"name": "Acartia",
"children": [
{
"name": "Acartia_longiremis",
"value": 6
},
{
"name": "Acartia_negligens",
"value": 6
},
{
"name": "Acartia_danae",
"value": 2
},
{
"name": "Acartia_pacifica",
"value": 1
}
]
},
{
"name": "Pleuromamma",
"children": [
{
"name": "Pleuromamma_scutullata",
"value": 10
},
{
"name": "Pleuromamma_borealis",
"value": 8
}
]
},
{
"name": "Calocalanus",
"children": [
{
"name": "Calocalanus_minutus",
"value": 143
},
{
"name": "Calocalanus_sp.",
"value": 61
},
{
"name": "Calocalanus_plumulosus",
"value": 12
},
{
"name": "Calocalanus_pavo",
"value": 19
}
]
},
{
"name": "Mecynocera",
"children": [
{
"name": "Mecynocera_clausi",
"value": 11
}
]
},
{
"name": "Oithona",
"children": [
{
"name": "Oithona_sp.",
"value": 18
}
]
},
{
"name": "Corycaeus",
"children": [
{
"name": "Corycaeus_speciosus",
"value": 9
}
]
},
{
"name": "Acrocalanus",
"children": [
{
"name": "Acrocalanus_monachus",
"value": 1
}
]
},
{
"name": "Subeucalanus",
"children": [
{
"name": "Subeucalanus_crassus",
"value": 5
}
]
},
{
"name": "Sapphirina",
"children": [
{
"name": "Sapphirina_sp.",
"value": 9
},
{
"name": "Sapphirina_scarlata",
"value": 3
},
{
"name": "Sapphirina_darwinii",
"value": 1
}
]
},
{
"name": "Paracalanus",
"children": [
{
"name": "Paracalanus_aculeatus",
"value": 1
}
]
},
{
"name": "Canthocalanus",
"children": [
{
"name": "Canthocalanus_pauper",
"value": 1
}
]
},
{
"name": "Temoropia",
"children": [
{
"name": "Temoropia_mayumbaensis",
"value": 5
}
]
},
{
"name": "Cosmocalanus",
"children": [
{
"name": "Cosmocalanus_darwinii",
"value": 2
}
]
},
{
"name": "Haloptilus",
"children": [
{
"name": "Haloptilus_longicornis",
"value": 1
}
]
},
{
"name": "Undinula",
"children": [
{
"name": "Undinula_vulgaris",
"value": 2
}
]
},
{
"name": "Centropages",
"children": [
{
"name": "Centropages_violaceus",
"value": 3
}
]
},
{
"name": "Euchaeta",
"children": [
{
"name": "Euchaeta_indica",
"value": 2
}
]
}
]
}
]
}
let svg_element = document.getElementById('treemap');
this.svg = d3.select(svg_element);
let margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 550 - margin.left - margin.right,
height = 550 - margin.top - margin.bottom;
// let height = 400;
// let width = 500;
let format = d3.format("");
let uids = new Map();
let uid = title => {
let counter = uids.has(title) ? uids.get(title) 1 : 0;
uids.set(title, counter);
return `${title}-${counter}`;
}
let name = d => d.ancestors().reverse().map(d => d.data.name).join("/");
function tile(node, x0, y0, x1, y1) {
d3.treemapBinary(node, 0, 0, width, height);
for (const child of node.children) {
child.x0 = x0 child.x0 / width * (x1 - x0);
child.x1 = x0 child.x1 / width * (x1 - x0);
child.y0 = y0 child.y0 / height * (y1 - y0);
child.y1 = y0 child.y1 / height * (y1 - y0);
}
}
let treemap = data => d3.treemap()
.tile(tile)(d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value))
const x = d3.scaleLinear().rangeRound([0, width]);
const y = d3.scaleLinear().rangeRound([0, height]);
const svg = this.svg
.attr("viewBox", [0.5, -30.5, width, height 30])
.style("font", "8px sans-serif");
let group = svg.append("g")
.call(render, treemap(data));
function render(group, root) {
const node = group
.selectAll("g")
.data(root.children.concat(root))
.join("g");
node.filter(d => d === root ? d.parent : d.children)
.attr("cursor", "pointer")
.on("click", (event, d) => d === root ? zoomout(root) : zoomin(d));
node.append("title")
.text(d => `${name(d)}\n${format(d.value)}`)
.style("font-size", "70%");
node.append("rect")
.attr("id", d => (d.leafUid = uid("leaf")))
.attr("fill", d => d === root ? "#edf3f7" : d.children ? "#0b9ba3" : "#0b9ba3")
.attr("stroke", "#edf3f7");
node.append("clipPath")
.attr("id", d => (d.clipUid = uid("clip")))
.append("use")
.attr("xlink:href", d => d.leafUid.href);
node.append("text")
.attr("clip-path", d => d.clipUid)
.attr("font-weight", d => d === root ? "bold" : null)
.selectAll("tspan")
.data(d => (d.data.name).split(/(?=[A-Z][^A-Z])/g).concat(format(d.value)))
.join("tspan")
.attr("x", 3)
.attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 1.1 i * 0.9}em`)
.attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 0.7 : null)
.attr("font-weight", (d, i, nodes) => i === nodes.length - 1 ? "normal" : null)
.text(d => d);
group.call(position, root);
}
function position(group, root) {
group.selectAll("g")
.attr("transform", d => d === root ? `translate(0,-30)` : `translate(${x(d.x0)},${y(d.y0)})`)
.select("rect")
.attr("width", d => d === root ? width : x(d.x1) - x(d.x0))
.attr("height", d => d === root ? 20 : y(d.y1) - y(d.y0));
}
// When zooming in, draw the new nodes on top, and fade them in.
function zoomin(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = group = svg.append("g").call(render, d);
x.domain([d.x0, d.x1]);
y.domain([d.y0, d.y1]);
svg.transition()
.duration(750)
.call(t => group0.transition(t).remove()
.call(position, d.parent))
.call(t => group1.transition(t)
.attrTween("opacity", () => d3.interpolate(0, 1))
.call(position, d));
}
// When zooming out, draw the old nodes on top, and fade them out.
function zoomout(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = group = svg.insert("g", "*").call(render, d.parent);
x.domain([d.parent.x0, d.parent.x1]);
y.domain([d.parent.y0, d.parent.y1]);
svg.transition()
.duration(750)
.call(t => group0.transition(t).remove()
.attrTween("opacity", () => d3.interpolate(1, 0))
.call(position, d))
.call(t => group1.transition(t)
.call(position, d.parent));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.4.4/d3.min.js"></script>
<svg id="treemap"></svg>
更新而不更改資料
let data = {
"name": "Crustacea",
"value": 0,
"children": [
{
"name": "Maxillopoda",
"value": 195,
"children": [
{
"name": "Maxillopoda_X",
"value": 0,
"children": [
{
"name": "Maxillopoda_X_sp.",
"value": 448
}
]
},
{
"name": "Acartia",
"value": 0,
"children": [
{
"name": "Acartia_longiremis",
"value": 6
},
{
"name": "Acartia_negligens",
"value": 6
},
{
"name": "Acartia_danae",
"value": 2
},
{
"name": "Acartia_pacifica",
"value": 1
}
]
},
{
"name": "Pleuromamma",
"value": 0,
"children": [
{
"name": "Pleuromamma_scutullata",
"value": 10
},
{
"name": "Pleuromamma_borealis",
"value": 8
}
]
},
{
"name": "Calocalanus",
"value": 9,
"children": [
{
"name": "Calocalanus_minutus",
"value": 43
},
{
"name": "Calocalanus_sp.",
"value": 61
},
{
"name": "Calocalanus_plumulosus",
"value": 12
},
{
"name": "Calocalanus_pavo",
"value": 19
}
]
},
{
"name": "Mecynocera",
"value": 0,
"children": [
{
"name": "Mecynocera_clausi",
"value": 11
}
]
},
{
"name": "Oithona",
"value": 0,
"children": [
{
"name": "Oithona_sp.",
"value": 18
}
]
},
{
"name": "Corycaeus",
"value": 0,
"children": [
{
"name": "Corycaeus_speciosus",
"value": 9
}
]
},
{
"name": "Acrocalanus",
"value": 0,
"children": [
{
"name": "Acrocalanus_monachus",
"value": 1
}
]
},
{
"name": "Subeucalanus",
"value": 0,
"children": [
{
"name": "Subeucalanus_crassus",
"value": 5
}
]
},
{
"name": "Sapphirina",
"value": 0,
"children": [
{
"name": "Sapphirina_sp.",
"value": 9
},
{
"name": "Sapphirina_scarlata",
"value": 3
},
{
"name": "Sapphirina_darwinii",
"value": 1
}
]
},
{
"name": "Paracalanus",
"value": 0,
"children": [
{
"name": "Paracalanus_aculeatus",
"value": 1
}
]
},
{
"name": "Canthocalanus",
"value": 0,
"children": [
{
"name": "Canthocalanus_pauper",
"value": 1
}
]
},
{
"name": "Temoropia",
"value": 0,
"children": [
{
"name": "Temoropia_mayumbaensis",
"value": 5
}
]
},
{
"name": "Cosmocalanus",
"value": 0,
"children": [
{
"name": "Cosmocalanus_darwinii",
"value": 2
}
]
},
{
"name": "Haloptilus",
"value": 0,
"children": [
{
"name": "Haloptilus_longicornis",
"value": 1
}
]
},
{
"name": "Undinula",
"value": 0,
"children": [
{
"name": "Undinula_vulgaris",
"value": 2
}
]
},
{
"name": "Centropages",
"value": 0,
"children": [
{
"name": "Centropages_violaceus",
"value": 3
}
]
},
{
"name": "Euchaeta",
"value": 0,
"children": [
{
"name": "Euchaeta_indica",
"value": 2
}
]
}
]
}
]
}
let svg_element = document.getElementById('treemap');
this.svg = d3.select(svg_element);
let margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 350 - margin.left - margin.right,
height = 198 - margin.top - margin.bottom;
// let height = 400;
// let width = 500;
let format = d3.format("");
let uids = new Map();
let uid = title => {
let counter = uids.has(title) ? uids.get(title) 1 : 0;
uids.set(title, counter);
return `${title}-${counter}`;
}
let name = d => d.ancestors().reverse().map(d => d.data.name).join("/");
function tile(node, x0, y0, x1, y1) {
d3.treemapBinary(node, 0, 0, width, height);
for (const child of node.children) {
child.x0 = x0 child.x0 / width * (x1 - x0);
child.x1 = x0 child.x1 / width * (x1 - x0);
child.y0 = y0 child.y0 / height * (y1 - y0);
child.y1 = y0 child.y1 / height * (y1 - y0);
}
}
function identifyLeaves(obj) {
var hasNoChildren = (obj.children) ? false : true;
if(hasNoChildren)
return obj.value
}
let treemap = data => d3.treemap()
.tile(tile)(d3.hierarchy(data)
.sum((d) => identifyLeaves(d))
.sort((a, b) => identifyLeaves(b) - identifyLeaves(a)))
const x = d3.scaleLinear().rangeRound([0, width]);
const y = d3.scaleLinear().rangeRound([0, height]);
const svg = this.svg
.attr("viewBox", [0.5, -30.5, width, height 30])
.style("font", "8px sans-serif");
let group = svg.append("g")
.call(render, treemap(data));
function render(group, root) {
const node = group
.selectAll("g")
.data(root.children.concat(root))
.data(root.children.concat(root))
.join("g");
node.filter(d => d === root ? d.parent : d.children)
.attr("cursor", "pointer")
.on("click", (event, d) => d === root ? zoomout(root) : zoomin(d));
node.append("title")
.text(d => `${name(d)}\n${format(d.value)}`)
.style("font-size", "70%");
node.append("rect")
.attr("id", d => (d.leafUid = uid("leaf")))
.attr("fill", d => d === root ? "#edf3f7" : d.children ? "#0b9ba3" : "#0b9ba3")
.attr("stroke", "#edf3f7");
node.append("clipPath")
.attr("id", d => (d.clipUid = uid("clip")))
.append("use")
.attr("xlink:href", d => d.leafUid.href);
node.append("text")
.attr("clip-path", d => d.clipUid)
.attr("font-weight", d => d === root ? "bold" : null)
.selectAll("tspan")
.data(d => (d.data.name).split(/(?=[A-Z][^A-Z])/g).concat(format(d.value)))
.join("tspan")
.attr("x", 3)
.attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 1.1 i * 0.9}em`)
.attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 0.7 : null)
.attr("font-weight", (d, i, nodes) => i === nodes.length - 1 ? "normal" : null)
.text(d => d);
group.call(position, root);
}
function position(group, root) {
group.selectAll("g")
.attr("transform", d => d === root ? `translate(0,-30)` : `translate(${x(d.x0)},${y(d.y0)})`)
.select("rect")
.attr("width", d => d === root ? width : x(d.x1) - x(d.x0))
.attr("height", d => d === root ? 30 : y(d.y1) - y(d.y0));
}
// When zooming in, draw the new nodes on top, and fade them in.
function zoomin(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = group = svg.append("g").call(render, d);
x.domain([d.x0, d.x1]);
y.domain([d.y0, d.y1]);
svg.transition()
.duration(750)
.call(t => group0.transition(t).remove()
.call(position, d.parent))
.call(t => group1.transition(t)
.attrTween("opacity", () => d3.interpolate(0, 1))
.call(position, d));
}
// When zooming out, draw the old nodes on top, and fade them out.
function zoomout(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = group = svg.insert("g", "*").call(render, d.parent);
x.domain([d.parent.x0, d.parent.x1]);
y.domain([d.parent.y0, d.parent.y1]);
svg.transition()
.duration(750)
.call(t => group0.transition(t).remove()
.attrTween("opacity", () => d3.interpolate(1, 0))
.call(position, d))
.call(t => group1.transition(t)
.call(position, d.parent));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.4.4/d3.min.js" integrity="sha512-hnFpvCiJ8Fr1lYLqcw6wLgFUOEZ89kWCkO cEekwcWPIPKyknKV1eZmSSG3UxXfsSuf z/SgmiYB1zFOg3l2UQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<svg id="treemap"></svg>
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/491662.html
