Day/Hour Heatmap + mySQL - javascript

I'm trying to fit my dataset into the example here: (http://bl.ocks.org/tjdecke/5558084).
Here's my d3js:
var margin = { top: 50, right: 0, bottom: 100, left: 30 },
width = 600 - margin.left - margin.right,
height = 430 - margin.top - margin.bottom,
gridSize = Math.floor(width / 24),
legendElementWidth = gridSize*2,
buckets = 9,
colors = ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#25 3494","#081d58"], // alternatively colorbrewer.YlGnBu[9]
days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
times = ["1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a", "9a", "10a", "11a", "12a", "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p", "12p"];
d3.json("/test/heatmap.php", function(error, data) {
data.forEach(function(d) {
console.log(d);
day= +d.day;
hour= +d.hour;
value= +d.value;
});
},
function(error, data) {
var colorScale = d3.scale.quantile()
.domain([0, buckets - 1, d3.max(data, function (d) { return d.value; })])
.range(colors);
console.log(d3.max(data, function (d) { return d.value; }))
;
console.log("test");
var svg = d3.select(".wpd3-8-3").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 dayLabels = svg.selectAll(".dayLabel")
.data(days)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) { return i * gridSize; })
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize / 1.5 + ")")
.attr("class", function (d, i) { return ((i >= 0 && i <= 4) ? "dayLabel mono axis axis-workweek" : "dayLabel mono axis"); });
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", "middle")
.attr("transform", "translate(" + gridSize / 2 + ", -6)")
.attr("class", function(d, i) { return ((i >= 7 && i <= 16) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"); });
var heatMap = svg.selectAll(".hour")
.data(data)
.enter().append("rect")
.attr("x", function(d) { return (d.hour - 1) * gridSize; })
.attr("y", function(d) { return (d.day - 1) * gridSize; })
.attr("rx", 4)
.attr("ry", 4)
.attr("class", "hour bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.style("fill", colors[0]);
heatMap.transition().duration(1000)
.style("fill", function(d) { return colorScale(d.value); });
heatMap.append("title").text(function(d) { return d.value; });
var legend = svg.selectAll(".legend")
.data([0].concat(colorScale.quantiles()), function(d) { return d; })
.enter().append("g")
.attr("class", "legend");
legend.append("rect")
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height)
.attr("width", legendElementWidth)
.attr("height", gridSize / 2)
.style("fill", function(d, i) { return colors[i]; });
legend.append("text")
.attr("class", "mono")
.text(function(d) { return "≥ " + Math.round(d); })
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height + gridSize);
});
And an example of my php echo:
[{"day":"4","hour":"0","value":"91"},{"day":"7","hour":"0","value":"93"},{"day":"3","hour":"1","value":"71"},{"day":"4","hour":"1","value":"90"},{"day":"3","hour":"2","value":"96"},{"day":"4","hour":"2","value":"89"},{"day":"5","hour":"2","value":"88"},{"day":"3","hour":"3","value":"97"},{"day":"4","hour":"3","value":"88"},{"day":"5","hour":"3","value":"88"},{"day":"6","hour":"3","value":"89"},{"day":"4","hour":"4","value":"86"},{"day":"5","hour":"4","value":"86"},{"day":"6","hour":"4","value":"88"},{"day":"7","hour":"4","value":"91"},{"day":"4","hour":"5","value":"86"},{"day":"6","hour":"5","value":"88"},{"day":"7","hour":"5","value":"91"},{"day":"6","hour":"6","value":"88"},{"day":"7","hour":"6","value":"91"},{"day":"6","hour":"7","value":"86"},{"day":"6","hour":"8","value":"86"},{"day":"6","hour":"9","value":"86"},{"day":"6","hour":"10","value":"86"},{"day":"6","hour":"11","value":"85"},{"day":"3","hour":"12","value":"91"},{"day":"4","hour":"12","value":"82"},{"day":"5","hour":"12","value":"82"},{"day":"6","hour":"12","value":"84"},{"day":"4","hour":"13","value":"82"},{"day":"6","hour":"13","value":"85"},{"day":"6","hour":"14","value":"87"},{"day":"6","hour":"15","value":"88"},{"day":"6","hour":"16","value":"88"},{"day":"6","hour":"17","value":"88"},{"day":"6","hour":"18","value":"88"},{"day":"6","hour":"19","value":"89"},{"day":"6","hour":"20","value":"90"},{"day":"6","hour":"21","value":"91"},{"day":"6","hour":"22","value":"91"},{"day":"6","hour":"23","value":"93"}]
I am stumped, because when I log the results of the d3.json, it matches the results of the example file. The thing I've noticed is in the example, the
console.log(d3.max(data, function (d) { return d.value; }))
loads with the max value. When I load my values, console.log doesn't seem to print beyond the d3.json step.
Any advice? I'm a bit stumped after a few days of reading and experimenting.
Thanks!

