首页 > 解决方案 > 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>

标签: d3.jsmeteor

解决方案


问题在于您的方法链接。

在第一次运行时,事情应该按预期运行:

// 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

在第一次运行时,我们选择了所有的gs,没有,所以输入选择将为数据数组中的每个项目提供一个元素:我们正在输入所有内容。与子矩形相同:进行选择时不存在子矩形,因此您在子数据数组中输入所有内容。

在第二次运行中,使用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 rects 和gs 。然后更新所有的rects 和gs 之后。这利用了 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>


推荐阅读