Skip to content
Closed
Changes from 2 commits
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
199 changes: 180 additions & 19 deletions frontend/src/utils/helpers/githubHeatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ export interface HeatmapData {
contributions: []
}

// Modified intensity scale for better contrast
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 +70,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

// Improved themes with better contrast
const themes = {
blue: {
background: '#10151C',
background: '#1a2233', // Slightly lighter background for contrast
text: '#FFFFFF',
meta: '#A6B1C1',
grade4: '#5F87A8',
grade3: '#46627B',
grade2: '#314257',
grade1: '#394d65',
grade0: '#202A37',
grade4: '#2a70d8', // Much brighter blue for highest activity
grade3: '#4682b4', // Steel blue
grade2: '#5f87a8', // Lighter blue
grade1: '#46627b', // Subtle blue
grade0: '#202A37', // Background for no activity
},
blueRed: {
background: '#1a2233',
text: '#FFFFFF',
meta: '#A6B1C1',
grade4: '#e64a4a', // Red for highest intensity
grade3: '#4682b4', // Blue
grade2: '#5f87a8', // Lighter blue
grade1: '#46627b', // Subtle blue
grade0: '#202A37', // Background for no activity
}
}

interface DataStructYear {
Expand Down Expand Up @@ -117,6 +127,7 @@ interface Options {
fontFace?: string
footerText?: string
theme?: string
showTooltips?: boolean // Added option for tooltips
}
interface DrawYearOptions extends Options {
year: DataStructYear
Expand Down Expand Up @@ -146,15 +157,46 @@ function getPixelRatio() {
}

const DATE_FORMAT = 'yyyy-MM-dd'
const boxWidth = 10
const boxMargin = 2
// Increased box size and spacing for better visibility
const boxWidth = 12
const boxMargin = 3
const textHeight = 15
const defaultFontFace = 'IBM Plex Mono'
const headerHeight = 0
const headerHeight = 40 // Increased for legend
const canvasMargin = 20
const yearHeight = textHeight + (boxWidth + boxMargin) * 8 + canvasMargin
const scaleFactor = getPixelRatio()

// Singleton tooltip element
let tooltipElement: HTMLDivElement | null = null

// Function to get or create the tooltip
function getOrCreateTooltip(): HTMLDivElement {
if (!tooltipElement && typeof window !== 'undefined') {
tooltipElement = document.createElement('div')
tooltipElement.style.position = 'absolute'
tooltipElement.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'
tooltipElement.style.color = 'white'
tooltipElement.style.padding = '5px 10px'
tooltipElement.style.borderRadius = '3px'
tooltipElement.style.fontSize = '14px'
tooltipElement.style.pointerEvents = 'none'
tooltipElement.style.opacity = '0'
tooltipElement.style.transition = 'opacity 0.2s'
tooltipElement.style.zIndex = '1000'
document.body.appendChild(tooltipElement)
}
return tooltipElement as HTMLDivElement
}

// Function to clean up tooltip when no longer needed
export function cleanupTooltip(): void {
if (tooltipElement && typeof window !== 'undefined') {
document.body.removeChild(tooltipElement)
tooltipElement = null
}
}

function getTheme(opts: Options): Theme {
const { themeName } = opts
const name = themeName ?? 'blue'
Expand Down Expand Up @@ -201,6 +243,9 @@ function drawYear(ctx: CanvasRenderingContext2D, opts: DrawYearOptions) {
ctx.font = `10px '${fontFace}'`
}

// Store cells data for interactive features
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]
Expand All @@ -224,6 +269,18 @@ function drawYear(ctx: CanvasRenderingContext2D, opts: DrawYearOptions) {
ctx.closePath()
ctx.fillStyle = color
ctx.fill()

// Store cell data for interactivity
if (opts.showTooltips) {
cellsData.push({
x: cellX,
y: cellY,
width: boxWidth,
height: boxWidth,
date: day.date,
count: day.info.count
})
}
}
}

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

return cellsData
}

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

// chart legend
// Draw 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

// Draw legend boxes with labels
for (let i = 0; i <= 4; i++) {
const boxX = canvasMargin + 120 + i * legendSpacing
const boxY = 20

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

// Draw label
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)
}

// Separator line
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 +375,88 @@ 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]
}
})

// Add interactivity if enabled
if (opts.showTooltips && typeof window !== 'undefined') {
const tooltip = getOrCreateTooltip()

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'
})

// Make cells clickable
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
) {
// Dispatch custom event with cell data
const event = new CustomEvent('heatmap-cell-click', {
detail: {
date: cell.date,
count: cell.count
}
})
canvas.dispatchEvent(event)
}
})
})
}
Comment thread
srinjoy933 marked this conversation as resolved.
Outdated
}