d3js войти, обновить, выйти из путаницы: нужна помощь, пожалуйста - PullRequest
0 голосов
/ 19 декабря 2018

Мне искренне не хочется задавать этот вопрос, тем более что я знаю, что его задавали десятки раз - и я прочитал посты.Но моя проблема остается - я просто не понимаю, как работает этот механизм.Я новичок в 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 ().

Iпошагово прошел в отладчике, и все работает так, как я ожидал во время первоначального создания тепловой карты, которая правильно отображает.Когда я отправляю новые данные, это когда начинается загадка.Отладчик показывает, что глубоко внутри самого d3js (не в моем коде), что enter () имеет массив с нулевыми значениями вместо данных, которые я передаю. Данные существуют до этой точки.Таким образом, поскольку 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>

1 Ответ

0 голосов
/ 19 декабря 2018

Проблема в цепочке вашего метода.

При первом запуске все должно работать так, как ожидается:

// 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 с, их нет, поэтому выбор ввода будет иметьэлемент для каждого элемента в массиве данных: мы вводим все.То же, что и с дочерними прямоугольниками: при выполнении выбора дочерних прямоугольников не существует, поэтому вы вводите все в дочерний массив данных.

При втором запуске с помощью 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()...

В приведенном ниже фрагменте используется этот шаблон, он вводит новые rect с и g с по мере необходимости.А затем обновляет все 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>
...