I'm working with a modified version of Mike Bostock's Calendar Heatmap (https://bl.ocks.org/mbostock/4063318). In this version, I rotated the calendar and I'm just focusing on a few months (this was helpful: Tweak D3 calendar view example to be vertical with days be ordered left to right, then top to bottom?). See the attached image.
Does anyone have ideas on how to add small font day numbers to each of the day rectangles? They would be font size 8, and in the corners of each day's rectangle. I've been fiddling with it for hours and haven't gotten anywhere.
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var width = 550,
height = 750,
cellSize = 45;
var formatPercent = d3.format(".1%");
var color = d3.scaleQuantize()
.domain([0, 100])
.range(["#ffffff","#e6f7ff","#b3e6ff","#99ddff","#66ccff","#4dc3ff","#1ab2ff","#0077b3","#004466"]);
var month_strings = ["January", "February", "March"]
var svg = d3.select("body")
.selectAll("svg")
.data([2018])
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + ((width - cellSize * 5) / 2) + "," + (height - cellSize * 16 - 1) + ")");
svg.append("text")
.attr("transform", "translate(-10," + cellSize * 3 + ")rotate(-90)")
.attr("font-family", "sans-serif")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text(month_strings[0]);
svg.append("text")
.attr("transform", "translate(-10," + cellSize * 7 + ")rotate(-90)")
.attr("font-family", "sans-serif")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text(month_strings[1]);
svg.append("text")
.attr("transform", "translate(-10," + cellSize * 11 + ")rotate(-90)")
.attr("font-family", "sans-serif")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text(month_strings[2]);
var rect = svg.append("g")
.attr("fill", "none")
.attr("stroke", "#d2d4d8")
.selectAll("rect")
.data(function(d) { return d3.timeDays(new Date(2018, 0, 1), new Date(2018, 3, 1)); })
.enter().append("rect")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("class", "hour bordered")
.attr("rx", 4)
.attr("ry", 4)
//.attr("x", function(d) { return d3.timeWeek.count(d3.timeYear(d), d) * cellSize; })
//.attr("y", function(d) { return d.getDay() * cellSize; })
.attr("x", function(d) { return d.getDay() * cellSize;})
.attr("y", function(d) { return d3.timeWeek.count(d3.timeYear(d), d) * cellSize; })
.datum(d3.timeFormat("%Y-%m-%d"));
svg.append("g")
.attr("fill", "none")
.attr("stroke", "#000")
.selectAll("path")
.data(function(d) { return d3.timeMonths(new Date(2018, 0, 1), new Date(2018, 3, 1)); })
.enter().append("path")
.attr("d", pathMonth);
d3.csv("static/test.csv", function(error, csv) {
if (error) throw error;
var data = d3.nest()
.key(function(d) { return d.Date; })
.rollup(function(d) { return (d[0].Close - d[0].Open) / d[0].Open; })
.object(csv);
rect.filter(function(d) { return d in data; })
.attr("fill", function(d) { return color(data[d]); })
.append("title")
.text(function(d) { return d + ": " + formatPercent(data[d]); });
});
function pathMonth(t0) {
var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
d0 = t0.getDay(), w0 = d3.timeWeek.count(d3.timeYear(t0), t0),
d1 = t1.getDay(), w1 = d3.timeWeek.count(d3.timeYear(t1), t1);
return "M" + d0 * cellSize + "," + (w0) * cellSize
+ "H" + 7 * cellSize + "V" + (w1) * cellSize
+ "H" + (d1 + 1) * cellSize + "V" + (w1 + 1) * cellSize
+ "H" + 0 + "V" + (w0 + 1) * cellSize + "H"
+ d0 * cellSize + "Z";
}
var start_box = svg.append("rect")
.attr("x", 225)
.attr("y", 45)
.attr("width", cellSize)
.attr("height", cellSize)
.attr("rx", 4)
.attr("ry", 4)
.attr("class", "hour bordered")
.style("fill", "#FFD700");
</script>
Here is the CSV:
Date,Weekday,Open,Close
2018-01-01,0,1,0
2018-01-02,1,1,1
2018-01-03,2,1,2
2018-01-04,3,1,3
2018-01-05,4,1,4
2018-01-06,5,1,1
2018-01-07,6,1,1
2018-01-08,0,1,7
2018-01-09,1,1,8
2018-01-10,2,1,9
2018-01-11,3,1,10
2018-01-12,4,1,11
2018-01-13,5,1,1
2018-01-14,6,1,1
2018-01-15,0,1,14
2018-01-16,1,1,15
2018-01-17,2,1,16
2018-01-18,3,1,17
2018-01-19,4,1,18
2018-01-20,5,1,1
2018-01-21,6,1,1
2018-01-22,0,1,21
2018-01-23,1,1,22
2018-01-24,2,1,23
2018-01-25,3,1,24
2018-01-26,4,1,25
2018-01-27,5,1,1
2018-01-28,6,1,1
2018-01-29,0,1,28
2018-01-30,1,1,29
2018-01-31,2,1,30
2018-02-01,3,1,31
2018-02-02,4,1,32
2018-02-03,5,1,1
2018-02-04,6,1,1
2018-02-05,0,1,35
2018-02-06,1,1,36
2018-02-07,2,1,37
2018-02-08,3,1,38
2018-02-09,4,1,39
2018-02-10,5,1,1
2018-02-11,6,1,1
2018-02-12,0,1,42
2018-02-13,1,1,43
2018-02-14,2,1,44
2018-02-15,3,1,45
2018-02-16,4,1,46
2018-02-17,5,1,1
2018-02-18,6,1,1
2018-02-19,0,1,49
2018-02-20,1,1,50
2018-02-21,2,1,51
2018-02-22,3,1,52
2018-02-23,4,1,53
2018-02-24,5,1,1
2018-02-25,6,1,1
2018-02-26,0,1,56
2018-02-27,1,1,57
2018-02-28,2,1,58
2018-03-01,3,1,59
2018-03-02,4,1,60
2018-03-03,5,1,1
2018-03-04,6,1,1
2018-03-05,0,1,63
2018-03-06,1,1,64
2018-03-07,2,1,65
2018-03-08,3,1,66
2018-03-09,4,1,67
2018-03-10,5,1,1
2018-03-11,6,1,1
2018-03-12,0,1,70
2018-03-13,1,1,71
2018-03-14,2,1,72
2018-03-15,3,1,73
2018-03-16,4,1,74
2018-03-17,5,1,1
2018-03-18,6,1,1
2018-03-19,0,1,77
2018-03-20,1,1,78
2018-03-21,2,1,79
2018-03-22,3,1,80
2018-03-23,4,1,81
2018-03-24,5,1,1
2018-03-25,6,1,1
2018-03-26,0,1,84
2018-03-27,1,1,85
2018-03-28,2,1,86
2018-03-29,3,1,87
2018-03-30,4,1,88
2018-03-31,5,1,1
I'd do it like this:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var width = 550,
height = 750,
cellSize = 45;
var formatPercent = d3.format(".1%");
var color = d3.scaleQuantize()
.domain([0, 100])
.range(["#ffffff", "#e6f7ff", "#b3e6ff", "#99ddff", "#66ccff", "#4dc3ff", "#1ab2ff", "#0077b3", "#004466"]);
var month_strings = ["January", "February", "March"]
var svg = d3.select("body")
.selectAll("svg")
.data([2018])
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + ((width - cellSize * 5) / 2) + "," + (height - cellSize * 16 - 1) + ")");
svg.append("text")
.attr("transform", "translate(-10," + cellSize * 3 + ")rotate(-90)")
.attr("font-family", "sans-serif")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text(month_strings[0]);
svg.append("text")
.attr("transform", "translate(-10," + cellSize * 7 + ")rotate(-90)")
.attr("font-family", "sans-serif")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text(month_strings[1]);
svg.append("text")
.attr("transform", "translate(-10," + cellSize * 11 + ")rotate(-90)")
.attr("font-family", "sans-serif")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text(month_strings[2]);
var g = svg.append("g")
.attr("fill", "none")
.attr("stroke", "#d2d4d8")
.selectAll("g")
.data(function(d) {
return d3.timeDays(new Date(2018, 0, 1), new Date(2018, 3, 1));
})
.enter()
.append("g")
.attr("transform", function(d){
var x = d.getDay() * cellSize,
y = d3.timeWeek.count(d3.timeYear(d), d) * cellSize;
return "translate(" + x + "," + y + ")";
})
g.append("rect")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("class", "hour bordered")
.attr("rx", 4)
.attr("ry", 4)
.datum(d3.timeFormat("%Y-%m-%d"));
g.append("text")
.text(function(d){
return d.getDate();
})
.attr("y", cellSize)
.style("font-family", "arial")
.style("font-size", "8pt")
svg.append("g")
.attr("fill", "none")
.attr("stroke", "#000")
.selectAll("path")
.data(function(d) {
return d3.timeMonths(new Date(2018, 0, 1), new Date(2018, 3, 1));
})
.enter().append("path")
.attr("d", pathMonth);
d3.csv("static/test.csv", function(error, csv) {
if (error) throw error;
var data = d3.nest()
.key(function(d) {
return d.Date;
})
.rollup(function(d) {
return (d[0].Close - d[0].Open) / d[0].Open;
})
.object(csv);
rect.filter(function(d) {
return d in data;
})
.attr("fill", function(d) {
return color(data[d]);
})
.append("title")
.text(function(d) {
return d + ": " + formatPercent(data[d]);
});
});
function pathMonth(t0) {
var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
d0 = t0.getDay(),
w0 = d3.timeWeek.count(d3.timeYear(t0), t0),
d1 = t1.getDay(),
w1 = d3.timeWeek.count(d3.timeYear(t1), t1);
return "M" + d0 * cellSize + "," + (w0) * cellSize + "H" + 7 * cellSize + "V" + (w1) * cellSize + "H" + (d1 + 1) * cellSize + "V" + (w1 + 1) * cellSize + "H" + 0 + "V" + (w0 + 1) * cellSize + "H" + d0 * cellSize + "Z";
}
var start_box = svg.append("rect")
.attr("x", 225)
.attr("y", 45)
.attr("width", cellSize)
.attr("height", cellSize)
.attr("rx", 4)
.attr("ry", 4)
.attr("class", "hour bordered")
.style("fill", "#FFD700");
</script>
</body>
</html>
I'm relatively new to D3 and trying to add labels to a grouped bar chart.. With below implementation, I'm only able to see them under one group instead of both.
Below is how the data looks in the db:
category,Exceed,Fully Meets,Partially Meets,Does not meet
business,10,20,30,30
leadership,15,5,30,50
Below is the code:
var chart1 = d3.select("#svgarea2"),
margin = { top: 70, right: 0, bottom: 30, left: 40 },
width = +chart1.attr("width") - margin.left - margin.right,
height = +chart1.attr("height") - margin.top - margin.bottom,
g = chart1.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//chart background color
var bg = d3.select("g").append("svg")
// .attr("width", width + margin.right + margin.left)
.attr("width", 510 + "px")
// .attr("height", height + margin.top + margin.bottom);
.attr("height", 310 + "px");
bg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "#f8f8ff");
bg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//scale chart
var x0 = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.4);
var x1 = d3.scaleBand()
.padding(0.05);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal() //d3.schemeCategory20
// .range(["#0000ff", "#dcdcdc", "#696969", "#00008b"]);
.range(["#00008b", "#696969", "#dcdcdc", "#0000ff"]);
var columns = ['category', 'Does Not Meet', 'Partially Meets', 'Fully Meets', 'Exceed'];
var keys = columns.slice(1);
var color = d3.scaleOrdinal()
.range(["#00008b", "#696969", "#dcdcdc", "#0000ff"]);
x0.domain(data.map(function (d) { return d.category; }));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, 100]).nice();
g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function (d) { return "translate(" + x0(d.category) + ",0)"; })
.selectAll("rect")
.data(function (d) { return keys.map(function (key) { return { key: key, value: d[key] }; }); })
.enter().append("rect")
.attr("x", function (d) { return x1(d.key); })
.attr("y", function (d) {
return y(d.value);
})
.attr("width", x1.bandwidth() - 7)
.attr("height", function (d) { return height - y(d.value); })
.attr("fill", function (d) { return z(d.key); })
.on("mousemove", function(d){
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html((d.key) + "<br>" + (d.value) + "%");
});
g.append("g")
.selectAll("g")
.data(data).enter()
.append("g")
.attr("transform", function (d) { return keys })
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.attr("x", function (d) { return x0(d.category); })
.call(d3.axisBottom(x1))
.selectAll("text")
.attr("y", 15)
.attr("x", 0)
.attr("dy", ".35em")
.attr("transform", "rotate(50)")
.style("text-anchor", "start");;
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y).ticks(null, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) - 5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Employees (%)");
//chart title
g.append("text")
.attr("x", (width / 2) + 30)
.attr("y", 1 - (margin.top / 2) + 20)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.style("text-decoration", "underline")
.attr("font-family", "sans-serif")
.text("Performance Distribution");
};
Any help is appreciated!
It will work if you create a g element for each barchart, and then add you axes, bars etc to each. See here for example:
http://blockbuilder.org/tomshanley/aa5471a3ecaf9e41283d68188aecf042
Relevant code:
var chart = g.append("g")
.selectAll("g")
.data(data)
.enter().append("g");
chart.attr("transform", function (d) { return "translate(" + x0(d.category) + ",0)"; })
.selectAll("rect")
.data(function (d) { return keys.map(function (key) { return { key: key, value: d[key] }; }); })
.enter().append("rect")
.attr("x", function (d) { return x1(d.key); })
.attr("y", function (d) {
return y(d.value);
})
.attr("width", x1.bandwidth() - 7)
.attr("height", function (d) { return height - y(d.value); })
.attr("fill", function (d) { return z(d.key); })
//APPEND AN AXIS TO THE CHART G'S
chart.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.attr("x", function (d) { return x0(d.category); })
.call(d3.axisBottom(x1))
.selectAll("text")
.attr("y", 15)
.attr("x", 0)
.attr("dy", ".35em")
.attr("transform", "rotate(50)")
.style("text-anchor", "start");
I've been drawing a planetarium with planets and labels on it :
svg.append("circle").attr("r", 20).attr("cx", w/2)
.attr("cy", h/2).attr("class", "sun");
svg.append("text").attr("x", w/2)
.attr("y", h/2).attr("text-anchor", "middle").attr("fill", "grey").text("VICTOR HUGO");
var container = svg.append("g")
.attr("transform", "translate(" + w/2 + "," + h/2 + ")")
var orbit = container.selectAll(".orbit")
.data(dataset, key)
.enter()
.append("circle")
.attr("class", "orbit")
.attr("r", 0);
//function(d) {return (d.key * ((w/2)/151) + 20); });
var planets = container.selectAll(".planet")
.data(dataset, key)
.enter()
.append("circle")
.attr("class", "planet")
.attr("r", function(d) {return 5;})
.attr("cx", function(d) {return d.x; })
.attr("cy", function(d) {return d.y; })
.on("mouseover", function(d, i) {
stopTooltip = false
showTooltip(d);
});
d3.select("svg").on("click", function(d) {stopTooltip = true;});
var texts = container.selectAll(".text")
.data(dataset, key)
.enter()
.append("text")
.attr("class", "text")
.attr("dx", function(d) {return d.x;})
.attr("dy", ".35em")
.text(function(d) {return d.titre});
Then, I've been adding a legend and a click function, so that when you click on one or another of the legend subtitle, the dataset is updated (if you look at the tooltips, it is indeed updated).
////////////////////////Legend//////////////////////////////////
update = function(newDataset, key){
var newplanets = container.selectAll(".planet").data(newDataset, key);
newplanets.enter().append("circle");
newplanets.transition()
.duration(0)
.attr("r", function(d) {return 5;})
.attr("cx", function(d) {return d.x; })
.attr("cy", function(d) {return d.y; })
.on("mouseover", function(d, i) {
stopTooltip = false
showTooltip(d);
});
newplanets.exit().remove();
var newtexts = container.selectAll(".text").data(newDataset, key);
newtexts.enter().append("text");
newtexts.transition()
.duration(0)
.attr("class", "text")
.attr("dx", function(d) {return d.x;})
.attr("dy", ".35em")
.text(function(d) {return d.titre});
newtexts.exit().remove();
}
var legendRectSize = 7;
var legendSpacing = 20;
var VictorHugoClass = [
{ "VHClassName": '...Auteur (oeuvre)', "VHClass": r990o, "color": "#0000FF" },
{ "VHClassName": '...Auteur (expression)', "VHClass": r990e, "color": "#FBA10D" },
];
//Initiate container around Legend
var legendContainer = svg.append("g").attr("class","legendContainer")
.attr("transform", "translate(" + 30 + "," + (h - 90) + ")");
//Create title of Legend
var legendTitle = legendContainer.append('text')
.attr('x', 0)
.attr('y', legendRectSize - legendSpacing)
.attr("dy", "1em")
.attr('class', 'legendTitle')
.attr('transform', function() {
var height = legendRectSize + legendSpacing;
var offset = height * VictorHugoClass.length / 2;
var horz = -2 * legendRectSize;
var vert = -2.3 * height - offset;
return 'translate(' + horz + ',' + vert + ')';
})
.attr("fill", "black")
.text("Victor Hugo en tant que...")
.call(wrap, 200);
//Create container per circle/text pair
var legend = legendContainer
.selectAll('.legend')
.data(VictorHugoClass)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * VictorHugoClass.length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
})
.on("click", function(d) {
update(d.VHClass, d.key);
});
//Append circles to Legend
legend.append('circle')
.attr('r', legendRectSize)
.attr('cx', 4)
.attr('cy', legendRectSize/2 - legendSpacing)
.attr("opacity", 1)
.style("fill", "white")
.style('stroke', function(d) {return d.color;});
//Append text to Legend
legend.append('text')
.attr('x', legendRectSize + legendSpacing/2)
.attr('y', legendRectSize - legendSpacing)
.attr("fill", "black")
.text(function(d) { return d.VHClassName; });
Nevertheless, the labels won't update with the planets, and some of the data of the former dataset are still remaining when the second dataset is loaded into the visualization. My Plunker is here for more details : http://plnkr.co/edit/UnhqCZhME7ymxdIbpcIj?p=preview
Thank you very much for your help in this...