Reputation: 1521
In my code below I have two year calendars with two users displaying the same year on each calendar.
In the following code I've done a conditional fill checking if the date is a weekend and fills it gray, which works great.
.attr("fill", function(d) {
var value = new Date(d).getDay();
var check = (value === 6 || value === 0) // Check if weekend
if (check) return "#E0E0E0"
else return "none";
});
What I'd like to do now is fill a square red if a certain date is provided, for example:
if (d == "2018-10-12") return "red"
The problem here is that it obviously fills both calendars red since both have that date.
I'm unsure how i'd go about comparing the dates with an array of data for each svg, this is my test data that I'd like to compare the dates to.
var data = [
{id: "Some_Name", dates: ["2018-01-01", "2018-02-11"]}, // <-- svg1
{id: "Another_Name", dates: ["2018-03-01", "2018-04-11"]}, // <-- svg2
]
The above data snippet is arbitrary, if you have another data structure or even just a string to compare the dates to (for each svg) then that's fine too.
Here's all the code:
var formatDay = d3.timeFormat("%d");
var formatDate = d3.timeFormat("%Y-%m-%d");
var data = [
{id: "Some_Name", dates: ["2018-01-01", "2018-02-11"]},
{id: "Another_Name", dates: ["2018-03-01", "2018-04-11"]},
]
var width = 1000,
height = 196,
cellSize = 18,
transCell1 = ((width - cellSize * 53) / 2),
transCell2 = (height - cellSize * 7 - 1);
var currentYear = new Date().getFullYear()
var id = [...new Set(data.map(function(d) {
return d.id
}))];
var svg = d3.select("#calendars").selectAll("svg")
.data(id)
.enter().append("svg")
.attr("class", "myCalendar")
.attr("id", d => d)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform",
"translate(" + transCell1 + "," + transCell2 +")");
var names = svg.append("text")
.attr("dx", - 5)
.attr("dy", - 35)
.attr("font-size", 14)
.attr("text-anchor", "start")
.text(function(d) {
var value = d.split("_")
return value[0] + " " + value[1]
});
update(currentYear, id);
function update(year, user) {
var getDays = function() {
return d3.timeDays(
new Date(year, 0, 1), new Date(year + 1, 0, 1)
);
}
var fillDays = svg.selectAll(".fillDays")
.data(getDays)
fillDays.exit().remove()
fillDays = fillDays
.enter().append("rect")
.attr("class", "fillDays")
.attr("stroke", "#ccc")
.attr("width", cellSize)
.attr("height", cellSize)
.merge(fillDays)
.attr("x", function(d) {
return d3.timeWeek.count(d3.timeYear(d), d) * cellSize;
})
.attr("y", function(d) { return d.getDay() * cellSize; })
.datum(d3.timeFormat("%Y-%m-%d"))
.attr("fill", function(d) {
var value = new Date(d).getDay();
var check = (value === 6 || value === 0) // Check if weekend
if (check) return "#E0E0E0"
if (d == data[0].dates[0]) return "red" //
else return "none";
});
var days = svg.selectAll(".days")
.data(getDays)
days = days
.enter().append("text")
.attr("class", "days")
.attr("font-size", 11)
.attr("dy", 13)
.attr("dx", 3)
.style("pointer-events", "none")
.attr("fill", "#333")
.attr("stroke", "none")
.merge(days)
.attr("x", function(d) {
return d3.timeWeek.count(d3.timeYear(d), d) * cellSize;
})
.attr("y", function(d) { return d.getDay() * cellSize; })
.text(function(d) {
return formatDay(d)
})
var lines = svg.selectAll("path")
.data(function() {
return d3.timeMonths(
new Date(year, 0, 1), new Date(year + 1, 0, 1)
);
})
lines.exit().remove()
lines = lines
.enter().append("path")
.attr("fill", "none")
.attr("stroke", "#000")
.merge(lines)
.attr("d", pathMonth);
//
// Hardcoded text
//
var months = svg.selectAll(".months")
.data([
"Jan", "Feb", "Mar", "Apr", "Maj", "Jun",
"Jul", "Aug", "Sep", "Okt", "Nov", "Dec"])
months = months
.enter()
.append("text")
.attr("class", "months")
.attr("dx", function(d, i) { return i * 80 + 25;})
.attr("dy", - 6)
.attr("font-size", 12)
.attr("fill", "#333")
.attr("stroke", "none")
.merge(months)
.text(d => d)
var weeks = svg.selectAll(".weeks")
.data(["Sö", "Må", "Ti", "On", "To", "Fr", "Lö"])
weeks = weeks
.enter()
.append("text")
.attr("class", "weeks")
.attr("dy", function(d, i) { return i * 18 + 13;})
.attr("dx", -22)
.attr("font-size", 11)
.attr("fill", "#333")
.attr("stroke", "none")
.merge(weeks)
.text(d => d)
}
d3.selectAll(".year").on("click", function() {
update(Number(this.value), id)
})
d3.selectAll(".myCalendar").on("click", function() {
console.log(d3.select(this).property("id"))
})
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" + (w0 + 1) * cellSize + "," + d0 * cellSize
+ "H" + w0 * cellSize + "V" + 7 * cellSize
+ "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
+ "H" + (w1 + 1) * cellSize + "V" + 0
+ "H" + (w0 + 1) * cellSize + "Z";
}
body {
margin: auto;
width: 1000px;
font: 12px arial;
}
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<button class="year" value="2017">2017</button>
<button class="year" value="2018">2018</button>
<button class="year" value="2019">2019</button>
<div id="calendars"></div>
Upvotes: 1
Views: 539
Reputation: 102174
Regarding your own answer: as a rule of thumb do not use loops in a D3 code. Most of time they are unnecessary or, even worse, they can be a hindrance or even make things break.
The easier and idiomatic solution here is getting the datum bound to each SVG.
For start, do not bind that array with just two strings. Instead of that, bind the data
array itself, which contains the dates:
var svg = d3.select("#calendars").selectAll("svg")
.data(data)
//etc...
Then, it's just a matter of getting those dates for each user in the rectangles...
var userDates = d3.select(this.parentNode).datum().dates;
... and using the if
to fill them:
if (userDates.indexOf(d) > -1) return "red";
Here is the code with those changes only:
var formatDay = d3.timeFormat("%d");
var formatDate = d3.timeFormat("%Y-%m-%d");
var data = [{
id: "Some_Name",
dates: ["2018-01-01", "2018-02-11"]
},
{
id: "Another_Name",
dates: ["2018-03-01", "2018-04-11"]
},
]
var width = 1000,
height = 196,
cellSize = 18,
transCell1 = ((width - cellSize * 53) / 2),
transCell2 = (height - cellSize * 7 - 1);
var currentYear = new Date().getFullYear()
var svg = d3.select("#calendars").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "myCalendar")
.attr("id", d => d.id)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform",
"translate(" + transCell1 + "," + transCell2 + ")");
var names = svg.append("text")
.attr("dx", -5)
.attr("dy", -35)
.attr("font-size", 14)
.attr("text-anchor", "start")
.text(function(d) {
var value = d.id.split("_")
return value[0] + " " + value[1]
});
update(currentYear);
function update(year) {
var getDays = function() {
return d3.timeDays(
new Date(year, 0, 1), new Date(year + 1, 0, 1)
);
}
var fillDays = svg.selectAll(".fillDays")
.data(getDays)
fillDays.exit().remove()
fillDays = fillDays
.enter().append("rect")
.attr("class", "fillDays")
.attr("stroke", "#ccc")
.attr("width", cellSize)
.attr("height", cellSize)
.merge(fillDays)
.attr("x", function(d) {
return d3.timeWeek.count(d3.timeYear(d), d) * cellSize;
})
.attr("y", function(d) {
return d.getDay() * cellSize;
})
.datum(d3.timeFormat("%Y-%m-%d"))
.attr("fill", function(d) {
var userDates = d3.select(this.parentNode).datum().dates;
var value = new Date(d).getDay();
var check = (value === 6 || value === 0) // Check if weekend
if (check) return "#E0E0E0"
if (userDates.indexOf(d) > -1) return "red" //
else return "none";
});
var days = svg.selectAll(".days")
.data(getDays)
days = days
.enter().append("text")
.attr("class", "days")
.attr("font-size", 11)
.attr("dy", 13)
.attr("dx", 3)
.style("pointer-events", "none")
.attr("fill", "#333")
.attr("stroke", "none")
.merge(days)
.attr("x", function(d) {
return d3.timeWeek.count(d3.timeYear(d), d) * cellSize;
})
.attr("y", function(d) {
return d.getDay() * cellSize;
})
.text(function(d) {
return formatDay(d)
})
var lines = svg.selectAll("path")
.data(function() {
return d3.timeMonths(
new Date(year, 0, 1), new Date(year + 1, 0, 1)
);
})
lines.exit().remove()
lines = lines
.enter().append("path")
.attr("fill", "none")
.attr("stroke", "#000")
.merge(lines)
.attr("d", pathMonth);
//
// Hardcoded text
//
var months = svg.selectAll(".months")
.data([
"Jan", "Feb", "Mar", "Apr", "Maj", "Jun",
"Jul", "Aug", "Sep", "Okt", "Nov", "Dec"
])
months = months
.enter()
.append("text")
.attr("class", "months")
.attr("dx", function(d, i) {
return i * 80 + 25;
})
.attr("dy", -6)
.attr("font-size", 12)
.attr("fill", "#333")
.attr("stroke", "none")
.merge(months)
.text(d => d)
var weeks = svg.selectAll(".weeks")
.data(["Sö", "Må", "Ti", "On", "To", "Fr", "Lö"])
weeks = weeks
.enter()
.append("text")
.attr("class", "weeks")
.attr("dy", function(d, i) {
return i * 18 + 13;
})
.attr("dx", -22)
.attr("font-size", 11)
.attr("fill", "#333")
.attr("stroke", "none")
.merge(weeks)
.text(d => d)
}
d3.selectAll(".year").on("click", function() {
update(Number(this.value))
})
d3.selectAll(".myCalendar").on("click", function() {
console.log(d3.select(this).property("id"))
})
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" + (w0 + 1) * cellSize + "," + d0 * cellSize +
"H" + w0 * cellSize + "V" + 7 * cellSize +
"H" + w1 * cellSize + "V" + (d1 + 1) * cellSize +
"H" + (w1 + 1) * cellSize + "V" + 0 +
"H" + (w0 + 1) * cellSize + "Z";
}
body {
margin: auto;
width: 1000px;
font: 12px arial;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<button class="year" value="2017">2017</button>
<button class="year" value="2018">2018</button>
<button class="year" value="2019">2019</button>
<div id="calendars"></div>
Upvotes: 1
Reputation: 1521
So I came up with my own solution sort of based on jonnys comment, I include the data in the update arguments and make a forEach loop, select the svg based on id and use more or less the same logic as before.
allData.forEach(function(data, i) {
d3.selectAll("#" + user[i]).selectAll(".fillDays").attr("fill", function(d) {
var value = new Date(d).getDay();
var check = (value === 6 || value === 0) // Check if weekend
if (check) return "#E0E0E0"
if (data.dates.includes(d)) return "red"
else return "none";
})
})
This is the cleanest solution I could come up with for now, feel free to answer if you got a better one.
var formatDay = d3.timeFormat("%d");
var formatDate = d3.timeFormat("%Y-%m-%d");
var sqlData = [
{id: "Some_Name", dates: ["2018-01-01", "2018-02-12"]},
{id: "Another_Name", dates: ["2018-03-01", "2018-04-11"]},
]
var width = 1000,
height = 196,
cellSize = 18,
transCell1 = ((width - cellSize * 53) / 2),
transCell2 = (height - cellSize * 7 - 1);
var currentYear = new Date().getFullYear()
var id = [...new Set(sqlData.map(function(d) {
return d.id
}))];
var svg = d3.select("#calendars").selectAll("svg")
.data(id)
.enter().append("svg")
.attr("class", "myCalendar")
.attr("id", d => d)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform",
"translate(" + transCell1 + "," + transCell2 +")");
var names = svg.append("text")
.attr("dx", - 5)
.attr("dy", - 35)
.attr("font-size", 14)
.attr("text-anchor", "start")
.text(function(d) {
var value = d.split("_")
return value[0] + " " + value[1]
});
d3.selectAll(".year").on("click", function() {
update(Number(this.value), id, sqlData)
})
update(currentYear, id, sqlData);
function update(year, user, allData) {
var getDays = function() {
return d3.timeDays(
new Date(year, 0, 1), new Date(year + 1, 0, 1)
);
}
var fillDays = svg.selectAll(".fillDays")
.data(getDays)
fillDays.exit().remove()
fillDays = fillDays
.enter().append("rect")
.attr("class", "fillDays")
.attr("stroke", "#ccc")
.attr("width", cellSize)
.attr("height", cellSize)
.merge(fillDays)
.attr("x", function(d) {
return d3.timeWeek.count(d3.timeYear(d), d) * cellSize;
})
.attr("y", function(d) { return d.getDay() * cellSize; })
.datum(d3.timeFormat("%Y-%m-%d"))
var days = svg.selectAll(".days")
.data(getDays)
days = days
.enter().append("text")
.attr("class", "days")
.attr("font-size", 11)
.attr("dy", 13)
.attr("dx", 3)
.style("pointer-events", "none")
.attr("fill", "#333")
.attr("stroke", "none")
.merge(days)
.attr("x", function(d) {
return d3.timeWeek.count(d3.timeYear(d), d) * cellSize;
})
.attr("y", function(d) { return d.getDay() * cellSize; })
.text(function(d) {
return formatDay(d)
})
var lines = svg.selectAll("path")
.data(function() {
return d3.timeMonths(
new Date(year, 0, 1), new Date(year + 1, 0, 1)
);
})
lines.exit().remove()
lines = lines
.enter().append("path")
.attr("fill", "none")
.attr("stroke", "#000")
.merge(lines)
.attr("d", pathMonth);
//
// Hardcoded text
//
var months = svg.selectAll(".months")
.data([
"Jan", "Feb", "Mar", "Apr", "Maj", "Jun",
"Jul", "Aug", "Sep", "Okt", "Nov", "Dec"])
months = months
.enter()
.append("text")
.attr("class", "months")
.attr("dx", function(d, i) { return i * 80 + 25;})
.attr("dy", - 6)
.attr("font-size", 12)
.attr("fill", "#333")
.attr("stroke", "none")
.merge(months)
.text(d => d)
var weeks = svg.selectAll(".weeks")
.data(["Sö", "Må", "Ti", "On", "To", "Fr", "Lö"])
weeks = weeks
.enter()
.append("text")
.attr("class", "weeks")
.attr("dy", function(d, i) { return i * 18 + 13;})
.attr("dx", -22)
.attr("font-size", 11)
.attr("fill", "#333")
.attr("stroke", "none")
.merge(weeks)
.text(d => d)
allData.forEach(function(data, i) {
d3.selectAll("#" + user[i]).selectAll(".fillDays").attr("fill", function(d) {
var value = new Date(d).getDay();
var check = (value === 6 || value === 0) // Check if weekend
if (check) return "#E0E0E0"
if (data.dates.includes(d)) return "red"
else return "none";
})
})
}
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" + (w0 + 1) * cellSize + "," + d0 * cellSize
+ "H" + w0 * cellSize + "V" + 7 * cellSize
+ "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
+ "H" + (w1 + 1) * cellSize + "V" + 0
+ "H" + (w0 + 1) * cellSize + "Z";
}
body {
margin: auto;
width: 1000px;
font: 12px arial;
}
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<button class="year" value="2017">2017</button>
<button class="year" value="2018">2018</button>
<button class="year" value="2019">2019</button>
<div id="calendars"></div>
Upvotes: 0