Robert Andersson
Robert Andersson

Reputation: 1521

Remove weekends in d3js calendar

In my snippet below I have a regular year calendar, what I've tried to do now is to exclude the weekends with the following code.

var day = function(d) { 
        if (d.getDay() == 0 || d.getDay() == 6) return null;
        return d.getDay(); 
    }

But it doesn't work as I would've hoped.

If this would require too big of a refactor in the monthPath function then I'll just look into a different type of calendar.

Here's the code:

var width = 960,
    height = 136,
    cellSize = 17,
    trans_1 = ((width - cellSize * 53) / 2),
    trans_2 = (height - cellSize * 7 - 1);

var day = function(d) { 
        // filter out weekends 
        if (d.getDay() == 0 || d.getDay() == 6) return null;
        return d.getDay(); 
    },
    week = d3.timeFormat("%U"),
    date = d3.timeFormat("%Y-%m-%d");

var svg = d3.select("body").selectAll("svg")
    .data([2018])
  .enter().append("svg")
    .attr("width", width)
    .attr("height", height)
  .append("g")
    .attr("transform", 
        "translate(" + trans_1 + "," + trans_2 + ")");

var rect = svg.selectAll(".day")
    .data(function(d) { 
        return d3.timeDays(
            new Date(d, 0, 1), new Date(d + 1, 0, 1));
    })
  .enter().append("rect")
    .attr("class", "day")
    .attr("width", cellSize)
    .attr("height", cellSize)
    .attr("x", d => week(d) * cellSize)
    .attr("y", d => day(d) * cellSize)
    .datum(date);

svg.selectAll(".month")
    .data(function(d) { 
        return d3.timeMonths(
            new Date(d, 0, 1), new Date(d + 1, 0, 1));
    })
  .enter().append("path")
    .attr("class", "month")
    .attr("d", monthPath);

function monthPath(t0) {
    var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
        d0 = +day(t0), w0 = +week(t0),
        d1 = +day(t1), w1 = +week(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 {
    padding-top: 25px;
    width: 1000px;
    margin: auto;
}
.day {
    fill: #fff;
    stroke: #ccc;
}
.month {
    fill: none;
    stroke: #000;
    stroke-width: 2px;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<meta charset="utf-8">

Upvotes: 2

Views: 233

Answers (1)

i alarmed alien
i alarmed alien

Reputation: 9520

To remove the weekends from your month path, first change the top and bottom vertical points. The previous minimum was 0 (for the top edge of day 0, Sunday), but since we are eliminating that, it will now be 1 (for day 1, Monday) * cellSize. The maximum was the bottom edge of Saturday, so day 6 + 1 (for the bottom edge) * cellSize, but since Saturday is being eliminated, the new bottom edge is Friday (day 5), i.e. 5+1 * cellSize. This gives:

    return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize
         + "H" + w0 * cellSize + "V" + 6 * cellSize // note change
         + "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
         + "H" + (w1 + 1) * cellSize + "V" + cellSize     // note change
         + "H" + (w0 + 1) * cellSize + "Z";

We also need to alter the first and last days of the month. If the first day falls on a Sunday, "round it up" to Monday:

    d0 = +day(t0) === 0 ? 1 : +day(t0)

Likewise, if the last day is a Saturday, "round it down" to a Friday:

    d1 = +day(t1) === 6 ? 5 : +day(t1)

Putting all that together, we get:

    var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
        d0 = +day(t0) === 0 ? 1 : +day(t0),
        w0 = +week(t0),
        d1 = +day(t1) === 6 ? 5 : +day(t1),
        w1 = +week(t1);
    return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize
         + "H" + w0 * cellSize + "V" + 6 * cellSize // note change
         + "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
         + "H" + (w1 + 1) * cellSize + "V" + cellSize     // note change
         + "H" + (w0 + 1) * cellSize + "Z";

var width = 960,
  height = 150,
  cellSize = 17,
  trans_1 = ((width - cellSize * 53) / 2),
  trans_2 = (height - cellSize * 7 - 1);

var day = function(d) {
    // filter out weekends 
    //            if (d.getDay() == 0 || d.getDay() == 6) return null;
    return d.getDay();
  },
  week = d3.timeFormat("%U"),
  date = d3.timeFormat("%Y-%m-%d");

var svg = d3.select("body").selectAll("svg")
  .data([2018])
  .enter().append("svg")
  .attr("width", width)
  .attr("height", height)
  .append("g")
  .attr("transform",
    "translate(" + trans_1 + "," + trans_2 + ")");

var rect = svg.selectAll(".day")
  .data(function(d) {
    return d3.timeDays(new Date(d, 0, 1), new Date(d + 1, 0, 1))
  })
  .enter().append("rect")
  .attr("class", function(d) {
    return "day _" + d.getDay()
  })
  .attr("width", cellSize)
  .attr("height", cellSize)
  .attr("x", d => week(d) * cellSize)
  .attr("y", d => day(d) * cellSize)
  .datum(date);

svg.selectAll(".month")
  .data(function(d) {
    return d3.timeMonths(
      new Date(d, 0, 1), new Date(d + 1, 0, 1));
  })
  .enter().append("path")
  .attr("class", "month")
  .attr("d", monthPath);

function monthPath(t0) {
  var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
    d0 = +day(t0) === 0 ? 1 : +day(t0),
    w0 = +week(t0),
    d1 = +day(t1) === 6 ? 5 : +day(t1),
    w1 = +week(t1);
  return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize +
    "H" + w0 * cellSize + "V" + 6 * cellSize // note change
    +
    "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize +
    "H" + (w1 + 1) * cellSize + "V" + cellSize // note change
    +
    "H" + (w0 + 1) * cellSize + "Z";
}
body {
  padding-top: 25px;
  width: 1000px;
  margin: auto;
}

.day {
  fill: #fff;
  stroke: #ccc;
}

.month {
  fill: none;
  stroke: #000;
  stroke-width: 2px;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<meta charset="utf-8">

Incidentally, your strategy to filter out the weekend days would have worked (kind of) to remove the grid cells representing the days of the week:

var rect = svg.selectAll(".day")
  .data(function(d) {
    return d3.timeDays(new Date(d, 0, 1), new Date(d + 1, 0, 1))
    .filter( function(x){
      return (x.getDay() !== 6 && x.getDay() !== 0) // filter out day 0 and 6
    })
  })
  .enter().append("rect")
  // etc.

Upvotes: 1

Related Questions