You have 2 functions in the json call (one for the error and one for the success?). You only need one - you first check if the error is there and do an error handling.
So you could remove this
function(error, data) {
data.forEach(function(d) {
console.log(d);
day= +d.day;
hour= +d.hour;
value= +d.value;
});
},
Apart from that, it's mostly good - just that you'll need to adjust the x attr call a bit - you don't need the -1
.attr("x", function (d) { return (d.hour) * gridSize; })

Related

d3 v4 Stacked to Grouped Bar Chart from CSV

Reference Mike Bostick's Stacked to Grouped Bar Chart example, I am modifying it to work with a CSV file. I have been working on this for a couple weeks and have looked through countless examples on Stack Overflow and elsewhere and am stumped.
The stacked bar chart works.
Stacked Bar Chart:
When I transition to a grouped bar chart I am only having issues referencing the key or series that is stacked or grouped. Right now all the rectangles display on top of each other instead of next to each other.
Grouped Bar Chart:
In the function transitionStep2() I want to multiply by a number corresponding to the series or key. I am currently multiplying by the number 1 in this function as a placeholder .attr("x", function(d) { return x(d.data.Year) + x.bandwidth() / 7 * 1; }).
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<html><body>
<form>
<label><input type="radio" name="mode" style="margin-left: 10" value="step1" checked>1</label>
<label><input type="radio" name="mode" style="margin-left: 20" value="step2">2</label>
</form>
<svg id = "bar" width = "500" height = "300"></svg>
<script>
var svg = d3.select("#bar"),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.08);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal()
.range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);
d3.csv("data.csv", function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}, function(error, data) {
if (error) throw error;
var keys = data.columns.slice(1);
x.domain(data.map(function(d) { return d.Year; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
color.domain(keys);
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth());
rect = g.selectAll("rect");
});
d3.selectAll("input")
.on("change", changed);
function changed() {
if (this.value === "step1") transitionStep1();
else if (this.value === "step2") transitionStep2();
}
function transitionStep1() {
rect.transition()
.attr("y", function(d) { return y(d[1]); })
.attr("x", function(d) { return x(d.data.Year); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
}
function transitionStep2() {
rect.transition()
.attr("x", function(d) { return x(d.data.Year) + x.bandwidth() / 7 * 1; })
.attr("width", x.bandwidth() / 7)
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("stroke", "red");
}
</script></body></html>
And the csv file:
Year,A,B,C,D
1995,60,47,28,39
1996,29,56,99,0
1997,30,26,63,33
1998,37,16,48,0
1999,46,49,64,21
2000,78,88,81,57
2001,18,11,11,64
2002,91,76,79,64
2003,30,99,96,79
The example you're referring has linear values to group the chart into and so .attr("x", function(d, i) { return x(i) + x.bandwidth() / n * this.parentNode.__data__.key; }) works fine.
In your case, the columns/keys is not a linear scale but an ordinal value set that you have to set a scale for:
Refer simple d3 grouped bar chart
To do that i.e. set up a ordinal scale, here's what can be done:
var x1 = d3.scaleBand();
x1.domain(keys).rangeRound([0, x.bandwidth()]);
So this new scale would have a range of the x scale's bandwidth with the domain of ["A", "B", "C", "D"]. Using this scale to set the x attribute of the rects to group them:
.attr("x", function(d) {
return x(d.data.Year) + x1(d3.select(this.parentNode).datum().key);
})
where d3.select(this.parentNode).datum().key represent the column name.
Here's JSFIDDLE (I've used d3.csvParse to parse the data but I'm sure you'll get the point here. It's just the x attribute you need to reset.
Here's a Plunkr that uses a file.
Here's a code snippet as well:
var svg = d3.select("#bar"),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.08);
var x1 = d3.scaleBand();
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal()
.range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);
var csv = 'Year,A,B,C,D\n1995,60,47,28,39\n1996,29,56,99,0\n1997,30,26,63,33\n1998,37,16,48,0\n1999,46,49,64,21\n2000,78,88,81,57\n2001,18,11,11,64\n2002,91,76,79,64\n2003,30,99,96,79';
var data = d3.csvParse(csv), columns = ["A", "B", "C", "D"];
data.forEach(function(d) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
});
var keys = columns;
x.domain(data.map(function(d) { return d.Year; }));
x1.domain(keys).rangeRound([0, x.bandwidth()]);
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
color.domain(keys);
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth());
rect = g.selectAll("rect");
d3.selectAll("input")
.on("change", changed);
function changed() {
if (this.value === "step1") transitionStep1();
else if (this.value === "step2") transitionStep2();
}
function transitionStep1() {
rect.transition()
.attr("y", function(d) { return y(d[1]); })
.attr("x", function(d) { return x(d.data.Year); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
}
function transitionStep2() {
rect.transition()
.attr("x", function(d) {
return x(d.data.Year) + x1(d3.select(this.parentNode).datum().key);
})
.attr("width", x.bandwidth() / 7)
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("stroke", "red");
}
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<html><body>
<form>
<label><input type="radio" name="mode" style="margin-left: 10" value="step1" checked>1</label>
<label><input type="radio" name="mode" style="margin-left: 20" value="step2">2</label>
</form>
<svg id = "bar" width = "500" height = "300"></svg>
Hope something helps. :)
I updated your examples so that it works,
see https://jsfiddle.net/mc5wdL6s/84/
function transitionStep2() {
rect.transition()
.duration(5500)
.attr("x", function(d,i) {
console.log("d",d);
console.log("i",i);
return x(d.data.Year) + x.bandwidth() / m / n * i; })
.attr("width", x.bandwidth() / n)
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("height", function(d) { return y(0) - y(d[1] - d[0]); })
.attr("stroke", "red");
}
You have to pass the index of the key to the individual rectangles and use this index to multiply with the reduced bandwith. If you also use the length of the keys array (instead of 7) you are CSV column count independent. You need to put the declaration of keys variable outside the d3.csv handler.
You forgot to stroke the initial rects green.
<script>
var svg = d3.select("#bar"),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.08);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal()
.range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);
var keys;
d3.csv("/data.csv", function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}, function(error, data) {
if (error) throw error;
keys = data.columns.slice(1);
x.domain(data.map(function(d) { return d.Year; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
color.domain(keys);
var stackData = d3.stack().keys(keys)(data);
stackData.forEach(element => {
var keyIdx = keys.findIndex(e => e === element.key);
element.forEach(e2 => { e2.keyIdx = keyIdx; });
});
g.append("g")
.selectAll("g")
.data(stackData)
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
rect = g.selectAll("rect");
});
d3.selectAll("input")
.on("change", changed);
function changed() {
if (this.value === "step1") transitionStep1();
else if (this.value === "step2") transitionStep2();
}
function transitionStep1() {
rect.transition()
.attr("y", function(d) { return y(d[1]); })
.attr("x", function(d) { return x(d.data.Year); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
}
function transitionStep2() {
rect.transition()
.attr("x", function(d, i) { return x(d.data.Year) + x.bandwidth() / (keys.length+1) * d.keyIdx; })
.attr("width", x.bandwidth() / (keys.length+1))
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("stroke", "red");
}
</script>

d3 add text in heatmap boxes

I am trying to plot an heatmap of my CSV file.
In each box of the heatmap I would like also to write the numerical value associated to that box.
This code does the job but the numbers are hidden behind the boxes. How can I change it so that the numbers appear above the boxes?
const margin = { top: 50, right: 0, bottom: 100, left: 130 },
xLocLabel = 10
width = 700 - margin.left - margin.right,
height = 3000 - margin.top - margin.bottom,
gridSize = Math.floor(width / 24),
legendElementWidth = gridSize*2,
buckets = 9,
colors = ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"], // alternatively colorbrewer.YlGnBu[9]
datasets = ["num_machines_full.csv"];
const svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right + 20)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const type = (d) => {
return {
username: d.UserName,
x: +d.Hour,
y: +d.Y,
value: +d.value
};
};
const heatmapChart = function(csvFile) {
d3.csv(csvFile, type, (error, data) => {
const colorScale = d3.scaleQuantile()
.domain([0, buckets - 1, d3.max(data, (d) => d.value)])
.range(colors);
console.log(data.length)
const cards = svg.selectAll(".boxColors")
.data(data, (d) => d.y+':'+d.x);
var valuesText = svg.selectAll(".valuesText")
.data(data)
.enter()
.append("text");
valuesText.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.attr("x", function(d){ return 35 + d.x*gridSize})
.attr("y", function(d){ return d.y*gridSize})
.text(function(d){ return d.value})
.attr("class", "parentText");
var users = [...new Set(data.map(function(d) {return d.username}))];
var firstOccurrence = users.map(function(d) {return data.find(function(e) {
return e.username === d})});
const yLabels = svg.selectAll(".yLabel")
.data(firstOccurrence)
.enter().append("text")
.text(function (d) { return d.username; })
.attr("x", xLocLabel)
.attr("y", (d, i) => (i+1) * gridSize)
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize / 1.5 + ")")
.attr("class", "dayLabel mono axis");
var times = [...new Set(data.map(function(d) {return d.x}))];
var firstOccurrence = times.map(function(d) {return data.find(function(e) {
return e.x === d})});
const xLabels = svg.selectAll(".xLabel")
.data(times)
.enter().append("text")
.text((d) => d)
.attr("x", (d, i) => (i+1) * gridSize)
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(" + gridSize / 2 + ", -6)")
.attr("class", "timeLabel mono axis");
cards.append("title");
cards.enter().append("rect")
.attr("x", (d) => (d.x + 1) * gridSize)
.attr("y", (d) => (d.y + 1) * gridSize)
.attr("rx", 4)
.attr("ry", 4)
.attr("class", "hour bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.style("fill", colors[0])
.merge(cards)
.transition()
.duration(1000)
.style("fill", (d) => colorScale(d.value));
cards.select("title").text((d) => d.value);
cards.exit().remove();
const legend = svg.selectAll(".legend")
.data([0].concat(colorScale.quantiles()), (d) => d);
const legend_g = legend.enter().append("g")
.attr("class", "legend");
legend_g.append("rect")
.attr("x", (d, i) => legendElementWidth * i)
.attr("y", height)
.attr("width", legendElementWidth)
.attr("height", gridSize / 2)
.style("fill", (d, i) => colors[i]);
legend_g.append("text")
.attr("class", "mono")
.text((d) => "≥ " + Math.round(d))
.attr("x", (d, i) => legendElementWidth * i)
.attr("y", height + gridSize);
legend.exit().remove();
});
};
heatmapChart(datasets[0]);
const datasetpicker = d3.select("#dataset-picker")
.selectAll(".dataset-button")
.data(datasets);
datasetpicker.enter()
.append("input")
.attr("value", (d) => "Dataset " + d)
.attr("type", "button")
.attr("class", "dataset-button")
.on("click", (d) => heatmapChart(d));
The final result should be similar to : https://i.stack.imgur.com/fUfuu.png
In an SVG whatever is painted last stays on top, just like a real painter using ink in a canvas.
That being said, this...
var valuesText = svg.selectAll(".valuesText")
.data(data)
.enter()
.append("text");
.... has to come after this:
cards.enter().append("rect")

d3.on("mouseover") occurring before mouseover

This is a d3 bar chart. line 162 ,
.on("mouseover", console.log("I am on it"))
should only happen when user is over a bar on the bar chart, but the log is output from the time the page loads.
The running code is here, https://shanegibney.github.io/d3Mouseover/
The full code is here, https://github.com/shanegibney/d3Mouseover
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
body {
font-family: avenir next, sans-serif;
font-size: 12px;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
.axis {
stroke-width: 0.5px;
stroke: #888;
font: 10px avenir next, sans-serif;
}
.axis>path {
stroke: #888;
}
</style>
<body>
<div id="totalDistance">
</div>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
/* Adapted from: https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172 */
var margin = {
top: 20,
right: 20,
bottom: 90,
left: 50
},
margin2 = {
top: 230,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
height2 = 300 - margin2.top - margin2.bottom;
var parseTime = d3.timeParse("%Y-%m-%d %H:%M");
var x = d3.scaleTime().range([0, width]),
x2 = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
y2 = d3.scaleLinear().range([height2, 0]),
dur = d3.scaleLinear().range([0, 12]);
var xAxis = d3.axisBottom(x).tickSize(0),
xAxis2 = d3.axisBottom(x2).tickSize(0),
yAxis = d3.axisLeft(y).tickSize(0);
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("start brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
d3.json("dataDefault.json", function(error, data) {
if (error) throw error;
var parseTime = d3.timeParse("%Y-%m-%d %H:%M");
var mouseoverTime = d3.timeFormat("%a %e %b %Y %H:%M");
var minTime = d3.timeFormat("%b%e, %Y");
var parseDate = d3.timeParse("%b %Y");
data.forEach(function(d) {
d.mouseoverDisplay = parseTime(d.date);
d.date = parseTime(d.date);
d.end = parseTime(d.end);
d.duration = ((d.end - d.date) / (60 * 1000)); // session duration in minutes
d.distance = +d.distance;
d.intensityInverted = (1 / (d.distance / d.duration)); // inverse of intensity so that the light colour is for low intensity and dark colour is for high intensity
d.intensity = Math.round(d.distance / d.duration); // actually intensity, metres per minute.
d.course = d.course.toLowerCase();
return d;
},
function(error, data) {
if (error) throw error;
});
var total = 0;
data.forEach(function(d) {
total = d.distance + total;
});
var minDate = d3.min(data, function(d) {
return d.date;
});
total = String(total).replace(/(.)(?=(\d{3})+$)/g, '$1,') //place thousands comma in total distance string
var xMin = d3.min(data, function(d) {
return d.date;
});
var yMax = Math.max(20, d3.max(data, function(d) {
return d.distance;
}));
dur.domain([0, d3.max(data, function(d) {
return d.duration;
})]);
var colorScale = d3.scaleSequential(d3.interpolateInferno)
.domain([0, d3.max(data, function(d) {
return d.intensityInverted;
})]);
var totalDistance = d3.select("#totalDistance").append("p").text("Total distance: " + total + "m");
x.domain([xMin, new Date()]);
y.domain([0, yMax]);
x2.domain(x.domain());
y2.domain(y.domain());
// append scatter plot to main chart area
var messages = focus.append("g");
messages.attr("clip-path", "url(#clip)");
messages.selectAll("message")
.data(data)
.enter().append("rect")
.style("fill", function(d) {
return colorScale(d.intensityInverted);
})
.attr("class", "message")
.attr("x", function(d) {
return x(d.date);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return dur(d.duration);
})
.attr("height", function(d) {
return height - y(d.distance);
})
// .on("mouseover", onit);
.on("mouseover", () => console.log("I am now on it for sure!"));
// function onit(d) {
// console.log("I am on it now!")
// }
focus.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
// Summary Stats
focus.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Distance in meters");
// focus.append("text")
// .attr("x", width - margin.right)
// .attr("dy", "1em")
// .attr("text-anchor", "end")
// // .text("Messages: " + num_messages(data, x));
// .text("Total distance: " + total + "m");
svg.append("text")
.attr("transform",
"translate(" + ((width + margin.right + margin.left) / 2) + " ," +
(height + margin.top + margin.bottom) + ")")
.style("text-anchor", "middle")
.text("Date");
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
// append scatter plot to brush chart area
var messages = context.append("g");
messages.attr("clip-path", "url(#clip)");
messages.selectAll("message")
.data(data)
.enter().append("rect")
.style("fill", function(d) {
return colorScale(d.intensityInverted);
})
.attr("class", "message")
.attr("x", function(d) {
return x2(d.date);
})
.attr("y", function(d) {
return y2(d.distance);
})
.attr("width", function(d) {
return dur(d.duration);
})
.attr("height", function(d) {
return height2 - y2(d.distance);
});
context.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
});
//create brush function redraw scatterplot with selection
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.selectAll(".message")
.attr("x", function(d) {
return x(d.date);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return dur(d.duration);
})
.attr("height", function(d) {
return height - y(d.distance);
});
focus.select(".x-axis").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
var e = d3.event.selection;
var selectedMessages = focus.selectAll('.message').filter(function() {
var xValue = this.getAttribute('x');
return e[0] <= xValue && xValue <= e[1];
});
// console.log(selectedMessages.nodes().length);
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.selectAll(".message")
// .enter().append("rect")
// .style("fill", function(d) {
// return colorScale(d.intensityInverted);
// })
// .attr("class", "message")
.attr("x", function(d) {
return x(d.date);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return dur(d.duration);
})
.attr("height", function(d) {
return height - y(d.distance);
});
focus.select(".x-axis").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
function handleMouseOver(d) {
d3.select(this)
.style("fill", "lightBlue");
g.select('text')
.attr("x", 15)
.attr("y", 5)
.text("Session no. " + d.number)
.append('tspan')
.text("Date: " + mouseoverTime(d.mouseoverDisplay))
.attr("x", 15)
.attr("y", 30)
.append('tspan')
.text("Distance: " + d.distance + "m")
.attr("x", 15)
.attr("y", 50)
.append('tspan')
.text("Duration: " + d.duration + " mins")
.attr("x", 15)
.attr("y", 70)
.append('tspan')
.text("Intensity: " + d.intensity + " meters/mins")
.attr("x", 15)
.attr("y", 90)
.append('tspan')
.text("Pool: " + d.pool + " (" + d.course + ")")
.attr("x", 15)
.attr("y", 110);
console.log("handleMouseOver function");
}
function handleMouseOut(d) {
d3.select(this)
.style("fill", function(d) {
return colorScale(d.intensityInverted);
});
g.select('text').text("Total distance since " + minTime(minDate) + ": " + total + "m");
}
</script>
Sample data,
[{
"number": "1",
"date": "2016-11-09 11:15",
"end": "2016-11-09 11:45",
"distance": "1100",
"course": "LC",
"pool": "UCD"
},
{
"number": "2",
"date": "2016-11-10 10:40",
"end": "2016-11-10 11:20",
"distance": "1500",
"course": "LC",
"pool": "UCD"
},
{
"number": "3",
"date": "2016-11-11 16:45",
"end": "2016-11-11 17:50",
"distance": "2000",
"course": "LC",
"pool": "UCD"
},
{
"number": "4",
"date": "2016-11-12 12:48",
"end": "2016-11-12 13:53",
"distance": "2500",
"course": "LC",
"pool": "UCD"
}
]
I added the last two lines here, but still no luck,
var messages = focus.append("g");
messages.attr("clip-path", "url(#clip)");
messages.selectAll("message")
.data(data)
.enter().append("rect")
.style("fill", function(d) {
return colorScale(d.intensityInverted);
})
.attr("class", "message")
.attr("x", function(d) {
return x(d.date);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return dur(d.duration);
})
.attr("height", function(d) {
return height - y(d.distance);
})
// .on("mouseover", onit);
.on("mouseover", () => console.log("I am now on it for sure!"));
// function onit(d) {
// console.log("I am on it now!")
// }
I suspect the problem is because I am using brushX() and zoom.
When you do this:
.on("mouseover", console.log("I am on it"))
You're passing the result of the console.log function to the callback. Instead of that, you want to pass its reference:
.on("mouseover", function(){
console.log("I am on it")
})
Check this snippet (don't hover over the circle!):
var circle = d3.select("circle");
circle.on("mouseover", console.log("I'm on it!"));
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg>
<circle cx="50" cy="50" r="15" fill="teal"></circle>
</svg>
Now the same code, with the reference to console.log:
var circle = d3.select("circle");
circle.on("mouseover", () => console.log("I'm on it!"));
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg>
<circle cx="50" cy="50" r="15" fill="teal"></circle>
</svg>
The issue is that you are calling the function at the time you are declaring it:
.on("mouseover", console.log("I am on it")) //function call
should be something like this:
.on("mouseover", console.log) //function ref
.on("mouseover", function(d) { console.log("I am on it") }) //function ref

D3.js placing text over arcs in a donut chart

I am trying to place text over a donut chart with inner rings created with D3. The plunker code can be accessed here: plunker
For placing the text over the arcs, I want to access the donutData array and place the values over each ring of the chart.
gs.append("text")
.attr("transform", function(d){
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function(d,i){
return donutData[i];
});
Since the donutData array stores the values of "actual" and "predicted" which it uses for creating the donut chart, I want those values to be placed over each of the arcs in the chart.
To add label to the center of the arc you need to add the label to the centroid.
gs.selectAll('text').data(function(d) {
return pie(d);
})
.enter().append("text")
.attr("transform", function(d, i, j) {//calculating the d as done in the path
k = arc.innerRadius(30 + donutWidth * j).outerRadius(donutWidth * (j + 1))(d);;
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) {
return d.data;
});
Working code here
Hope this helps!
You will have to group the arc paths and labels.
var gs = chart1.selectAll("g").data(donutData).enter().append("g");
var groups = gs.selectAll('.arc') //Created groups
.data(function(d) {
return pie(d);
})
.enter().append("g").classed("arc", true);
path = groups.append('path') //Append paths
.attr('d', function(d, i, j) {
return arc.innerRadius(30 + donutWidth * j).outerRadius(donutWidth * (j + 1))(d);
})
.attr('fill', function(d, i) {
return color(i);
})
.on("mouseover", function(d) {
d3.select(this).select("path").transition()
.duration(200)
.attr("d", arcOver);
})
.on("mouseout", function(d) {
d3.select(this).select("path").transition()
.duration(100)
.attr("d", arc);
});
groups.append("text") //Add labels
.attr("transform", function(d, i, j) {
var arc1 = arc.innerRadius(30 + donutWidth * j).outerRadius(donutWidth * (j + 1));
return "translate(" + arc1.centroid(d) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d, i) {
return d.data
});
// Code goes here
(function() {
var margin = {
top: 30,
right: 30,
bottom: 70,
left: 40
},
width = 580 - margin.left - margin.right,
height = 560 - margin.top - margin.bottom,
donutWidth = 100
inner = 70;
radius = Math.min(width, height) / 2;
var color = d3.scale.category10();
var pie = d3.layout.pie()
//.value(function(d){ return d.count;})
.sort(null);
var arc = d3.svg.arc();
//.innerRadius(radius - donutWidth)
//.outerRadius(radius - 50);
var arcOver = d3.svg.arc()
.innerRadius(inner + 5)
.outerRadius(radius + 5);
var chart1 = d3.select("#chartArea1").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
/* d3.csv("data.csv", function(error, data) {
if (error) throw error;*/
var data = [{
"Class": "Class A",
"Actual_Class": 495,
"Predicted_Class": 495,
"Accuracy": 100
}, {
"Class": "Class B",
"Actual_Class": 495,
"Predicted_Class": 492,
"Accuracy": 99
}, {
"Class": "Class C",
"Actual_Class": 495,
"Predicted_Class": 495,
"Accuracy": 100
}, {
"Class": "Class D",
"Actual_Class": 495,
"Predicted_Class": 495,
"Accuracy": 100
}, {
"Class": "Class E",
"Actual_Class": 495,
"Predicted_Class": 495,
"Accuracy": 100
}];
var donutData = new Array,
actualValues = new Array,
predValues = new Array;
data.forEach(function(d) {
actualValues.push(d.Actual_Class);
predValues.push(d.Predicted_Class);
});
console.log(data);
donutData.push(actualValues);
donutData.push(predValues);
console.log(donutData);
var gs = chart1.selectAll("g").data(donutData).enter().append("g");
var groups = gs.selectAll('.arc')
.data(function(d) {
return pie(d);
})
.enter().append("g").classed("arc", true);
path = groups.append('path')
.attr('d', function(d, i, j) {
return arc.innerRadius(30 + donutWidth * j).outerRadius(donutWidth * (j + 1))(d);
})
.attr('fill', function(d, i) {
return color(i);
})
.on("mouseover", function(d) {
d3.select(this).select("path").transition()
.duration(200)
.attr("d", arcOver);
})
.on("mouseout", function(d) {
d3.select(this).select("path").transition()
.duration(100)
.attr("d", arc);
});
groups.append("text")
.attr("transform", function(d, i, j) {
var arc1 = arc.innerRadius(30 + donutWidth * j).outerRadius(donutWidth * (j + 1));
return "translate(" + arc1.centroid(d) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d, i) {
return d.data
});
var legend = chart1.selectAll(".legend")
.data(color.domain().slice()) //.reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", 190)
.attr("y", -(margin.top) * 7 - 8)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", margin.right * 7)
.attr("y", -(margin.top) * 7)
.attr("dy", ".35em")
.text(function(d, i) {
return data[i].Class;
});
// });
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chartArea1"></div>

D3.js Enter/update/exit not adding elements as expected

I have a day/hour heatmap based on this example. I'm trying to write an updateHeatMap method so that I can dynamically update the heatmap with new data.
I'm using the enter/update/exit methods as suggested in several tutorials and here is the behavior that I am noticing. I open the page and the data is there, great. I update the data and it has fewer elements, so the old elements disappear, this is exactly what I want. I update the data again with new elements, and they do not appear, this is not what I want.
The initHeatMap function is only called once on load, the updateHeatMap function is called every time the data is updated.
I have changed the original day/hour heatmap example slightly to look like this:
$scope.heatMapData = $http.get(...
$scope.initHeatMap = function() {
$scope.margin = { top: 50, right: 0, bottom: 100, left: 30 },
width = 960 - margin.left - margin.right,
height = 430 - margin.top - margin.bottom,
gridSize = Math.floor(width / 24),
legendElementWidth = gridSize*2,
buckets = 9,
colors = ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"], // alternatively colorbrewer.YlGnBu[9]
days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
times = ["1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a", "9a", "10a", "11a", "12a", "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p", "12p"];
$scope.colorScale = d3.scale.quantile()
.domain([0, buckets - 1, d3.max($scope.heatMapData, function (d) { return d.value; })])
.range(colors);
var svg = d3.select("#heatmap").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 dayLabels = svg.selectAll(".dayLabel")
.data(days)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) { return i * gridSize; })
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize / 1.5 + ")")
.attr("class", function (d, i) { return ((i >= 0 && i <= 4) ? "dayLabel mono axis axis-workweek" : "dayLabel mono axis"); });
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", "middle")
.attr("transform", "translate(" + gridSize / 2 + ", -6)")
.attr("class", function(d, i) { return ((i >= 7 && i <= 16) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"); });
var heatMap = svg.selectAll(".hour")
.data($scope.heatMapData)
.enter().append("rect")
.attr("x", function(d) { return (d.hour - 1) * gridSize; })
.attr("y", function(d) { return (d.day - 1) * gridSize; })
.attr("rx", 4)
.attr("ry", 4)
.attr("class", "hour bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.style("fill", colors[0]);
heatMap.transition().duration(1000)
.style("fill", function(d) { return $scope.colorScale(d.value); });
heatMap.append("title").text(function(d) { return d.value; });
var legend = svg.selectAll(".legend")
.data([0].concat($scope.colorScale.quantiles()), function(d) { return d; })
.enter().append("g")
.attr("class", "legend");
legend.append("rect")
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height)
.attr("width", legendElementWidth)
.attr("height", gridSize / 2)
.style("fill", function(d, i) { return colors[i]; });
legend.append("text")
.attr("class", "mono")
.text(function(d) { return "≥ " + Math.round(d); })
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height + gridSize);
};
Here is the update method:
$scope.rowSelected() = function(){
$http.get(...).then(function(result){
$scope.heatMapData = result.data;
$scope.updateHeatMap();
});
}
$scope.updateHeatMap = function(){
var selection = d3.select("#heatmap");
var heatMap = selection.selectAll(".hour").data($scope.heatMapData);
heatMap.enter().append("rect")
.attr("x", function(d) { return (d.hour - 1) * gridSize; })
.attr("y", function(d) { return (d.day - 1) * gridSize; })
.attr("rx", 4)
.attr("ry", 4)
.attr("class", "hour bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.style("fill", colors[0]);
heatMap.transition().duration(1000)
.style("fill", function(d) {return $scope.colorScale(d.value);});
heatMap.exit().remove();
};
I solved the issue. In initHeatMap the svg element has "g" appended to it and then the heatMap variable is created within that "g".
var svg = d3.select("#heatmap").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 heatMap = svg.selectAll(".hour")...
So when I update the heatMap, I needed to select 'g' first before adding the data.
This is the solution:
var heatMap = selection.selectAll('g').selectAll(".hour")...

Categories