I tried to simulate a real time chart with dynamic data using d3.js. I am running this using IE-10 browser.
My Source Code
I come across to a problem where the memory of my IE browser will be gradually increased if left the web application running for a period of time.
I Google searched the possible reason that caused this problem.
Two things come into my mind for discussion:
The timer prevents the garbage collection of IE
The d3 chart does not release memory after data.shift()
My question:
How could I diagnose if my problem actually originated from discussion 1 or 2 or neither?
How could I solve the memory problem?
You might need to download the code and run it with some time and monitor the iexplorer.exe using resource monitor in order to identify the problem.
Thank you.
Source Code:
<html>
<head>
<title>Animated Sparkline using SVG Path and d3.js</title>
<script src="http://mbostock.github.com/d3/d3.v2.js"></script>
<style>
/* tell the SVG path to be a thin blue line without any area fill */
path {
stroke: steelblue;
stroke-width: 1;
fill: none;
}
</style>
</head>
<body>
<span>
<b>Size:</b> 300x30 <b>Interpolation:</b> basis <b>Animation:</b> true <b>Transition:</b> 1000ms <b>Update Frequency:</b> 1000ms
<div id="graph1" class="aGraph" style="width:300px; height:30px;"></div>
</span>
<script>
var myTimer;
function FeedDataToChart(id, width, height, interpolation, animate, updateDelay, transitionDelay, data, startIndex) {
// create an SVG element inside the #graph div that fills 100% of the div
var graph = d3.select(id).append("svg:svg").attr("width", "80%").attr("height", "80%");
// X scale will fit values from 0-10 within pixels 0-100
var x = d3.scale.linear().domain([0, 48]).range([10, width-10]); // starting point is -5 so the first value doesn't show and slides off the edge as part of the transition
// Y scale will fit values from 0-10 within pixels 0-100
var y = d3.scale.linear().domain([0, 20]).range([height-10, 10]);
// create a line object that represents the SVN line we're creating
var line = d3.svg.line()
// assign the X function to plot our line as we wish
.x(function(d,i) {
// verbose logging to show what's actually being done
//console.log('Plotting X value for data point: ' + d + ' using index: ' + i + ' to be at: ' + x(i) + ' using our xScale.');
// return the X coordinate where we want to plot this datapoint
return x(i);
})
.y(function(d) {
// verbose logging to show what's actually being done
//console.log('Plotting Y value for data point: ' + d + ' to be at: ' + y(d) + " using our yScale.");
// return the Y coordinate where we want to plot this datapoint
return y(d);
})
.interpolate(interpolation)
var counter = startIndex;
//var myData = data.slice();
// display the line by appending an svg:path element with the data line we created above
graph.append("svg:path").attr("d", line(data));
// or it can be done like this
function redrawWithAnimation() {
// update with animation
graph.selectAll("path")
.data([data]) // set the new data
.attr("transform", "translate(" + x(1) + ")") // set the transform to the right by x(1) pixels (6 for the scale we've set) to hide the new value
.attr("d", line) // apply the new data values ... but the new value is hidden at this point off the right of the canvas
.transition() // start a transition to bring the new value into view
.ease("linear")
.duration(transitionDelay) // for this demo we want a continual slide so set this to the same as the setInterval amount below
.attr("transform", "translate(" + x(0) + ")"); // animate a slide to the left back to x(0) pixels to reveal the new value
}
function redrawWithoutAnimation() {
// static update without animation
graph.selectAll("path")
.data([data]) // set the new data
.attr("d", line); // apply the new data values
}
function stopTimer()
{
clearInterval(myTimer);
myTimer = null;
graph.selectAll("path").data([data]).remove().append("svg:path").attr("d", line);
buffer = null;
signalGenerator();
}
function startTimer()
{
if (myTimer == null)
{
myTimer = setInterval(function() {
if (counter < data.length - 1)
{
var v = data.shift(); // remove the first element of the array
data.push(v); // add a new element to the array (we're just taking the number we just shifted off the front and appending to the end)
if(animate)
{
redrawWithAnimation();
}
else
{
redrawWithoutAnimation();
}
counter++;
}
else
{
//alert("no more data in buffer");
stopTimer();
counter = startIndex;
}
}, updateDelay);
}
}
startTimer();
}
var buffer;
function signalGenerator()
{
if (buffer == null)
{
buffer = new Array(100);
var i;
for (i = 0; i < buffer.length; i++)
{
buffer[i] = Math.random() * 10;
}
FeedDataToChart("#graph1", 300, 300, "basis", true, 100, 100, buffer, 0);
}
}
function startGenerator()
{
signalGenerator();
}
startGenerator();
</script>
</body>
</html>
I tried as you said for 2 hours and it was initially 56 MB memory usage and in the end around 56.8 MB. It means only 0.8 MB difference with some exceptional cases. But I can help you finding the exact point where memory load is occurring. Just follow the steps one by one.
Open "Developer Tools" of IE by pressing F12
Go to Memory (A camera symbol or CTRL+7)
Click the Start Profiling Session ( Green Play button on top)
Take a Heap Snap Shot to create Base Line.
Now every 10 or 15 minutes take a heap snap shot
Do this for how many hours you require (In your case 2 hours)
Once profiling is done for desired time, stop it and analyze from beginning by comparing Heap Snap Shots.
If memory difference in the beginning and end is so big, check where this memory increase started by analyzing the memory difference in the snap shot.
Here you can check the difference of memory used by the process in terms of bytes or KB.
Check which function or variable or operation is creating the memory issue. Most probably some calculations that are repeatedly carried out so that the variables used in these calculations won't be released from a certain point of time. I saw some "Ba, p", "n, p", "Wa, n, p" etc when analyzed the memory flow. I believe the functions that use these variables are creating the problem for you.
Note
If you use the UI Responsiveness (CTRL+5), you can easily see that the Garbage Collection is carried out by IE automatically.
Related
because of my amount of data, I try to display them in few times, by smallest amount, thanks to requestAnimationFrame.
I'm new with this method and have some issue with it.
It works well for small database, with less than 1000 entries, that is smooth. But when I try it with bigger databases, the loop isn't smooth anymore.
I don't understand this slowness because normally render does the same thing, regardless of the size of "data".
function paths(data, ctx, count) {
var n = data.length,
i = 0,
reset = false;
var lastRun=0;
var fps;
function render() {
var max = d3.min([i+60, n]);
data.slice(i,max).forEach(function(d) {
d3.select(".foreground")
.append("path")
.attr("d", function(p){ return path(d);
})
.attr("stroke", "steelblue");
});
i = max;
console.log("end render");
};
(function animloop(){
console.log("animloop");
if (i >= n || count < brush_count) return;
lastRun = new Date().getTime();
requestAnimationFrame(animloop);
render();
})();
};
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) {
return [position(p), y[p](d[p])]; }));
}
I tried to see where the slowness comes from, thanks to console.log(), but actually the lapse is after render. On the console are printed blocks of "end render - animloop" / a lapse / "end render - animloop". I don't understand this lapse...
When I try to use the debugger step by step, I can't see any difference between the cases "few data" and "big data".
If someone sees a problem in my code or knows the origin of the problem, I'll be very grateful.
PS: speedness now : 50fps for 500 entries, 15fps for 7,000, 5fps for 20,000 (I don't need 60fps but 5 is really not enough).
If you don’t need your animation’s frame rate to be equal to display’s frame rate (or cannot provide acceptable performance), then consider skipping some frames (to prevent performing time-consuming computations at each frame) based on the time passed to the requestAnimationFrame()’s callback in the DOMHighResTimeStamp format.
I am using a couple of functions from Snap.SVG, mainly path2curve and the functions around it to build a SVG morph plugin.
I've setup a demo here on Codepen to better illustrate the issue. Basically morphing shapes simple to complex and the other way around is working properly as of Javascript functionality, however, the visual isn't very pleasing.
The first shape morph looks awful, the second looks a little better because I changed/rotated it's points a bit, but the last example is perfect.
So I need either a better path2curve or a function to prepare the path string before the other function builds the curves array. Snap.SVG has a function called getClosest that I think may be useful but it's not documented.
There isn't any documentation available on this topic so I would appreciate any suggestion/input from RaphaelJS / SnapSVG / d3.js / three/js developers.
I've provided a runnable code snippet below that uses Snap.svg and that I believe demonstrates one solution to your problem. With respect to trying to find the best way to morph a starting shape into an ending shape, this algorithm essentially rotates the points of the starting shape one position at a time, sums the squares of the distances between corresponding points on the (rotated) starting shape and the (unchanged) ending shape, and finds the minimum of all those sums. i.e. It's basically a least squares approach. The minimum value identifies the rotation that, as a first guess, will provide the "shortest" morph trajectories. In spite of these coordinate reassignments, however, all 'rotations' should result in visually identical starting shapes, as required.
This is, of course, a "blind" mathematical approach, but it might help provide you with a starting point before doing manual visual analysis. As a bonus, even if you don't like the rotation that the algorithm chose, it also provides the path 'd' attribute strings for all the other rotations, so some of that work has already been done for you.
You can modify the snippet to provide any starting and ending shapes you want. The limitations are as follows:
Each shape should have the same number of points (although the point types, e.g. 'lineto', 'cubic bezier curve', 'horizontal lineto', etc., can completely vary)
Each shape should be closed, i.e. end with "Z"
The morph desired should involve only translation. If scaling or rotation is desired, those should be applied after calculating the morph based only on translation.
By the way, in response to some of your comments, while I find Snap.svg intriguing, I also find its documentation to be somewhat lacking.
Update: The code snippet below works in Firefox (Mac or Windows) and Safari. However, Chrome seems to have trouble accessing the Snap.svg library from its external web site as written (<script...github...>). Opera and Internet Explorer also have problems. So, try the snippet in the working browsers, or try copying the snippet code as well as the Snap library code to your own computer. (Is this an issue of accessing third party libraries from within the code snippet? And why browser differences? Insightful comments would be appreciated.)
var
s = Snap(),
colors = ["red", "blue", "green", "orange"], // colour list can be any length
staPath = s.path("M25,35 l-15,-25 C35,20 25,0 40,0 L80,40Z"), // create the "start" shape
endPath = s.path("M10,110 h30 l30,20 C30,120 35,135 25,135Z"), // create the "end" shape
staSegs = getSegs(staPath), // convert the paths to absolute values, using only cubic bezier
endSegs = getSegs(endPath), // segments, & extract the pt coordinates & segment strings
numSegs = staSegs.length, // note: the # of pts is one less than the # of path segments
numPts = numSegs - 1, // b/c the path's initial 'moveto' pt is also the 'close' pt
linePaths = [],
minSumLensSqrd = Infinity,
rotNumOfMin,
rotNum = 0;
document.querySelector('button').addEventListener('click', function() {
if (rotNum < numPts) {
linePaths.forEach(function(linePath) {linePath.remove();}); // erase any previous coloured lines
var sumLensSqrd = 0;
for (var ptNum = 0; ptNum < numPts; ptNum += 1) { // draw new lines, point-to-point
var linePt1 = staSegs[(rotNum + ptNum) % numPts]; // the new line begins on the 'start' shape
var linePt2 = endSegs[ ptNum % numPts]; // and finished on the 'end' shape
var linePathStr = "M" + linePt1.x + "," + linePt1.y + "L" + linePt2.x + "," + linePt2.y;
var linePath = s.path(linePathStr).attr({stroke: colors[ptNum % colors.length]}); // draw it
var lineLen = Snap.path.getTotalLength(linePath); // calculate its length
sumLensSqrd += lineLen * lineLen; // square the length, and add it to the accumulating total
linePaths[ptNum] = linePath; // remember the path to facilitate erasing it later
}
if (sumLensSqrd < minSumLensSqrd) { // keep track of which rotation has the lowest value
minSumLensSqrd = sumLensSqrd; // of the sum of lengths squared (the 'lsq sum')
rotNumOfMin = rotNum; // as well as the corresponding rotation number
}
show("ROTATION OF POINTS #" + rotNum + ":"); // display info about this rotation
var rotInfo = getRotInfo(rotNum);
show(" point coordinates: " + rotInfo.ptsStr); // show point coordinates
show(" path 'd' string: " + rotInfo.dStr); // show 'd' string needed to draw it
show(" sum of (coloured line lengths squared) = " + sumLensSqrd); // the 'lsq sum'
rotNum += 1; // analyze the next rotation of points
} else { // once all the rotations have been analyzed individually...
linePaths.forEach(function(linePath) {linePath.remove();}); // erase any coloured lines
show(" ");
show("BEST ROTATION, i.e. rotation with lowest sum of (lengths squared): #" + rotNumOfMin);
// show which rotation to use
show("Use the shape based on this rotation of points for morphing");
$("button").off("click");
}
});
function getSegs(path) {
var absCubDStr = Snap.path.toCubic(Snap.path.toAbsolute(path.attr("d")));
return Snap.parsePathString(absCubDStr).map(function(seg, segNum) {
return {x: seg[segNum ? 5 : 1], y: seg[segNum ? 6 : 2], seg: seg.toString()};
});
}
function getRotInfo(rotNum) {
var ptsStr = "";
for (var segNum = 0; segNum < numSegs; segNum += 1) {
var oldSegNum = rotNum + segNum;
if (segNum === 0) {
var dStr = "M" + staSegs[oldSegNum].x + "," + staSegs[oldSegNum].y;
} else {
if (oldSegNum >= numSegs) oldSegNum -= numPts;
dStr += staSegs[oldSegNum].seg;
}
if (segNum !== (numSegs - 1)) {
ptsStr += "(" + staSegs[oldSegNum].x + "," + staSegs[oldSegNum].y + "), ";
}
}
ptsStr = ptsStr.slice(0, ptsStr.length - 2);
return {ptsStr: ptsStr, dStr: dStr};
}
function show(msg) {
var m = document.createElement('pre');
m.innerHTML = msg;
document.body.appendChild(m);
}
pre {
margin: 0;
padding: 0;
}
<script src="//cdn.jsdelivr.net/snap.svg/0.4.1/snap.svg-min.js"></script>
<p>Best viewed on full page</p>
<p>Coloured lines show morph trajectories for the points for that particular rotation of points. The algorithm seeks to optimize those trajectories, essentially trying to find the "shortest" cumulative routes.</p>
<p>The order of points can be seen by following the colour of the lines: red, blue, green, orange (at least when this was originally written), repeating if there are more than 4 points.</p>
<p><button>Click to show rotation of points on top shape</button></p>
Is there an example floating around of collision detection that avoids collision by manipulating radius rather than x,y coordinates? I'm aware of the examples Mike Bostock and others have put together, but I'm not using a force graph and my points are geographic and can't have their coordinates manipulated.
My best-guess implementation would be to begin with circles of radius 0, iterate over them and increase their individual radii as long as they don't collide with another circle. I think this would make a fantastic visualization, but I'm not sure how to efficiently determine whether one circle collides with another.
JSBin of my map with inline D3js (JavaScript tab is simply holding a 600kb GeoJSON dataset): http://jsbin.com/tapuhefamu/1/edit?html,output
Notice how the markers overlap when zoomed, it doesn't seem like a big deal in the fiddle (just zoom in further, right?) but the map I'm working with has ~2,000 pins clustered in only a few counties which need to display an informative DIV when clicked. Some pins are almost completely obscured and aren't able to be interacted with because of the overlap.
I have coded up something for you. Detecting the collision is pretty easy, basically calculate the distance between the two center points, and if the distance is less than the two radii added together, then they must have collided.
I had some issues with jsbin, so I've turned it into a gist, which you can view at http://bl.ocks.org/benlyall/6a81499abf7a0e2ad304
The interesting bits are:
Add a radiusStep parameter - use this to balance the trade off between the number of iterations, and the amount of potential overlap between nodes.
radiusStep = 0.01,
Remove the radius scaling from the zoom handler:
zoom = d3.behavior.zoom().on("zoom",function() {
g.attr("transform","translate("+ d3.event.translate.join(",")+")scale("+d3.event.scale+")");
//g.selectAll("circle")
//.attr("r", nodeRadius / d3.event.scale);
g.selectAll("path")
.style('stroke-width', countyBorderWidth / d3.event.scale )
.attr("d", path.projection(projection));
}),
Create a new structure to keep track of whether a node has collided with another, the radius and also the x and y position (pre calculated with your projection)
nodes = nodeGeoData.map(function(n) {
var pos = projection(n);
return {
collided: false,
x: pos[0],
y: pos[1],
r: 0
};
});
Two new functions to work with detecting the collision and expanding the radius until the collision is detected.
function tick() {
nodes.forEach(collided);
nodes.forEach(function(n) {
if (!n.collided) {
n.r += radiusStep;
if (n.r > nodeRadius) {
n.r = nodeRadius;
n.collided = true;
}
}
});
}
This tick function first calls collide on each node to determine if it has collided with any other. It then increases the radius by radiusStep of any node that has not collided. If the radius becomes larger than the nodeRadius parameter, then it sets the radius to that value and marks it as collided to stop it being increased.
function collided(node, i) {
if (node.collided) return;
nodes.forEach(function(n, j) {
if (n !== node) {
var dx = node.x - n.x, dy = node.y - n.y,
l = Math.sqrt(dx*dx+dy*dy);
if (l < node.r + n.r) {
node.collided = true;
n.collided = true;
}
}
});
}
The collided function checks each node to see if has collided with any other (except itself, for obvious reasons). If it detects a collision then both nodes in the comparison are marked as collided. To detect the actual collision the differences in the x and y position are calculated and then using Pythagoras the distance between them is calculated. If that distance is less than the radii of the two nodes added together, then a collision occurs.
The drawMap function is updated to calculate the radii before drawing the nodes.
while (nodes.filter(function(n) { return n.collided; }).length < nodes.length) {
tick();
}
This will basically just call the tick function until all nodes are marked as collided.
The drawNodes function is updated to use the new nodes data structure:
function drawNodes(nodes) {
g.selectAll('circle').data(nodes).enter().append("circle")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", function(d, i) { return d.r; })
.attr("class", "map-marker");
}
The changes here just reference the x, y and r attributes of each node object created earlier.
Though this works, and seems to be pretty effective, it is naive and will quickly get bogged down, since the combination of the tick and collided functions is O(n^2).
I'm using the line-with-focus chart ( View Finder ) example in nvd3. That means there's 3 or 4 lines ( series ) being drawn on the graph. When i hover over any of the lines I want to get back all the y-values for all lines of that given x-axis position ( for the most part these will be interpolated y-values per line ).
I see in the nv.models.lineWithFocusChart source code that using a callback for the elementMouseover.tooltip event I can get my data's x-value back for the data points on the line.
The closest part of the source code that does what i want is with the interactiveGuideline code for the lineChart examples. However, i don't want to create a <rect> overlay with elementMousemove interaction. I think i can modify this code to filter my data and get each line's y-value, but I'm sure there's an easier way I'm not seeing.
I think I'm on the right track, but just wondering if someone had this need before and found a quicker route than the rabbit hole I'm about jump in.
Thanks for feedback
This is the basic functionality you're looking for, it still needs a bit of finesse and styling of the tooltips. (Right now the tooltip blocks the view of the points...)
Key code to call after the drawing the chart in (for example, within the nv.addGraph function on the NVD3 live code site):
d3.selectAll("g.nv-focus g.nv-point-paths")
.on("mouseover.mine", function(dataset){
//console.log("Data: ", dataset);
var singlePoint, pointIndex, pointXLocation, allData = [];
var lines = chart.lines;
var xScale = chart.xAxis.scale();
var yScale = chart.yAxis.scale();
var mouseCoords = d3.mouse(this);
var pointXValue = xScale.invert(mouseCoords[0]);
dataset
.filter(function(series, i) {
series.seriesIndex = i;
return !series.disabled;
})
.forEach(function(series,i) {
pointIndex = nv.interactiveBisect(series.values, pointXValue, lines.x());
lines.highlightPoint(i, pointIndex, true);
var point = series.values[pointIndex];
if (typeof point === 'undefined') return;
if (typeof singlePoint === 'undefined') singlePoint = point;
if (typeof pointXLocation === 'undefined')
pointXLocation = xScale(lines.x()(point,pointIndex));
allData.push({
key: series.key,
value: lines.y()(point, pointIndex),
color: lines.color()(series,series.seriesIndex)
});
});
/*
Returns the index in the array "values" that is closest to searchVal.
Only returns an index if searchVal is within some "threshold".
Otherwise, returns null.
*/
nv.nearestValueIndex = function (values, searchVal, threshold) {
"use strict";
var yDistMax = Infinity, indexToHighlight = null;
values.forEach(function(d,i) {
var delta = Math.abs(searchVal - d);
if ( delta <= yDistMax && delta < threshold) {
yDistMax = delta;
indexToHighlight = i;
}
});
return indexToHighlight;
};
//Determine which line the mouse is closest to.
if (allData.length > 2) {
var yValue = yScale.invert( mouseCoords[1] );
var domainExtent = Math.abs(yScale.domain()[0] - yScale.domain()[1]);
var threshold = 0.03 * domainExtent;
var indexToHighlight = nv.nearestValueIndex(
allData.map(function(d){ return d.value}), yValue, threshold
);
if (indexToHighlight !== null)
allData[indexToHighlight].highlight = true;
//set a flag you can use when styling the tooltip
}
//console.log("Points for all series", allData);
var xValue = chart.xAxis.tickFormat()( lines.x()(singlePoint,pointIndex) );
d3.select("div.nvtooltip:last-of-type")
.html(
"Point: " + xValue + "<br/>" +
allData.map(function(point){
return "<span style='color:" + point.color +
(point.highlight? ";font-weight:bold" : "") + "'>" +
point.key + ": " +
chart.yAxis.tickFormat()(point.value) +
"</span>";
}).join("<br/><hr/>")
);
}).on("mouseout.mine", function(d,i){
//select all the visible circles and remove the hover class
d3.selectAll("g.nv-focus circle.hover").classed("hover", false);
});
The first thing to figure out was which objects should I bind the events to? The logical choice was the Voronoi path elements, but even when I namespaced the event names to avoid conflict the internal event handlers nothing was triggering my event handling function. It seems that a parent <g> event captures the mouse events before they can reach the individual <path> elements. However, it works just fine if instead I bind the events to the <g> element that contains the Voronoi paths, and it has the added benefit of giving me direct access to the entire dataset as the data object passed to my function. That means that even if the data is later updated, the function is still using the active data.
The rest of the code is based on the Interactive Guideline code for the NVD3 line graphs, but I had to make a couple important changes:
Their code is inside the closure of the chart function and can access private variables, I can't. Also the context+focus graph has slightly different names/functionality for accessing chart components, because it is made up of two charts. Because of that:
chart in the internal code is chart.lines externally,
xScale and yScale have to be accessed from the chart axes,
the color scale and the x and y accessor functions are accessible within lines,
I have to select the tooltip instead of having it in a variable
Their function is called with custom event as the e parameter that has already had the mouse coordinates calculated, I have to calculate them myself.
One of their calculations uses a function (nv.nearestValueIndex) which is only initialized if you create an interactive layer, so I had to copy that function definition into mine.
I think that about covers it. If there's anything else you can't follow, leave a comment.
I'm new to d3.js and still a beginner in javascript in general. I've got d3 correctly drawing a single SVG rectangle based on a single value in an array. When I increase the value of the number in the area via an input field and then call the reDraw function, the rectangle changes to the new size for just a second and then switches back to the initial size. I'm thinking I must have a scoping problem, but can't figure it out. Any ideas?
var dataset, h, reDraw, svg, w;
w = 300;
h = 400;
dataset = [1000];
// Create SVG element
svg = d3.select("#left").append("svg").attr("width", w).attr("height", h);
// set size and position of bar
svg.selectAll("rect").data(dataset).enter().append("rect").attr("x", 120).attr("y", function(d) {
return h - (d / 26) - 2;
}).attr("width", 60).attr("height", function(d) {
return d / 26;
}).attr("fill", "rgb(3, 100, 0)");
// set size and position of text on bar
svg.selectAll("text").data(dataset).enter().append("text").text(function(d) {
return "$" + d;
}).attr("text-anchor", "middle").attr("x", 150).attr("y", function(d) {
return h - (d / 26) + 14;
}).attr("font-family", "sans-serif").attr("font-size", "12px").attr("fill", "white");
// grab the value from the input, add to existing value of dataset
$("#submitbtn").click(function() {
localStorage.setItem("donationTotal", Number(localStorage.getItem("donationTotal")) + Number($("#donation").val()));
dataset.shift();
dataset.push(localStorage.getItem("donationTotal"));
return reDraw();
});
// redraw the rectangle to new size
reDraw = function() {
return svg.selectAll("rect").data(dataset).attr("y", function(d) {
return h - (d / 26) - 2;
}).attr("height", function(d) {
return d / 26;
});
};
You need to tell d3 how to match new data to existing data in the reDraw function. That is, you're selecting all rectangles in reDraw and then binding data to it without telling it the relation between the old and the new. So after the call to data(), the current selection (the one you're operating on) should be empty (not sure why something happens for you at all) while the enter() selection contains the new data and the exit() selection the old one.
You have several options to make this work. You could either operate on the enter() and exit() selections in your current setup, i.e. remove the old and add the new, or restructure your data such that you're able to match old and new. You could for example use an object with appropriate attributes instead of a single number. The latter has the advantage that you could add a transition from the old to the new size.
Have a look at this tutorial for some more information on data joins.
Edit:
Turns out that this was actually not the issue, see comments.