问题
I am having a hell of a time trying to update JSON data going to a D3 histogram after a button click.
While I solved the button click to update issue, the D3 javascript is now appending one chart per button click, resulting in duplicate charts instead of simply updating the data.
I understand that the click event is calling append()
in the D3 code every time it is triggered, but how can I get around this so there's only one chart with updated data per click?
console.log('chart.js loaded');
$(document).ready(function() {
var vimeoVideoId = $('p#vimeoVideoId').text();
var api = 'http://localhost:3001/videos/' + vimeoVideoId + '/json';
function initData() {
$('#all-notes').click(function() {
getData();
});
}
function getData() {
$.getJSON(api, draw);
}
function draw(json) {
data = json;
var duration = data.duration;
var timeToSec = function(data) {
notes = [];
// convert min:sec to seconds
for(i=0; i < data.notes.length; i++) {
var min_sec = data.notes[i].timecode;
var tt = min_sec.split(':');
var seconds = tt[0]*60+tt[1]*1;
notes.push(seconds);
}
return notes;
};
noteTimes = timeToSec(data);
console.log(noteTimes);
// Formatters for counts and times (converting numbers to Dates).
var formatCount = d3.format(",.0f"),
formatTime = d3.time.format("%H:%M"),
formatMinutes = function(d) { return formatTime(new Date(2012, 0, 1, 0, d)); };
var margin = {top: 10, right: 20, bottom: 30, left: 20},
width = 550;
height = 285;
var x = d3.scale.linear()
.domain([0, duration])
// .domain([0, d3.max(noteTimes)])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(50))
(noteTimes);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.y; })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(formatMinutes);
var svg = d3.select("#chartSet").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", x(data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); });
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", x(data[0].dx) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
}
initData();
});
回答1:
To handle creation and updates, you will have to reorganize how the draw
function is written.
function draw(json) {
// Initialization (few wasted CPU cycles)
// ...
// Update hook
var svg = d3.select("#chartSet").data([data]);
// Enter hook
var svgEnter = svg.enter();
// Enter cycle
svgEnter.append("svg")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Update cycle
svg
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
// Exit cycle (not really needed here)
svg.exit().remove();
// Update hook
var bar = svg.selectAll(".bar")
.data(data)
// Enter hook
var barEnter = bar.enter();
// Enter cycle
var barG_Enter = barEnter
.append("g")
.attr("class", "bar")
barG_Enter
.append("rect")
.attr("x", 1);
barG_Enter
.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("text-anchor", "middle");
// Update cycle
bar.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.select("rect")
.attr("width", x(data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); });
bar.select("text")
.attr("x", x(data[0].dx) / 2)
.text(function(d) { return formatCount(d.y); });
// Exit cycle
bar.exit().remove();
// Enter cycle
svgEnter.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Update cycle
svg.select('g.x.axis')
.call(xAxis);
}
This classical enter-update-exit pattern is improved upon in this article on making reusable graphs. This answer draws heavily on that pattern.
With a slightly better implementation which uses closures, you will be able to save the few CPU cycles wasted on initialization each time.
回答2:
You can either create the SVG outside the function that you call when the button is clicked, or check whether it exists and only add it if it does not exist. That is, something like
var svg = d3.select("svg");
if(svg.empty()) {
// code for appending svg
}
The same thing applies to the block of code at the end of your function that appends the x axis. For the bars, all you need to do is handle the update and exit selections in addition to the enter selection, i.e. set the dimensions and potentially remove elements based on the new data.
来源:https://stackoverflow.com/questions/15773140/updating-d3-chart-resulting-in-duplicate-charts