Horizontal bar chart exit method not working - javascript

I have a D3 horizontal bar chart that updates the data off a selection filter. When the chart first renders everything works as expected. When the filter selects any of the dropdowns the chart looks as expected, however when I select the "All" option again, the y-axis has the correct domain names and the x-axis resets to the correct scale. However, the problem occurs that the whole svg chart element is a grey block. If you hover over the bar the right size of the bar is selected, so I believe old data isn't exiting correctly, as can be seen in the image below.
I'm stuck as I thought my bars.exit() call was removing elements, I'm missing a key point in my understanding of what is happening. I think i'm very close but not sure what I'm missing?
****** UPDATE *********
Selecting all appears to insert an additional "rect" element that isn't present when it is first run.
Looking at the console log I can't understand where it is generated from as the array length is 3 not 4.
var topicData = [{
"name": "Vehicle - Poor",
"value": 5
},
{
"name": "Problems - Unresolved",
"value": 3
},
{
"name": "Reliability - Poor",
"value": 2
}
]
var margin = {
top: 10,
right: 10,
bottom: 100,
left: 200
}
var width = 1000 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("#graphic_two").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var time = 0;
var data;
// Set the X Label
var xLabel = g.append("text")
.attr("class", "x label")
.attr("y", height + 50)
.attr("x", width / 2)
.attr("font-size", "15px")
.attr("text-anchor", "middle")
.text("Topic Count");
// Set the Y Label
var yLabel = g.append("text")
.attr("class", "y axisLabel")
.attr("transform", "rotate(-90)")
.attr("y", -(120))
.attr("x", -350)
.attr("font-size", "15px")
.attr("text-anchor", "middle")
.text("Topic Names")
// Scales
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleBand()
.range([height, 0]);
// X-axis
var xAxisCall = d3.axisBottom()
var xAxis = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
// Y-axis
var yAxisCall = d3.axisLeft()
var yAxis = g.append("g")
.attr("class", "y axis");
//console.log(data);
formattedData = topicData;
update(formattedData)
$("#survey")
.on("change", function() {
update(formattedData);
})
function step() {
// At the end of our data, loop back
time = (time < 214) ? time + 1 : 0
update(formattedData);
}
function update(data) {
console.log("I'm in the update function")
var survey = $("#survey").val();
var data = data.filter(function(d) {
if (survey == "all") {
return true;
} else {
return d.name == survey;
}
})
console.log(data)
// X Scale
x.domain([0, d3.max(data, function(d) {
return d.value;
})]);
y.domain(data.map(function(d) {
return d.name;
}));
// Update axes
xAxisCall.scale(x);
xAxis.transition().call(xAxisCall);
yAxisCall.scale(y);
yAxis.transition().call(yAxisCall);
// JOIN new data with old elements
var bars = g.selectAll(".bars").data(data, function(d) {
return d.name;
});
// EXIT old elements not present in new data.
bars.exit()
.attr("class", "exit")
.remove();
// ENTER new elements present in new data.
bars.enter()
.append("rect")
.attr("width", function(d) {
return x(d.value);
}) // Width corresponds with the value from the data
.attr("y", function(d) {
return y(d.name);
}) // Maps the name to the bar
.attr("height", y.bandwidth())
.attr("fill", "grey")
.on("mouseover", function() {
d3.select(this)
.attr("fill", "blue");
})
.on("mouseout", function(d, i) {
d3.select(this)
.attr("fill", "grey");
})
.on('click', function click(element) {
selection = element.name;
url = "http://127.0.0.1:5000/table"
window.location = url;
window.localStorage.setItem("topic", selection)
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.jquery.min.js"></script>
<select id="survey" data-placeholder="Select..." class="chosen-select" style="width:350px;" tabindex="3">
<option selected value="all">All</option>
<option value="Vehicle - Poor">Vehicle - Poor</option>
<option value="Problems - Unresolved">Problems - Unresolved</option>
<option value="Reliability - Poor">Reliability - Poor</option>
</select>
<div class="col-md-12">
<div id="graphic_two"></div>
</div>

The accepted answer is not correct (removing elements before updating a D3 dataviz is definitely not the idiomatic way to do it) and, what's more, it doesn't explain why your exit selection was not working.
Your problem is just that you're selecting a class in your update selection...
var bars = g.selectAll(".bars")
//etc...
... but you never set that class in the enter selection. Therefore, just do:
bars = bars.enter()
.append("rect")
.attr("class", "bars")
Also, you have other problems:
You never change the update selection. Pay attention to the merge() in my code below;
You're changing the domain of the band scale. So, the width of each bar depends on the number of bars. I have a feeling that this is not what you want. If that's correct, refactor that part of the code. In my snippet below I'm using a simple paddingOuter math to change the width of the bars.
Here is your code with those changes:
var topicData = [{
"name": "Vehicle - Poor",
"value": 5
},
{
"name": "Problems - Unresolved",
"value": 3
},
{
"name": "Reliability - Poor",
"value": 2
}
];
var margin = {
top: 10,
right: 10,
bottom: 100,
left: 200
}
var width = 1000 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("#graphic_two").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var time = 0;
var data;
// Set the X Label
var xLabel = g.append("text")
.attr("class", "x label")
.attr("y", height + 50)
.attr("x", width / 2)
.attr("font-size", "15px")
.attr("text-anchor", "middle")
.text("Topic Count");
// Set the Y Label
var yLabel = g.append("text")
.attr("class", "y axisLabel")
.attr("transform", "rotate(-90)")
.attr("y", -(120))
.attr("x", -350)
.attr("font-size", "15px")
.attr("text-anchor", "middle")
.text("Topic Names")
// Scales
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleBand()
.range([height, 0]);
// X-axis
var xAxisCall = d3.axisBottom()
var xAxis = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
// Y-axis
var yAxisCall = d3.axisLeft()
var yAxis = g.append("g")
.attr("class", "y axis");
//console.log(data);
formattedData = topicData;
update(formattedData)
$("#survey")
.on("change", function() {
update(formattedData);
})
function step() {
// At the end of our data, loop back
time = (time < 214) ? time + 1 : 0
update(formattedData);
}
function update(data) {
var survey = $("#survey").val();
var data = data.filter(function(d) {
if (survey == "all") {
return true;
} else {
return d.name == survey;
}
})
// X Scale
x.domain([0, d3.max(data, function(d) {
return d.value;
})]);
y.domain(data.map(function(d) {
return d.name;
}))
.paddingOuter(1 / data.length);
// Update axes
xAxisCall.scale(x);
xAxis.transition().call(xAxisCall);
yAxisCall.scale(y);
yAxis.transition().call(yAxisCall);
// JOIN new data with old elements
var bars = g.selectAll(".bars").data(data, function(d) {
return d.name;
});
// EXIT old elements not present in new data.
bars.exit()
.attr("class", "exit")
.remove();
// ENTER new elements present in new data.
bars = bars.enter()
.append("rect")
.attr("class", "bars")
.merge(bars)
.attr("width", function(d) {
return x(d.value);
}) // Width corresponds with the value from the data
.attr("y", function(d) {
return y(d.name);
}) // Maps the name to the bar
.attr("height", y.bandwidth())
.attr("fill", "grey")
.on("mouseover", function() {
d3.select(this)
.attr("fill", "blue");
})
.on("mouseout", function(d, i) {
d3.select(this)
.attr("fill", "grey");
})
.on('click', function click(element) {
selection = element.name;
url = "http://127.0.0.1:5000/table"
window.location = url;
window.localStorage.setItem("topic", selection)
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.jquery.min.js"></script>
<select id="survey" data-placeholder="Select..." class="chosen-select" style="width:350px;" tabindex="3">
<option selected value="all">All</option>
<option value="Vehicle - Poor">Vehicle - Poor</option>
<option value="Problems - Unresolved">Problems - Unresolved</option>
<option value="Reliability - Poor">Reliability - Poor</option>
</select>
<div class="col-md-12">
<div id="graphic_two"></div>
</div>
PS: do not mix jQuery and D3. That's not only unnecessary but also harmful sometimes.

Related

Use of .on("mouseover", ...) in d3.js [duplicate]

This question already has answers here:
Why does click event handler fire immediately upon page load?
(4 answers)
Closed 4 years ago.
I'm getting introduced to javascript and I'm trying to use .on("mouseovert", ...) in order to get the x-value of my graph when the cursor is upon the graph.
My code look like this:
// do something as mouseover the graph
svg.select("svg")
.on("mouseover", alert("mouse on graph"));
The result is: an alert appears when I open the html file (and loading my js script), but nothing happen as is hover the graph.
Everything else in the script works fine.
Do you know why?
Thank you very much for the time you take!
Here is the full script:
function draw_co2(url) {
d3.select("svg").remove() //remove the old graph
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%d");
// Get the data
d3.json(url, function (error, data) {
if (error)
throw ('There was an error while getting geoData: ' + error);
data.forEach(function (d) {
d.Date = parseTime(d.Date);
d.Trend = +d.Trend;
});
// set the ranges // Scale the range of the data
var x = d3.scaleTime().domain([new Date("1960"), new Date("2015")]).range([0, width]);
var y = d3.scaleLinear()
.domain([d3.min(data, function (d) {
return d.Trend;
}) - 1 / 100 * d3.min(data, function (d) {
return d.Trend;
}), d3.max(data, function (d) {
return d.Trend;
}) + 1 / 100 * d3.min(data, function (d) {
return d.Trend;
})])
.range([height, 0]);
// define the line
var valueline = d3.line()
.x(function (d) {
return x(d.Date);
})
.y(function (d) {
return y(d.Trend);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#graph_draw").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 + ")");
//Y Axis label
svg.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Carbon dioxide (ppm)");
// Add the valueline path.
svg.append("path")
.data([data])
.style("opacity", 0)
.transition()
.duration(1000)
.style("opacity", 1)
.attr("class", "line")
.attr("d", valueline);
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
// gridlines in x axis function
function make_x_gridlines() {
return d3.axisBottom(x)
.ticks(10);
};
// add the X gridlines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_gridlines()
.tickSize(-height)
.tickFormat(""));
// do something as mouseover the graph
svg.select("svg")
.on("mouseover", alert("mouse on graph"));
})
}
Use mouser over as an inline function
svg.select("svg")
.on("mouseover", function () {
alert("mouse on graph")
});

D3 JS: How to add transition to refresh a plot when the dataset x-scale changes?

Looking at this Histogram chart using d3 example I plugged in my data but it had some strange side effects e.g. after refreshing to a new dataset, some information from the previous dataset i.e. x-axis scale was retained. I tried deleting and appending a new x-axis etc but nothing worked.
This happened due to the fact that my datasets had completely different x-axis ranges and scales. The only way I found to make it work was to select the whole svg element, remove it and re-append everything anew. However, this doesn't make a pleasant transition for the user so I was wondering how can this be improved to make it refreshable using transitions as in the original example even when having datasets with different x-scales and ranges.
This was my last approach which is a bit harsh to the eye:
// delete old
d3.select("#" + divId).select("svg").remove();
// then recreate all new
And this was my refresh attempt (integrated with AngularJS). Note how it has some common initialization and then if the SVG doesn't exist appends everything new otherwise tries to update it. I went bit by bit but can't see why the refresh doesn't remove all the previous dataset information of the x-axis scale:
var divId = $scope.histogramData.divId;
var color = $scope.histogramData.color;
var values = $scope.histogramData.data[$scope.histogramData.selected];
var svg = $scope.histogramData.svg;
// plot common initialization
var margin = {top: 40, right: 20, bottom: 20, left: 20},
width = 450 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
var max = d3.max(values);
var min = d3.min(values);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(10))
(values);
var yMax = d3.max(data, function(d){ return d.length });
var yMin = d3.min(data, function(d){ return d.length });
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// ===================================================================
// If the SVG doesn't exist then adds everything new
// ===================================================================
if (svg === undefined) {
var svg = d3.select("#" + divId)
.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 + ")");
$scope.histogramData.svg = svg;
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) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
var gTitle = svg.append("text")
.attr("x", 0)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "left")
.classed("label", true)
.text($scope.histogramData.spec[selected]);
$scope.histogramData.gTitle = gTitle;
var gAxis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
$scope.histogramData.gAxis = gAxis;
} else {
// ===================================================================
// If the SVG does exist then tries refreshing
// ===================================================================
var bar = svg.selectAll(".bar").data(data);
// remove object with data
bar.exit().remove();
bar.transition()
.duration(1000)
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.select("rect")
.transition()
.duration(1000)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.select("text")
.transition()
.duration(1000)
.text(function(d) { return formatCount(d.y); });
var gTitle = $scope.histogramData.gTitle;
gTitle.transition()
.duration(1000)
.text($scope.histogramData.spec[selected]);
var gAxis = $scope.histogramData.gAxis;
gAxis.transition()
.duration(1000)
.call(xAxis);
}
I would suggest to keep this d3 code inside one angularJS directive and keep a watch on the json which you are using to plot that graph. As soon as values are changing the directive will be called again and the graph will be plotted. Hope it helps.

d3 input resets radio buttons

I'm having a bar chart created with d3 that sorts itself whenever there's an input. I found it in the internet on http://bl.ocks.org/mbostock/3885705.
It looks like that:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
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 + ")");
d3.tsv("data.tsv", function(error, data) {
data.forEach(function(d) {
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
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("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.frequency - a.frequency; }
: function(a, b) { return d3.ascending(a.letter, b.letter); })
.map(function(d) { return d.letter; }))
.copy();
svg.selectAll(".bar")
.sort(function(a, b) { return x0(a.letter) - x0(b.letter); });
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.letter); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
});
I left out the CSS part as I don't think it matters. The problem is, that I also have multiple radio buttons in html like:
<div>
<input type="radio" name="radio" id="radio1" class="radio" onclick="radios('finance.csv')" />
<label for="radio1">Financial Situation</label>
</div>
Whenever the bars sort themselves, the radio buttons reset themselves. So I click on a radio button. It is marked for a few seconds, and then it jumps back to the first radio button. I guess its because of the "input" parts in:
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
How can I prevent the radios to reset themselves but still have my countries be sorted on input change?
Thanks in advance!
.property can be used as a getter or a setter. Here, you're using it as a setter, so when you do:
d3.select("input").property("checked", true)
You're setting all inputs to be checked. And since only one radio button is allowed to be checked, it checks the first.
I don't have the time to set up a fiddle and test, but I believe just using it as a getter would fix it:
d3.select("input").property("checked").each(change);
You could also be more precise with your selector:
d3.select("input[type=radio]:checked").each(change);
This has the benefit of not firing unpredictably if you add another input somewhere else on the page.

Cannot find D3.js memory leak

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.

Tooltips for multiple line graphs in D3

I am new to D3 and my requirement is to get multiple line graphs and provide tooltips for them.
I could get the multiple line graphs to appear but i am going wrong in getting multiple tooltip points.
I am new to javascript as well. So any help will be much appreciated.
Here is my code.
<script>
function showData(obj, d) {
var coord = d3.mouse(obj);
var infobox = d3.select(".infobox");
// now we just position the infobox roughly where our mouse is
infobox.style("left", (coord[0] + 200) + "px" );
infobox.style("top", (coord[1] - 130) + "px");
$(".infobox").html(d);
$(".infobox").show();
}
function hideData() {
$(".infobox").hide();
}
var xx,yy;
function xx(e) {
return x(e.date); };
function yy(e) {
return y(e.returns); };
var draw = function() {
var margin = {top:100,left:200,right:200,bottom:100},
width = 1150 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%m-%d").parse;
x = d3.time.scale().range([0,width]);
y = d3.scale.linear().range([height,0]);
//values of the axis is plotted here
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left");
var svg = d3.select("#chart").append("svg")
.attr("width" , width + margin.left + margin.right)
.attr("height" , height + margin.top + margin.bottom)
.attr("pointer-events" , "all")
.append("g")
//this is the line that positions the graph
.attr("transform" , "translate(" + margin.left + "," + margin.top +") ");
var activeReturns = new Array();
var passiveReturns = new Array();
var D3Obj = new Array();
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.returns);});
d3.json("d3.v3/sample1.json",function(error,result) {
result.data.forEach(function(d){
var arObj = new Object();
arObj.date = parseDate(d.date);
arObj.returns = +d.returns;
var prObj = new Object();
prObj.date = parseDate(d.date);
prObj.returns = +d.ticker_performance;
activeReturns.push(arObj);
passiveReturns.push(prObj);
});
D3Obj.push(activeReturns);
D3Obj.push(passiveReturns);
// this is where i tell that the line graph to be done
x.domain(d3.extent(D3Obj[0], function(d) {return d.date ;} ));
y.domain(d3.extent(D3Obj[0], function(d) {return d.returns ;} ));
svg.append("g")
.attr("class" , "x axis")
.call(xAxis)
.attr("transform","translate(0 ,"+ height + ")")
svg.append("g")
.attr("class" , "y axis")
//this is where yaxis line is added
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
svg.selectAll(".line")
.data(D3Obj)
.enter().append("path")
.attr("class","line")
.attr("d",line)
//this is where i am adding the tooltips
//tooltip for 1st line
svg.selectAll("circle")
.data(D3Obj[0])
.enter().append("circle")
.attr("fill", "red")
.attr("r", 2)
.attr("cx", xx)
.attr("cy", yy)
.on("mouseover", function(d) { showData(this, d.returns);})
.on("mouseout", function(){ hideData();});
//tooltip for 2nd line - this is where i think i am going wrong.
svg.selectAll("circle")
.data(D3Obj[1])
.enter().append("circle")
.attr("fill", "steelblue")
.attr("r", 2)
.attr("cx", xx)
.attr("cy", yy)
.on("mouseover", function(d) { showData(this, d.returns);})
.on("mouseout", function(){ hideData();});
});
$("#chart").append("<div class='infobox' style='display:none;'>Test</div>");
};
</script>
When you are creating the second point, nothing actually happens. The .data() function will try to match the data elements you pass to what you have selected (in this case one circle) and will succeed here. This means that your enter selection is empty and nothing happens.
The d3 way is to pass in all the data you want to use to create elements at once and handle accordingly in the functions to set attributes etc. That is, your code should look something like
svg.selectAll("circle")
.data(D3Obj)
.enter().append("circle")
.attr("fill", function(d, i) { if(i == 0) { return "red"; } else { return "steelblue"; } })
.attr("r", 2)
.attr("cx", xx)
.attr("cy", yy)
.on("mouseover", function(d) { showData(this, d.returns);})
.on("mouseout", function(){ hideData();});
This will create two circles and attach the corresponding listeners to them.

Categories

Resources