Related
I would love to implement a dropdown filter in my visualisation that allows me to filter by endorsed president. However, being very new to d3, I am really struggling. I have tried to implement it using code I have found elsewhere but to no avail.
var dataPath = "data/p.csv";
var dataPath2 = "data/e.csv";
var field1=[];
var field2=[];
d3.csv(dataPath2,function(data){
data.map(function(d){
field1.push(d.year);
field2.push(d.publication);
})
//called after the AJAX is success
console.log("field1",field1);
console.log("field2",field2);
console.log("field1",field1[0]);
var myData = data;
var margin = 150,
width = 1000 - margin,
height = 2000 - margin;
/*
* value accessor - returns the value to encode for a given data object.
* scale - maps value to a visual display encoding, such as a pixel position.
* map function - maps from data value to display value
* axis - sets up axis
*/
// setup x
var yValue = function(d) { return d.publication;}, // data -> value
yScale = d3.scale.ordinal().domain(field2).rangePoints([height, margin]); // value -> display
yMap = function(d) { return yScale(yValue(d));}, // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");
// setup y
var xValue = function(d) { return d.year;}, // data -> value
xExtent = d3.extent(data, function(d) {
return d.year;
});
xScale = d3.scale.linear().domain(xExtent).range([0,width-200]), // value -> display
xMap = function(d) { return xScale(xValue(d));}, // data -> display
xAxis = d3.svg.axis().scale(xScale).orient("bottom");
//
//
// // setup fill color
var cValue = function(d) { return d.endorsed;},
color = d3.scale.category10();
//
// // add the graph canvas to the body of the webpage
var svg = d3.select("body").append("svg")
.attr("width", width + margin)
.attr("height", height + margin)
.append("g")
.attr("transform", "translate(150)");
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis
.ticks(12))
.append("text")
.attr("class", "label")
.attr("x", width-200)
.attr("y", -6)
.style("text-anchor", "end")
.text("Year");
//
// // y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis
.ticks(50))
.append("text")
.attr("class", "label")
// .attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Publication");
// draw dots
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 4)
.attr("x", width - 10)
.attr("y", 95)
.attr("cx", xMap )
.attr("cy", yMap)
.style("fill", function(d) { return color(cValue(d));})
// if (d.endorsed == "Clinton") { return "red"}
// else {return "black"}; })
.on("mouseover", function(d) {
tooltip.transition()
.duration(1000)
.style("opacity", .9);
tooltip.html( d.endorsed
)
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(1000)
.style("opacity", 0);
});
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 10)
.attr("y", 95)
.attr("width", 10)
.attr("height", 10)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", width - 24)
.attr("y", 100)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
});
I had a similar requirement that I was able to solve with help of others, but you are going to need more than just d3. Take a look at using JQuery to create dropdown options, and you can filter your data based on selections.
I experience the weirdest problem:
If I draw my axis before my graph, the graph misses as much text elements as there are ticks in the axis. When I increase or decrease the number of ticks, the number of missing text elements increases or decreases alike.
If I draw my axis after my graph, everything is alright.
I want to draw the axis first, as I want the grid lines to appear below the graph. And first of all, I want to understand what is going on here.
Here the code snippet in question:
var generateVisualization = function() {
var margin = {top: 30, right: 10, bottom: 30, left: 10};
var width = 1000 - margin.left - margin.right;
var height = (dataset.length * 11) + 5;
var xScale = d3.scale.linear()
.domain([1850, 2020])
.range([0, width])
var xAxisBottom = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickSize(-height)
.tickFormat(d3.format("d")); // removes the comma as thousands separator
var svg = d3.select("body").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 + ")")
// Axis drawn first
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxisBottom);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(d.BeginDate);
})
.attr("y", function(d, i) {
return i * 11 + 3;
})
.attr("width", function(d, i) {
return xScale(d.EndDate) - xScale(d.BeginDate);
})
.attr("height", 4)
.attr("class", "line");
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d.DisplayName;
})
.attr("x", function(d, i) {
return xScale(d.BeginDate) + (xScale(d.EndDate) - xScale(d.BeginDate)) + 4;
})
.attr("y", function(d, i) {
return i * 11 + 8;
})
.attr("font-family", "sans-serif")
.attr("font-size", 8);
// Alternative: Axis drawn last
/*
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxisBottom);
*/
};
My hunch:
Instead of this:
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d.DisplayName;
})
do this:
svg.selectAll(".myLabel")//selection via class
.data(dataset)
.enter()
.append("text")
.attr("class", "myLabel")//adding a class to the label
.text(function(d) {
return d.DisplayName;
})
Reason: when you do
svg.selectAll("text")
It will select the text on ticks, and bind the data to it. This is the reason for the anomaly... you increase the ticks the labels displayed decreases.
The above solution will add the class myLabel to only the labels but not the tick text, so the problem should resolve.
I'm using D3 to present some data as a horizontal bar chart. Values will typically range between -10 and +10 on 8 different scales. I have the bars rendering as I want, but I can't work out how to add lables for each of the extreems of the axes.
so far I have:
but I want to achieve something like:
In other words a label for each extreme of each scale.
I have found lots of examples that add data labels to the bars them selves (e.g. the value), but I want to some how force the array of strings to be rendered at the extremes of the container.
At the moment, I am rendering the data from an array, and I have the labels stored in 2 other arrays e.g.
var data = [10, 5, -5, -10, 2, -2, 8, -8];
var leftLabels = ["label 1","label 2", ...];
var rightLabels = ["label 1", "label 2", ...];
Any ideas or links to examples most welcome.
I am not an expert in d3.js, but I think this can be easily done. There are different ways to go about it. I have created a pen for your use case.
I will paste the important part of the code below. In your chart, you will have to certainly make some adjustments to suit your needs. Feel free to play around with the values until you feel they are stable.
// Your array containing labels for left and right values
var leftSideData = ["left1", "left2", "left3", "left4", "left5", "left6", "left7", "left8"];
var rightSideData = ["right1", "right2", "right3", "right4", "right5", "right6", "right7", "right8"];
var left = svg.selectAll(".leftData")
.data(leftSideData)
.enter().append("g")
.attr("class", "leftVal")
.attr("transform", function(d, i) {
return "translate(0," + i * 57 + ")";
});
left.append("text")
.attr("x", 0)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
var right = svg.selectAll(".rightData")
.data(rightSideData)
.enter().append("g")
.attr("class", "rightVal")
.attr("transform", function(d, i) {
return "translate(0," + i * 57 + ")";
});
right.append("text")
.attr("x", width + 30)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
I won't say this is perfect, but I hope you get an idea about how to approach it. All the best!!
It's funny, just by asking the q on SE I find it helps me reformulate the problem.. and then some time later a new try yields a result. Anyone else find that?
I managed to make it work by changing the way the SVG was created. So I now have the following structure:
<SVG>
><g> (one for each bar)
>><text>
>><rect>
>><text>
><other stuff like axies>
It turns out that <text> elements cannot be added to <rect> elements (well they can, be added but they won't render).
the code is:
var data = [10,2,4,-10,...etc...];
var leftLabels = ["left 1","left 1", ...etc...];
var rightLabels = ["right 1","right 2", ...etc...];
//chart dimentions
var margin = { top: 20, right: 30, bottom: 40, left: 30 },
width = 600 - margin.left - margin.right,
barHeight = 30,
height = barHeight * data.length;
//chart bar scaling
var x = d3.scale.linear()
.range([100, width-100]);
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], 0.1);
var chart = d3.select(".chartsvg")
.attr("width", width + margin.left + margin.right)
.attr("height", barHeight * data.length + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain([d3.min(data), d3.max(data)]);
//append a g for each data item
var bar = chart.selectAll(".bar")
.data(data)
.enter()
.append("g");
//in each bar add a rect for the bar chart bar
bar.append("rect")
.attr("class", function (d) { return "bar--" + (d < 0 ? "negative" : "positive"); })
.attr("x", function (d) { return x(Math.min(0, d)); })
.attr("y", function (d, i) { return i* barHeight; })
.attr("width", function (d) { return Math.abs(x(d) - x(0)); })
.attr("height", barHeight-1);
//append the labels to each g using the label data
bar.append("text")
.data(rightLabels)
.attr("x", width)
.attr("y", function (d, i) { return (i * barHeight)+barHeight/2; })
.attr("dy", ".5em")
.attr("fill","steelblue")
.attr("text-anchor","end")
.text(function (d) { return d; });
bar.append("text")
.data(leftLabels)
.attr("x", 0)
.attr("y", function (d, i) { return (i * barHeight) + barHeight / 2; })
.attr("dy", ".5em")
.attr("fill","darkorange")
.attr("text-anchor", "start")
.text(function (d) { return d; });
//then append axis etc...
Formatting: something else to note. It turns out that to color the text in the label you need to use "stroke" and "fill" attributes. These are broadly equiv to the HTML "color" attribute on text.
I'm trying this graph:
with my data.
I wanted to rotate the x-axis labels.
Here's the code :
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d) { return d; })
.attr("x", function(d, i) { return i * gridSize; })
.attr("y", 0)
.style("text-anchor", "end")
.attr("transform", "translate(" + gridSize / 2 + ", -6)rotate(-90)")
.attr("class", function(d, i) { return ((i >= 8 && i <= 16) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"); });
But the result I get is. Entire labels get rotated to -90 deg in a single straight line. Instead I wanted each label to be rotated to -90 deg. Like this:
I even tried using .attr("transform", function(d) {return "translate(" + gridSize / 2 + ", -6)rotate(-90)"}) but it didn't help.
Result :
Help will be very much pleased.
The problem is that when you rotate an element, it gets rotated around the origin point, (0,0), of the local coordinate system. If the element you're rotating isn't right next to the origin, it can end up moved quite a large distance, and possibly moved outside the chart altogether.
There are two ways you can fix it:
Position the label with a "transform" attribute instead of with x and y attributes. That way, the label's coordinate system -- including the (0,0) point of rotation -- will be positioned with it, and the rotation will happen where you expect. The code #Manoj gave follows that system, but assumes you are using the default xAxis function. For your custom axis label code, your labels are given a y value of 0, and an x value determined from a function. You just need to move those values into the transform attribute, paying careful attention that the transformation of the overall position comes before the rotation and adjustment:
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d) { return d; })
.attr("transform", function(d, i) {
return "translate(" + ( i * gridSize) + ",0)"
+ "translate(" + gridSize / 2 + ", -6)rotate(-90)";
} )
.style("text-anchor", "end")
.attr("class", function(d, i) { return ((i >= 8 && i <= 16) ?
"timeLabel mono axis axis-worktime" :
"timeLabel mono axis");
});
Of course, you could combine those two translation statements, as:
.attr("transform", function(d, i) {
return "translate(" + ( (i + 0.5) * gridSize) + ",-6)"
+ "rotate(-90)")
Alternately, you can specify a center of rotation in the rotate statement, by including the x and y values of the point you want it to rotate around:
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d) { return d; })
.attr("x", function(d, i) { return i * gridSize; })
.attr("y", 0)
.attr("transform", function(d, i) {
return "translate(" + gridSize / 2 + ", -6)" +
"rotate(-90 "+ ((i + 0.5) * gridSize) + " " + (-6) +")";
} )
.style("text-anchor", "end")
.attr("class", function(d, i) { return ((i >= 8 && i <= 16) ?
"timeLabel mono axis axis-worktime" :
"timeLabel mono axis");
});
As you can see, this version is rather repetitive, as we have to calculate the position of the label twice -- once to position it, and once to set the center of rotation. You can see why most examples (and the d3 axis functions) position the labels with translations, so they can just be rotated in place.
Try this code:
Fiddle:
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("id", "x")
.attr("transform", "translate(0," + (h) + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function (d) {
return "rotate(-90)";
});
I'm a bit stuck, I created a D3 application that retrieves a bunch of sensor information from a database. I've made it so that it transitions and operates on a 30 second loop to update the information. It parses a Javascript Object and plots a line for each sensor on a graph. Everything seems to go well, but a couple of hours in the app will grind to a halt and stop updating. Then it complains about a script not responding. Here's the JS for the plot:
var thresholdTemp = 72;
var minutes = 5;
var transInterval;
//Main function to create a graph plot
function plot(date1, date2, interval) {
var data;
//If we define a date search parameter then we don't want to have it load interactive
if (date1 == undefined && date2==undefined) {
data = loadMinutesJSON(minutes);
} else {
data = searchJSON(date1, date2, interval);
}
var margin = {top: 20, right: 80, bottom: 60, left: 50},
width = 960 - margin.left - margin.right ,
height = 500 - margin.top - margin.bottom;
//Re-order the data to be more usable by the rest of the script
var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
//Convert the JSON to be better usable by D3
data = convertJSON(data);
//Nest the data so that it's grouped by serial number
data = d3.nest().key(function(d) { return d.serial; }).entries(data);
//Set the X domain to exist within the date and times given
var x = d3.time.scale().domain([getMinDate(data), getMaxDate(data)]).range([0, (width)]);
//Y Axis scale set to start at 45 degrees and go up to 30 degrees over highest temp
var y = d3.scale.linear()
.domain([
45,
getMaxTemp(data) + 10
])
.range([height, 0]);
//Set up the line colors based on serial number, this generates a color based on an ordinal value
var color = d3.scale.category20()
.domain(d3.keys(data[0]).filter(function(key) { return key === 'serial';}));
//Define where the X axis is
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%b %d %H:%M:%S"));
//Define where the Y axis is
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//When called creates a line with the given datapoints
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.date); })
y(function(d) { return y(d.reading); });
//An extra line to define a maximum temperature threshold
var threshold = d3.svg.line()
.x(function(d) { return x(d.date);})
.y(function(d) { return y(thresholdTemp); });
//Append the SVG to the HTML element
var svg = d3.select("#plot").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 + ")");
//Define the clipping boundaries
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", (width + margin.left + margin.right))
.attr("height", height + margin.top + margin.bottom);
//Add the X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform" , function (d) {return "rotate(-35)"});
//Add the Y axis and a label denoting it as temperature in F
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Temperature (F)");
//Create the lines based on serial number
var serial = svg.selectAll(".serial")
.data(data, function(d) { return d.key;})
.enter().append("g")
.attr("class", "serial");
//Add the extra line for a threshold and align it with the current time
var threshElement = svg.selectAll(".thresh")
.data(data, function(d) { return d.key;})
.enter().append("g")
.attr("class", ".thresh");
//Add the path to draw lines and clipping so they stay within bounds
var path = serial.append("path")
.attr("clip-path", "url(#clip)")
.attr("class", "line")
.attr("d", function (d) {return line(d.values);})
.style("stroke", function(d) {return color(d.key);});
//Custom path to add a line showing the temperature threshold
var threshpath = threshElement.append("path")
.attr("class", "line")
.attr("d", function(d) { return threshold(d.values);})
.style("stroke", "red");
//Add a label to the end of the threshold line denoting it as the threshold
threshElement.append("text")
.attr("transform", "translate(" + x(getMaxDate(data)) + "," + y(thresholdTemp + .5) + ")")
.attr("x", 3)
.attr("dy", ".35em")
.text("Threshold");
//Add the legend
plotLegend(data);
//Load in the new data and add it to the SVG
function transition() {
var newdata = loadMinutesJSON(minutes);
newdata = convertJSON(newdata);
console.log(newdata.length);
newdata = d3.nest().key(function(d) { return d.serial; }).entries(newdata);
if (data[0]["values"][0]["date"].toString() === newdata[0]["values"][0]["date"].toString()) { console.log("No New Data");return;}
//Merge the new data with the old and remove duplicates
data = merge(data,newdata);
//Chop off the preceding data that is no longer needed or visible
data = reduce(data, minutes);
x.domain([getMinDate(data), getMaxDate(data)]);
y.domain([45, getMaxTemp(data) + 10]);
d3.select(".x.axis")
.transition()
.duration(500).call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform" , function (d) {return "rotate(-35)"});
d3.select(".y.axis").transition().duration(500).call(yAxis);
var serial2 = svg.selectAll(".serial")
.data(data, function(d) { return d.key;});
serial2.enter().append("g").attr("class", "line");
var threshold2 = svg.selectAll(".thresh")
.data(data, function(d) { return d.key;});
threshold2.enter().append("g").attr("class", "line");
threshpath
.attr("d", function(d) {return threshold(d.values);})
.attr("transform", null)
.transition()
.duration(500)
.ease("linear");
threshElement.selectAll("text")
.attr("transform", "translate(" + x(getMaxDate(data)) + "," + y(thresholdTemp + .5 ) + ")")
.attr("x", 3)
.transition()
.duration(500)
.ease("linear");
path
.attr("d", function(d) { return line(d.values);})
.attr("transform", null)
.transition()
.duration(500)
.ease("linear");
//.attr("transform", "translate(" + x(0) + ")");
console.log(svg);
serial2.exit().remove();
threshold2.exit().remove();
//Remove the old, unused data
//data = reduce(data, minutes);
}
if(date1 == undefined && date2 == undefined) {
//Set the transition loop to run every 30 seconds
transInterval = setInterval(transition,30000);
}
}
Here's an example of the data structure:
[{"serial":"2D0008017075F210","date":"2013-08-23T14:35:19.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"serial":"2D0008017075F210","date":"2013-08-23T14:35:50.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"serial":"2D0008017075F210","date":"2013-08-23T14:36:20.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"serial":"2D0008017075F210","date":"2013-08-23T14:36:50.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"serial":"2D0008017075F210","date":"2013-08-23T14:37:20.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"serial":"2D0008017075F210","date":"2013-08-23T14:37:50.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"serial":"2D0008017075F210","date":"2013-08-23T14:38:20.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"serial":"2D0008017075F210","date":"2013-08-23T14:38:50.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"serial":"2D0008017075F210","date":"2013-08-23T14:39:20.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"serial":"2D0008017075F210","date":"2013-08-23T14:39:50.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"serial":"1D00080170496D10","date":"2013-08-23T14:35:19.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"serial":"1D00080170496D10","date":"2013-08-23T14:35:50.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"serial":"1D00080170496D10","date":"2013-08-23T14:36:20.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"serial":"1D00080170496D10","date":"2013-08-23T14:36:50.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"serial":"1D00080170496D10","date":"2013-08-23T14:37:20.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"serial":"1D00080170496D10","date":"2013-08-23T14:37:50.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"serial":"1D00080170496D10","date":"2013-08-23T14:38:20.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"serial":"1D00080170496D10","date":"2013-08-23T14:38:50.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"serial":"1D00080170496D10","date":"2013-08-23T14:39:20.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"serial":"1D00080170496D10","date":"2013-08-23T14:39:50.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"serial":"380008017037ED10","date":"2013-08-23T14:35:19.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"serial":"380008017037ED10","date":"2013-08-23T14:35:50.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"serial":"380008017037ED10","date":"2013-08-23T14:36:20.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"serial":"380008017037ED10","date":"2013-08-23T14:36:50.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"serial":"380008017037ED10","date":"2013-08-23T14:37:20.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"serial":"380008017037ED10","date":"2013-08-23T14:37:50.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"serial":"380008017037ED10","date":"2013-08-23T14:38:20.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"serial":"380008017037ED10","date":"2013-08-23T14:38:50.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"serial":"380008017037ED10","date":"2013-08-23T14:39:20.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"serial":"380008017037ED10","date":"2013-08-23T14:39:50.000Z","reading":74.3,"elevation":null,"room":null,"system":null}]
Watch your memory usage with the debugger. Remove code. Check if memory leak still occurs. Rinse and repeat.
Also, as a suggestion, don't use setInterval(). Use setTimeout() recursively instead.