Skip to content
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 156 additions & 21 deletions frontend/src/utils/helpers/githubHeatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const endDate = new Date()
endDate.setDate(endDate.getDate())

const startDate = new Date(endDate)
startDate.setDate(endDate.getDate() - 7 * 52 - 1) // 52 weeks and 1 day.
startDate.setDate(endDate.getDate() - 7 * 52 - 1)

export interface HeatmapData {
years: DataStructYear[]
Expand All @@ -20,9 +20,9 @@ export interface HeatmapData {

const getIntensity = (count) => {
if (count === 0) return '0'
if (count <= 4) return '1'
if (count <= 8) return '2'
if (count <= 12) return '3'
if (count <= 3) return '1'
if (count <= 6) return '2'
if (count <= 10) return '3'
return '4'
}

Expand Down Expand Up @@ -69,20 +69,29 @@ export const fetchHeatmapData = async (username) => {
return err.message
}
}
// The code below is a modified version of 'github-contributions-canvas'
// https://www.npmjs.com/package/github-contributions-canvas?activeTab=code


const themes = {
blue: {
background: '#10151C',
background: '#1a2233',
text: '#FFFFFF',
meta: '#A6B1C1',
grade4: '#5F87A8',
grade3: '#46627B',
grade2: '#314257',
grade1: '#394d65',
grade4: '#2a70d8',
grade3: '#4682b4',
grade2: '#5f87a8',
grade1: '#46627b',
grade0: '#202A37',
},
blueRed: {
background: '#1a2233',
text: '#FFFFFF',
meta: '#A6B1C1',
grade4: '#e64a4a',
grade3: '#4682b4',
grade2: '#5f87a8',
grade1: '#46627b',
grade0: '#202A37',
}
}

interface DataStructYear {
Expand Down Expand Up @@ -117,6 +126,7 @@ interface Options {
fontFace?: string
footerText?: string
theme?: string
showTooltips?: boolean
}
interface DrawYearOptions extends Options {
year: DataStructYear
Expand Down Expand Up @@ -146,11 +156,12 @@ function getPixelRatio() {
}

const DATE_FORMAT = 'yyyy-MM-dd'
const boxWidth = 10
const boxMargin = 2

const boxWidth = 12
const boxMargin = 3
const textHeight = 15
const defaultFontFace = 'IBM Plex Mono'
const headerHeight = 0
const headerHeight = 40
const canvasMargin = 20
const yearHeight = textHeight + (boxWidth + boxMargin) * 8 + canvasMargin
const scaleFactor = getPixelRatio()
Expand Down Expand Up @@ -201,19 +212,22 @@ function drawYear(ctx: CanvasRenderingContext2D, opts: DrawYearOptions) {
ctx.font = `10px '${fontFace}'`
}


const cellsData = []

for (let y = 0; y < graphEntries.length; y += 1) {
for (let x = 0; x < graphEntries[y].length; x += 1) {
const day = graphEntries[y][x]
const cellDate = parseISO(day.date)
if (isAfter(cellDate, endDate) || !day.info) {
continue
}
// @ts-ignore

const color = theme[`grade${day.info.intensity}`]
ctx.fillStyle = color
const cellX = offsetX + (boxWidth + boxMargin) * x
const cellY = offsetY + textHeight + (boxWidth + boxMargin) * y
const cellRadius = 2 // radius for rounded corners
const cellRadius = 2

ctx.beginPath()
ctx.moveTo(cellX + cellRadius, cellY)
Expand All @@ -224,6 +238,18 @@ function drawYear(ctx: CanvasRenderingContext2D, opts: DrawYearOptions) {
ctx.closePath()
ctx.fillStyle = color
ctx.fill()


if (opts.showTooltips) {
cellsData.push({
x: cellX,
y: cellY,
width: boxWidth,
height: boxWidth,
date: day.date,
count: day.info.count
})
}
}
}

Expand All @@ -239,6 +265,8 @@ function drawYear(ctx: CanvasRenderingContext2D, opts: DrawYearOptions) {
lastCountedMonth = month
}
}

return cellsData
}

function drawMetaData(ctx: CanvasRenderingContext2D, opts: DrawMetadataOptions) {
Expand All @@ -247,15 +275,39 @@ function drawMetaData(ctx: CanvasRenderingContext2D, opts: DrawMetadataOptions)
ctx.fillStyle = theme.background
ctx.fillRect(0, 0, width, height)

// chart legend
ctx.fillStyle = theme.text
ctx.textBaseline = 'hanging'
ctx.font = `20px '${fontFace}'`
ctx.font = `12px '${fontFace}'`
ctx.fillText('Contributions:', canvasMargin, 20)

const legendBoxSize = 12
const legendSpacing = 60


for (let i = 0; i <= 4; i++) {
const boxX = canvasMargin + 120 + i * legendSpacing
const boxY = 20

ctx.fillStyle = theme[`grade${i}`]
ctx.beginPath()
ctx.rect(boxX, boxY, legendBoxSize, legendBoxSize)
ctx.fill()

ctx.fillStyle = theme.text
let label = ''

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These are way our of bounds for the heatmap. And I'm not even sure if we want to show this on the heatmap to be honest 🤔
Screenshot 2025-04-06 at 10 08 31 AM
Screenshot 2025-04-06 at 10 09 44 AM

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

can you give me some more details that how you want this heatmap to be looked @kasya

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i myself want to change some things like the tooltip for date should appear ,and colours for day to day activities should be a little bit more dfferent that it should be easy to understand, and one bar which represents which colour suggests what. is it sounds good to you?

if (i === 0) label = 'No activity'
else if (i === 1) label = '1-3'
else if (i === 2) label = '4-6'
else if (i === 3) label = '7-10'
else label = '10+'

ctx.fillText(label, boxX + legendBoxSize + 5, boxY + 2)
}
ctx.beginPath()
ctx.moveTo(canvasMargin, 55 + 10)
ctx.lineTo(width - canvasMargin, 55 + 10)
ctx.moveTo(canvasMargin, 55)
ctx.lineTo(width - canvasMargin, 55)
ctx.strokeStyle = theme.grade0
ctx.stroke()
}

export function drawContributions(canvas: HTMLCanvasElement, opts: Options) {
Expand Down Expand Up @@ -287,15 +339,98 @@ export function drawContributions(canvas: HTMLCanvasElement, opts: Options) {
})
}

let allCellsData = []
data.years.forEach((year, i) => {
const offsetY = yearHeight * i + canvasMargin + headerOffset + 10
const offsetX = canvasMargin
drawYear(ctx, {
const cellsData = drawYear(ctx, {
...opts,
year,
offsetX,
offsetY,
data,
})
if (cellsData) {
allCellsData = [...allCellsData, ...cellsData]
}
})

if (opts.showTooltips && typeof window !== 'undefined') {
const tooltip = document.createElement('div')
tooltip.style.position = 'absolute'
tooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'
tooltip.style.color = 'white'
tooltip.style.padding = '5px 10px'
tooltip.style.borderRadius = '3px'
tooltip.style.fontSize = '14px'
tooltip.style.pointerEvents = 'none'
tooltip.style.opacity = '0'
tooltip.style.transition = 'opacity 0.2s'
tooltip.style.zIndex = '1000'
document.body.appendChild(tooltip)

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect()
const x = (e.clientX - rect.left) / scaleFactor
const y = (e.clientY - rect.top) / scaleFactor

let found = false
allCellsData.forEach(cell => {
if (
x >= cell.x &&
x <= cell.x + cell.width &&
y >= cell.y &&
y <= cell.y + cell.height
) {
const date = new Date(cell.date).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
})

tooltip.innerHTML = `
<div>${date}</div>
<div>${cell.count} contribution${cell.count !== 1 ? 's' : ''}</div>
`
tooltip.style.left = `${e.clientX + 10}px`
tooltip.style.top = `${e.clientY + 10}px`
tooltip.style.opacity = '1'
found = true
}
})

if (!found) {
tooltip.style.opacity = '0'
}
})

canvas.addEventListener('mouseout', () => {
tooltip.style.opacity = '0'
})


canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect()
const x = (e.clientX - rect.left) / scaleFactor
const y = (e.clientY - rect.top) / scaleFactor

allCellsData.forEach(cell => {
if (
x >= cell.x &&
x <= cell.x + cell.width &&
y >= cell.y &&
y <= cell.y + cell.height
) {

const event = new CustomEvent('heatmap-cell-click', {
detail: {
date: cell.date,
count: cell.count
}
})
canvas.dispatchEvent(event)
}
})
})
}
}