Skip to content
This repository has been archived by the owner on Sep 7, 2020. It is now read-only.

Added some more feature #17

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# D3 Calendar Heatmap
A [d3.js](https://d3js.org/) heatmap representing time series data. Inspired by Github's contribution chart

![Reusable D3.js Calendar Heatmap chart](https://raw.githubusercontent.com/DKirwan/calendar-heatmap/develop/example/thumbnail.png)
![Reusable D3.js Calendar Heatmap chart](https://raw.githubusercontent.com/khairulhasanmd/calendar-heatmap/master/example/thumbnail.png)

## TODO

Expand Down Expand Up @@ -52,6 +52,9 @@ var chart1 = calendarHeatmap()
.selector('#chart-one')
.colorRange(['#D8E6E7', '#218380'])
.tooltipEnabled(true)
.allWeekdayNames(true)
.dayNumbersInBox(true)
.monthSpace(true)
.onClick(function (data) {
console.log('onClick callback. Data:', data);
});
Expand Down
5 changes: 4 additions & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
.data(chartData)
.selector('.container')
.tooltipEnabled(true)
.colorRange(['#f4f7f7', '#79a8a9'])
.allWeekdayNames(true)
.dayNumbersInBox(true)
.monthSpace(true)
.colorRange(['#D8E6E7', '#218380'])
.onClick(function (data) {
console.log('data', data);
});
Expand Down
Binary file modified example/thumbnail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 8 additions & 2 deletions src/calendar-heatmap.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ text.day-initial {
font-family: Helvetica, arial, 'Open Sans', sans-serif
}
.day-cell {
border: 1px solid gray;
border: 1px solid gry;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo here 'gry'

}
rect.day-cell:hover {
stroke: #555555;
stroke: red;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this back to #555555. If a user wants to change the color they can override this in their own stylesheet. I think changing it to The dark grey color was more neutral and fit in with most sites, whereas red will only suit specific sites.

stroke-width: 1px;
cursor: pointer;
}
.day-text:hover {
stroke: red;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, this would be better if it was dark grey like #55555 and users could override it in their own stylesheet if necessary.

stroke-width: 1px;
cursor: pointer;
}
.day-cell-tooltip {
position: absolute;
Expand Down
117 changes: 103 additions & 14 deletions src/calendar-heatmap.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@

function calendarHeatmap() {
// defaults
var SQUARE_LENGTH = 10;
var width = 750;
width = SQUARE_LENGTH * (52+12);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this change?

Copy link
Author

@khairulhasanmd khairulhasanmd Nov 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried to determine the svg size by square width. It can be done opposite, i mean, determine square width by svg size(smarter and useful)..

var height = 110;
var legendWidth = 150;
var dayNumbersFontSz = 8;
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var days = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
var selector = 'body';
var SQUARE_LENGTH = 11;

var SQUARE_PADDING = 2;
var MONTH_LABEL_PADDING = 6;
var now = moment().endOf('day').toDate();
var yearAgo = moment().startOf('day').subtract(1, 'year').toDate();
var data = [];
var colorRange = ['#D8E6E7', '#218380'];
var tooltipEnabled = true;
var allWeekdayNames = true;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

showDayNames would be a more accurate variable name here

var dayNumbersInBox = true;
var monthSpace = true;
var tooltipUnit = 'contribution';
var legendEnabled = true;
var onClick = null;
Expand Down Expand Up @@ -44,6 +50,24 @@ function calendarHeatmap() {
return chart;
};

chart.allWeekdayNames = function (value) {
if (!arguments.length) { return allWeekdayNames; }
allWeekdayNames = value;
return chart;
};

chart.dayNumbersInBox = function (value) {
if (!arguments.length) { return dayNumbersInBox; }
dayNumbersInBox = value;
return chart;
};

chart.monthSpace = function (value) {
if (!arguments.length) { return monthSpace; }
monthSpace = value;
return chart;
};

chart.tooltipUnit = function (value) {
if (!arguments.length) { return tooltipUnit; }
tooltipUnit = value;
Expand All @@ -70,14 +94,16 @@ function calendarHeatmap() {
var monthRange = d3.time.months(moment(yearAgo).startOf('month').toDate(), now); // it ignores the first month if the 1st date is after the start of the month
var firstDate = moment(dateRange[0]);
var max = d3.max(chart.data(), function (d) { return d.count; }); // max data value

// color range
var color = d3.scale.linear()
.range(chart.colorRange())
.domain([0, max]);

var tooltip;
var dayRects;
if (monthSpace){
width = width + (12 * (SQUARE_LENGTH + SQUARE_PADDING));
}

drawChart();

Expand All @@ -87,7 +113,7 @@ function calendarHeatmap() {
.attr('width', width)
.attr('class', 'calendar-heatmap')
.attr('height', height)
.style('padding', '36px');
.style('padding', '23px');

dayRects = svg.selectAll('.day-cell')
.data(dateRange); // array of days for the last yr
Expand All @@ -99,7 +125,11 @@ function calendarHeatmap() {
.attr('fill', 'gray')
.attr('x', function (d, i) {
var cellDate = moment(d);
var result = cellDate.week() - firstDate.week() + (firstDate.weeksInYear() * (cellDate.weekYear() - firstDate.weekYear()));
var monthSpacing = 0;
if (monthSpace){//moment(startDate).endOf('month')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the code in the comment please

monthSpacing = cellDate.diff((firstDate).startOf('month'), 'months');
}
var result = (cellDate.week() + monthSpacing) - firstDate.week() + (firstDate.weeksInYear() * (cellDate.weekYear() - firstDate.weekYear()));
return result * (SQUARE_LENGTH + SQUARE_PADDING);
})
.attr('y', function (d, i) { return MONTH_LABEL_PADDING + d.getDay() * (SQUARE_LENGTH + SQUARE_PADDING); });
Expand All @@ -111,31 +141,78 @@ function calendarHeatmap() {
});
}

if (dayNumbersInBox){
dayNumbers = svg.selectAll('.day-numbers')
.data(dateRange); // array of days for the last yr

dayNumbers.enter().append('text')
.attr('class', 'day-numbers')
.attr("font-size", dayNumbersFontSz+"px")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a code style thing - we use single quotes here and put spaces either side of the +

.attr('x', function (d, i) {
var cellDate = moment(d);
var numSzBlank = 0;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you set this to 3 then you wouldn't need the else statement on line 156

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you called it dayTextPadding or similar it would be a more descriptive variable name

if(d.getDate()>9){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space before and after the > symbol

numSzBlank = 1;
}else{
numSzBlank = 3;
}
var monthSpacing = 0;
if (monthSpace){
monthSpacing = cellDate.diff((firstDate).startOf('month'), 'months');
}
var result = (cellDate.week() + monthSpacing) - firstDate.week() + (firstDate.weeksInYear() * (cellDate.weekYear() - firstDate.weekYear()));
return (result * (SQUARE_LENGTH + SQUARE_PADDING)) + (((SQUARE_LENGTH-dayNumbersFontSz)/4) * (numSzBlank));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add spaces between operators here too please

})
.attr('y', function (d, i) { return MONTH_LABEL_PADDING + dayNumbersFontSz + d.getDay() * (SQUARE_LENGTH + SQUARE_PADDING); })
.text(function (d) { return d.getDate() ; });
}

if (chart.tooltipEnabled()) {
dayRects.on('mouseover', function (d, i) {
var monthSpacing = 0;
if (monthSpace){
monthSpacing = moment(d).diff((firstDate).startOf('month'), 'months');
}
tooltip = d3.select(chart.selector())
.append('div')
.attr('class', 'day-cell-tooltip')
.html(tooltipHTMLForDate(d))
.style('left', function () { return Math.floor(i / 7) * SQUARE_LENGTH + 'px'; })
.style('top', function () { return d.getDay() * (SQUARE_LENGTH + SQUARE_PADDING) + MONTH_LABEL_PADDING * 3 + 'px'; });
.style('left', function () { return (Math.floor(i / 7)+monthSpacing) * SQUARE_LENGTH + 'px'; })
.style('top', function () { return d.getDay() * (SQUARE_LENGTH + SQUARE_PADDING) + (MONTH_LABEL_PADDING - 5) * 3 + 'px'; });
})
.on('mouseout', function (d, i) {
tooltip.remove();
});
if (dayNumbersInBox){
dayNumbers.on('mouseover', function (d, i) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please set the css rule pointer-events: none; to the day numbers to prevent the need to duplicate these functions. You should be able to delete all code in this if statement then. Let me know if it doesn't work and we can investigate another way of doing it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have little experience on this subject or, cant understand.. Please change as required.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, I can change this for you

var monthSpacing = 0;
if (monthSpace){
monthSpacing = moment(d).diff((firstDate).startOf('month'), 'months');
}
tooltip = d3.select(chart.selector())
.append('div')
.attr('class', 'day-cell-tooltip')
.html(tooltipHTMLForDate(d))
.style('left', function () { return (Math.floor(i / 7)+monthSpacing) * SQUARE_LENGTH + 'px'; })
.style('top', function () { return d.getDay() * (SQUARE_LENGTH + SQUARE_PADDING) + (MONTH_LABEL_PADDING - 5) * 3 + 'px'; });
})
.on('mouseout', function (d, i) {
tooltip.remove();
});
}
}

if (chart.legendEnabled()) {
var colorRange = [color(0)];
for (var i = 3; i > 0; i--) {
for (var i = 4; i > 0; i--) {
colorRange.push(color(max / i));
}

var legendGroup = svg.append('g');
legendGroup.selectAll('.calendar-heatmap-legend')
.data(colorRange)
.enter()
.append('rect')
.append('rect')
.attr('class', 'calendar-heatmap-legend')
.attr('width', SQUARE_LENGTH)
.attr('height', SQUARE_LENGTH)
Expand All @@ -149,7 +226,7 @@ function calendarHeatmap() {
.attr('y', height + SQUARE_LENGTH)
.text('Less');

legendGroup.append('text')
legendGroup.append('text')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix indentation on this line

.attr('class', 'calendar-heatmap-legend-text')
.attr('x', (width - legendWidth + SQUARE_PADDING) + (colorRange.length + 1) * 13)
.attr('y', height + SQUARE_LENGTH)
Expand All @@ -167,23 +244,35 @@ function calendarHeatmap() {
})
.attr('x', function (d, i) {
var matchIndex = 0;
var monthSpacing = 0;
if (monthSpace){
monthSpacing = moment(d).diff((firstDate).startOf('month'), 'months');
}
dateRange.find(function (element, index) {
matchIndex = index;
return moment(d).isSame(element, 'month') && moment(d).isSame(element, 'year');
});

return Math.floor(matchIndex / 7) * (SQUARE_LENGTH + SQUARE_PADDING);
return ((Math.floor(matchIndex / 7)+monthSpacing) * (SQUARE_LENGTH + SQUARE_PADDING));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space before and after the + please

})
.attr('y', 0); // fix these to the top

days.forEach(function (day, index) {
if (index % 2) {
if(allWeekdayNames){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be all or nothing. So if allWeekdayNames if false, we should not show any weekday names

svg.append('text')
.attr('class', 'day-initial')
.attr('transform', 'translate(-8,' + (SQUARE_LENGTH + SQUARE_PADDING) * (index + 1) + ')')
.attr('transform', 'translate(-14,' + (SQUARE_LENGTH + SQUARE_PADDING) * (index + 1) + ')')
.style('text-anchor', 'middle')
.attr('dy', '2')
.text(day);
}else{
if (index % 2) {
svg.append('text')
.attr('class', 'day-initial')
.attr('transform', 'translate(-14,' + (SQUARE_LENGTH + SQUARE_PADDING) * (index + 1) + ')')
.style('text-anchor', 'middle')
.attr('dy', '2')
.text(day);
}
}
});
}
Expand Down