2012-03-01 28 views
17

Tôi đang cố gắng tìm ra cách cập nhật một số phần tử D3.js chỉ bằng cách liên kết dữ liệu mới. Tôi không thực sự chắc chắn nếu điều này là có thể hay không, nhưng nó cảm thấy như nó phải được.Thao tác các phần tử bằng cách ràng buộc dữ liệu mới

Vì vậy, đầu tiên tôi đã tạo ra bốn vòng tròn SVG, và thiết lập các cx bù đắp như một chức năng của dữ liệu:

<body><div id="container"></div></body> 

var svg = d3.select("div.container").append("svg") 
    .attr("class", "chart") 
    .attr("width", 1000) 
    .attr("height", 500); 

    // Create initial data and display 
    var data = [0, 10, 20, 30]; 
    var circle = svg.selectAll("circle") 
     .data(data) 
     .enter() 
     .append('circle') 
     .attr("cx", function(d) { 
     return d*10; 
     }) 
     .attr("cy", 100) 
     .attr("r", 10) 
     .style("fill", "steelblue"); 

Tiếp theo tôi đính kèm dữ liệu mới và một sự chuyển tiếp. Tôi hy vọng sẽ thấy các vòng tròn di chuyển chậm đến vị trí mới (đó là những gì tôi đang cố gắng đạt được), nhưng chúng không:

var data1 = [40, 50, 60, 70]; 
    circle.data(data1).transition().duration(2500); 

Tôi có mắc lỗi cơ bản không? Có lẽ tôi đã chọn sai. Hay đơn giản là không thể cập nhật các phần tử chỉ bằng thao tác dữ liệu?

CẬP NHẬT: nếu tôi làm console.log(circle) thì tôi sẽ thấy một mảng các phần tử vòng tròn SVG, đó là những gì tôi mong đợi.

Trả lời

23

Một cách mạnh mẽ (nhưng khó chịu) D3.js đôi khi buộc bạn lặp lại để hiển thị đơn giản. Nếu bạn nói rằng bạn muốn tạo một phần tử với các thuộc tính bắt nguồn từ dữ liệu theo một cách nhất định, và sau đó bạn muốn chuyển các thuộc tính đó sang dữ liệu mới, bạn phải nói lại cho nó cách lấy các giá trị (trong trường hợp bạn muốn để làm điều gì đó khác, chẳng hạn như bố cục trực quan khác). Như @Andrew nói, bạn phải nói cho nó biết phải làm gì khi nó chuyển tiếp.

Bạn có thể làm việc xung quanh này 'vấn đề' bằng cách làm theo mô hình này:

var foo = d3.select('foo'); 
function redraw(someArray){ 
    var items = foo.selectAll('bar').data(someArray); 
    items.enter().append('bar'); 
    items.exit().remove(); 
    items 
    .attr('foo',function(d){ return d }); 
} 

Trong 2.0 khi bạn append() mục mới trong enter() họ sẽ được tự động thêm vào lựa chọn dữ liệu ràng buộc ban đầu, vì vậy các cuộc gọi đến attr() và những điều sau này sẽ áp dụng cho chúng. Điều này cho phép bạn sử dụng cùng một mã cho cả cài đặt giá trị ban đầu và giá trị cập nhật.

Vì bạn không muốn tạo lại trình bao bọc SVG mỗi lần cập nhật, nên tạo điều này bên ngoài chức năng redraw.

Nếu bạn muốn thực hiện chuyển tiếp:

function redraw(someArray){ 
    var items = foo.selectAll('bar').data(someArray); 
    items.enter().append('bar') 
    .attr('opacity',0) 
    .attr('foo',initialPreAnimationValue); 
    items.exit().transition().duration(500) 
    .attr('opacity',0) 
    .remove(); 
    items.transition.duration(500) 
    .attr('opacity',1) 
    .attr('foo',function(d){ return d }); 
} 

Lưu ý rằng các đối tác trên các đối tượng với dữ liệu bằng cách chỉ mục. Nếu bạn muốn xóa điểm dữ liệu trung gian để làm mờ điểm dữ liệu đó trước khi xóa nó (thay vì xóa mục cuối cùng và chuyển đổi mọi mục khác thành mục đó) thì bạn nên chỉ định giá trị chuỗi duy nhất (không phải chỉ mục) để liên kết mỗi mục với:

var data = [ 
    {id:1, c:'red', x:150}, 
    {id:3, c:'#3cf', x:127}, 
    {id:2, c:'green', x:240}, 
    {id:4, c:'red', x:340} 
]; 
myItems.data(data,function(d){ return d.id; }); 

Bạn có thể xem ví dụ về điều này và phát trực tiếp trên số my D3.js playground.

Trước tiên, hãy xem điều gì xảy ra khi bạn nhận xét một trong các dòng dữ liệu và sau đó đặt lại. Tiếp theo, xóa thông số ƒ('id') khỏi cuộc gọi đến data() trên dòng 4 và một lần nữa thử nhận xét và trong dòng dữ liệu.

Sửa: Ngoài ra, như ý kiến ​​của mbostock lừng, bạn có thể sử dụng selection.call() cùng với một chức năng tái sử dụng như là một cách để làm khô lên mã của bạn:

var foo = d3.select('foo'); 
function redraw(someArray){ 
    var items = foo.selectAll('bar').data(someArray); 
    items.enter().append('bar').call(setEmAll); 
    items.exit().remove(); 
    items.call(setEmAll); 
} 

function setEmAll(myItems){ 
    myItems 
    .attr('foo',function(d){ return d*2 }) 
    .attr('bar',function(d){ return Math.sqrt(d)+17 }); 
} 

Như đã trình bày ở trên, .call() gọi một hàm và chuyển theo lựa chọn làm đối số để bạn có thể thực hiện cùng một cài đặt cho lựa chọn của mình ở nhiều vị trí.

+0

Câu trả lời thú vị, cảm ơn. Tôi vẫn đang cố gắng làm tròn đầu của tôi hầu hết những thứ này, nhưng đây sẽ là một sự giúp đỡ thực sự! – Richard

+2

Câu trả lời chu đáo. Bạn cũng có thể viết hàm 'redraw' theo cách tương thích với [selection.call] (https://github.com/mbostock/d3/wiki/Selections#wiki-call). Liên quan: [Hướng tới Biểu đồ có thể sử dụng lại] (http://bost.ocks.org/mike/chart/). – mbostock

+1

@mbostock Ooh, điều đó rất hữu ích; Tôi đã không nhìn thấy '.call()' trước đây. Tôi chỉ là một người mới làm quen với D3.js giúp tôi có thể. Tôi sẽ chỉnh sửa để bao gồm thông tin đó (và thực hiện đầy đủ thông qua các tài liệu để thử và gắn tất cả các khả năng vào đầu của tôi). – Phrogz

2

OK, giờ tôi đã hiểu! Bạn cần phải nói với nó NHỮNG GÌ để chuyển đổi:

circle.data(data1).transition().duration(2500).attr("cx", function(d) { 
     return d*10; 
    }); 
2

Một tài nguyên rất tốt để thêm dữ liệu mới vào hình ảnh hiện tại là hướng dẫn chính thức về cập nhật dữ liệu.

http://bl.ocks.org/mbostock/3808221

Vấn đề chính là bạn muốn xác định một chức năng quan trọng khi bạn gọi dữ liệu để d3 có thể theo dõi trong đó dữ liệu mới, vs mà dữ liệu cũ.

Các vấn đề liên quan