d3.js - d3js进入,更新,退出混乱:需要帮助请
问题描述
我真的很讨厌问这个问题,尤其是我知道这个问题已经被问了几十次了——而且我已经通读了这些帖子。但我的问题仍然存在——我根本不明白这种机制是如何工作的。我是 d3js 的新手,并且在流星中使用 v3.x;我已经完成了教程,并且得到了一些工作,但无法使用新数据进行更新。再次,我很抱歉重新讨论这个问题,但我读过的其他帖子都没有解决这个问题。
这是一个代码片段,我已经删除了所有不应该影响核心功能的东西:
var w = 800;
var h = 800;
var intensity = 25;
var margin = {
top: 75,
right: 100,
bottom: 75,
left: 60
};
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// get csv data, x & y coords, etc...
createHeatmap = function(csv, x, y) {
var data = d3.csv.parseRows(csv).map(function(row) {
return row.map(function(d) {
return +d;
});
});
// set some values
var min = 0;
var max = d3.max(data, function(d, i) {
return i + 1;
});
var rectSize = 4;
// set the scales for both axes
...
// set up the axes
...
// define colorScale
...
// create heatmap
svg.selectAll('g')
.data(data)
.enter()
.append('g')
.selectAll('rect')
.data(function(d, i, j) {
return d;
})
.enter() // start drawing rects
.append('rect')
.attr('x', function(d, i, j) {
return (i * rectSize);
})
.attr('y', function(d, i, j) {
return (j * rectSize);
})
.attr('width', w / max)
.attr('height', h / max)
.style('fill', function(d, i, j) {
return colorScale(d * intensity);
});
// append axes, scales, labels, etc.
}
// create heatmap
createHeatmap(csv, x, y);
我的问题是我不明白为什么当我将新数据传递到 createHeatmap() 时图表不更新热图。
我在调试器中逐步完成了它,并且在热图的初始创建过程中一切正常,并且可以正确呈现。当我发送新数据时,谜团就开始了。调试器显示,在 d3js 本身的深处(不在我的代码中), enter() 有一个完整的 od 空值数组,而不是我传入的数据。数据一直存在到那时。因此,当 d3js 处理空数据时,它自然会返回一个空对象,因此不会发生更新。
显然我没有正确地进行更新,但对我需要做些什么来纠正它一无所知。
非常感谢任何建议。
谢谢!
更新: 安德鲁,感谢您的回复。我尝试了您的第一个建议,修改了您的示例以适合我的数据,但它不会使用新数据进行更新。
我的变化:
var w = 800;
var h = 800;
var intensity = 25;
var margin = {
top: 75,
right: 100,
bottom: 75,
left: 60
};
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// get csv data, x & y coords, etc...
createHeatmap = function(csv, x, y) {
var data = d3.csv.parseRows(csv).map(function(row) {
return row.map(function(d) {
return +d;
});
});
// set some values
var min = 0;
var max = d3.max(data, function(d, i) {
return i + 1;
});
var rectSize = 4;
// set the scales for both axes
...
// set up the axes
...
// define colorScale
...
// append group of svg elements bound to data
var rows = svg.selectAll('g')
.data(data);
// enter new rows where needed
rows.enter().append('g');
// select all rects
var rects = rows.selectAll('rect')
.data(function(d, i, j) {
return d;
});
// enter new rects:
rects.enter().append('rect')
.attr('x', function(d, i, j) {
return (i * rectSize);
})
.attr('y', function(d, i, j) {
return (j * rectSize);
})
.attr('width', w / max)
.attr('height', h / max)
.style('fill', function(d, i, j) {
return colorScale(d * intensity);
});
添加了片段:
var csv = "'3, 6, 0, 8'\n'1, 9, 0, 4'\n'3, 0, 1, 8'\n'4, 0, 2, 7";
csv = csv.replace(/'/g,'');
var button = d3.select('button')
.on('click', function() {
createHeatmap(update());
});
var w = 120;
var h = 120;
var intensity = 10;
var margin = {
top: 25,
right: 25,
bottom: 25,
left: 25
};
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
createHeatmap(csv);
function createHeatmap(csv) {
console.log(csv);
var data = d3.csv.parseRows(csv).map(function(row) {
return row.map(function(d) {
return +d;
});
});
var min = 0;
var max = d3.max(data, function(d, i) {
return i + 1;
});
var rectSize = 30;
// define a colorScale with domain and color range
var colorScale = d3.scale.linear()
.domain([0,0.5,1])
.range(['red', 'green', 'blue']);
// append group of svg elements bound to data
var rows = svg.selectAll('g')
.data(data);
// enter new rows where needed
rows.enter().append('g');
// select all rects
var rects = rows.selectAll('rect')
.data(function(d, i, j) {
return d;
});
// enter new rects:
rects.enter().append('rect')
.attr('x', function(d, i, j) {
return (i * rectSize);
})
.attr('y', function(d, i, j) {
return (j * rectSize);
})
.attr('width', w / max)
.attr('height', h / max)
.style('fill', function(d, i, j) {
return colorScale(d * intensity);
});
}
function update() {
var data = "'0, 1, 9, 5'\n'4, 0, 7, 2'\n'6, 3, 0, 8'\n'5, 3, 7, 0";
data = data.replace(/'/g,'');
return data;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<button>Update</button>
<div id="heatmap"></div>
解决方案
问题在于您的方法链接。
在第一次运行时,事情应该按预期运行:
// create heatmap
svg.selectAll('g') // 1. select all g elements
.data(data) // 2. assign data
.enter() // 3. enter and append a g for each item in the data array
.append('g') // that doesn't have a corresponding element in the DOM (or more accurately, the selection)
.selectAll('rect') // 4. For each newly entered g, select child rectangles
.data(function(d, i, j) { // 5. assign data to child selection.
return d;
})
.enter() // 6. Enter and append a rect for each item in the child g's data array
.append("rect") // that doesn't have a corresponding element in the DOM.
.... // 7. Style
在第一次运行时,我们选择了所有的g
s,没有,所以输入选择将为数据数组中的每个项目提供一个元素:我们正在输入所有内容。与子矩形相同:进行选择时不存在子矩形,因此您在子数据数组中输入所有内容。
在第二次运行中,使用svg.selectAll("g")
选择g
第一次创建的所有 s - 如果数据数组具有相同数量的项目,则无需输入任何内容。你不想附加任何东西:enter().append()
第二次(不是说你在任何情况下都用 .append() 附加更多元素)。
基本上在第二遍中,您正在修改一个空选择。
相反,您想更新。虽然在第二遍时输入选择为空,但更新选择具有所有现有g
的 s。
有几种方法可以做到这一点,一种是打破你的链接:
这是版本 3 的解决方案:
var rows = svg.selectAll("g")
.data(data);
// enter new rows where needed
rows.enter().append("g")
// Select all rects
var rects = rows.selectAll("rect")
.data(function(d) { return d; })
// Enter new rects:
rects.enter().append("rect")
// Update rects (all rects, not just the newly entered):
rects.attr()...
下面的代码片段使用了这种模式,它根据需要输入 new rect
s 和g
s 。然后更新所有的rect
s 和g
s 之后。这利用了 d3v3 中的魔法,其中更新选择和输入选择在内部合并,这在 d3v4,v5 中不是这种情况,我将在下面展示。
var button = d3.select("button")
.on("click", function() {
update(random());
})
var svg = d3.select("div")
.append("svg");
var color = d3.scale.linear()
.domain([0,0.5,1])
.range(["red","orange","yellow"])
update(random());
function update(data) {
var rows = svg.selectAll("g")
.data(data);
// enter new rows where needed
rows.enter()
.append("g")
.attr("transform", function(d,i) {
return "translate("+[0,i*22]+")";
})
// Select all rects:
var rects = rows.selectAll("rect")
.data(function(d) { return d; })
// Enter new rects:
rects.enter().append("rect")
// Update rects:
rects.attr("fill", function(d) {
return color(d);
})
.attr("x", function(d,i) { return i*22; })
.attr("width", 20)
.attr("height", 20);
console.log("entered rows:" + rows.enter().size());
console.log("entered rects:" + rects.enter().size());
}
function random() {
return d3.range(5).map(function() {
return d3.range(5).map(function() {
return Math.random();
})
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<button>Update</button>
<div></div>
v4/v5:
对于我建议升级到的 v4/v5,模式有点不同,因为您必须明确合并输入和更新选择:
var button = d3.select("button")
.on("click", function() {
update(random());
})
var svg = d3.select("div")
.append("svg");
var color = d3.scaleLinear()
.domain([0,0.5,1])
.range(["red","orange","yellow"])
update(random());
function update(data) {
var rows = svg.selectAll("g")
.data(data);
// enter new rows where needed
rows = rows.enter()
.append("g")
.merge(rows) // merge with existing rows
.attr("transform", function(d,i) {
return "translate("+[0,i*22]+")";
})
// Select all rects:
var rects = rows.selectAll("rect")
.data(function(d) { return d; })
// Enter new rects:
rects = rects.enter().append("rect")
.merge(rects);
// Update rects:
rects.attr("fill", function(d) {
return color(d);
})
.attr("x", function(d,i) { return i*22; })
.attr("width", 20)
.attr("height", 20);
}
function random() {
return d3.range(5).map(function() {
return d3.range(5).map(function() {
return Math.random();
})
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button>Update</button>
<div></div>
更新
您的代码段几乎包含了更改,但您仍然需要分解第二个选择,即矩形的选择,以便输入新的矩形,然后更新所有矩形:
var csv = "'3, 6, 0, 8'\n'1, 9, 0, 4'\n'3, 0, 1, 8'\n'4, 0, 2, 7";
csv = csv.replace(/'/g,'');
var button = d3.select('button')
.on('click', function() {
createHeatmap(update());
});
var w = 120;
var h = 120;
var intensity = 10;
var margin = {
top: 25,
right: 25,
bottom: 25,
left: 25
};
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
createHeatmap(csv);
function createHeatmap(csv) {
console.log(csv);
var data = d3.csv.parseRows(csv).map(function(row) {
return row.map(function(d) {
return +d;
});
});
var min = 0;
var max = d3.max(data, function(d, i) {
return i + 1;
});
var rectSize = 30;
// define a colorScale with domain and color range
var colorScale = d3.scale.linear()
.domain([0,0.5,1])
.range(['red', 'green', 'blue']);
// append group of svg elements bound to data
var rows = svg.selectAll('g')
.data(data);
// enter new rows where needed
rows.enter().append('g');
// select all rects
var rects = rows.selectAll('rect')
.data(function(d, i, j) {
return d;
});
// enter new rects:
rects.enter().append('rect');
// CHANGES HERE:
// Broke chain so that update actions aren't carried out on the enter selection:
rects.attr('x', function(d, i, j) {
return (i * rectSize);
})
.attr('y', function(d, i, j) {
return (j * rectSize);
})
.attr('width', w / max)
.attr('height', h / max)
.style('fill', function(d, i, j) {
return colorScale(d * intensity);
});
}
function update() {
var data = "'0, 1, 9, 5'\n'4, 0, 7, 2'\n'6, 3, 0, 8'\n'5, 3, 7, 0";
data = data.replace(/'/g,'');
return data;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<button>Update</button>
<div id="heatmap"></div>
推荐阅读
- swift - 如何快速获取两个 UICollectionView 的 ScrollView?
- nginx - nginx 反向代理 - 如何为多个应用程序提供服务
- xml - 属性 xsi:type 不允许使用值
- python - 使用python在一个大的sql文件中拆分sql命令
- java - 在循环中将int转换为字符串
- python - 在 python 面向对象的类中解释代码
- php - 是否可以在库存 Alpine 3.12 Docker 映像上安装 PHP7.4?
- r - ggplot2 | 仅为一个变量自定义 geom_point
- python - default_attrs.update(attrs) ValueError: 字典更新序列元素 #0 的长度为 1;2 是必需的
- css - 使用 gtk+3 和 Css 时的问题