Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancing GitHub Heatmap with Interactive Tooltips and Clickable Cells #1271

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

srinjoy933
Copy link
Contributor

Resolves #1270

improved the color scale with more distinct shades
Added a new theme option "blueRed" that uses red for highest activity levels
Created a comprehensive legend showing contribution counts for each colorPositioned at the top of the chart for better visibility
Clear labels indicate the meaning of each color shade
Implemented tooltip functionality that shows when hovering over cells
Added date and exact contribution count information on hover
Included clickable cells that dispatch custom events with cell data

Copy link
Contributor

coderabbitai bot commented Apr 2, 2025

Summary by CodeRabbit

  • New Features
    • The GitHub contribution heatmap now displays interactive tooltips that show detailed date and contribution count when hovering over cells.
  • Style
    • Updated theme colors enhance the visual appeal of the heatmap.
  • Bug Fixes
    • Improved error handling ensures a more stable and consistent experience, even when contribution data isn’t available.

Walkthrough

This pull request updates the GitHub heatmap utility by enhancing type safety, error handling, and tooltip functionality. It introduces new interfaces for structured data, improves error messaging, and ensures proper initialization of data structures. The tooltip feature is implemented to display contribution details on hover, and color themes have been updated for better visual representation. Additionally, the tracking of cell positions is introduced to manage tooltip visibility effectively.

Changes

File Change Summary
frontend/.../githubHeatmap.ts - Enhanced type safety by adding type annotations and new interfaces (HeatmapContribution, HeatmapResponse)
- Improved error handling in fetchHeatmapData
- Updated tooltip functionality for displaying contribution details
- Initialized contributions with empty arrays when not found
- Introduced cell position tracking for tooltip management
- Updated theme color values for better contrast

Assessment against linked issues

Objective Addressed Explanation
Make It Interactive (Issue #1270)
Increase Contrast (Issue #1270)
Better Dark Mode Support (Issue #1270) The changes do not explicitly address dark mode support adjustments.
Improve Sizing & Spacing (Issue #1270) The changes do not provide details on sizing and spacing adjustments.

📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b01034 and 9918a87.

📒 Files selected for processing (1)
  • frontend/src/utils/helpers/githubHeatmap.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/utils/helpers/githubHeatmap.ts

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (7)
frontend/src/utils/helpers/githubHeatmap.ts (7)

14-14: Consider using date manipulation libraries for precise year calculation

While the current approach of startDate.setDate(endDate.getDate() - 7 * 52 - 1) works for setting the start date to approximately one year before the end date, consider using a more precise method like the date-fns library you're already importing to handle edge cases like leap years.

-startDate.setDate(endDate.getDate() - 7 * 52 - 1)
+import { subYears } from 'date-fns/subYears'
+const startDate = subYears(endDate, 1)

23-25: Ensure intensity thresholds align with legend labels

You've modified the intensity thresholds, but later in the legend (lines 298-303), there's a slight inconsistency for the highest threshold. The intensity function defines values >10 as grade4, but the legend labels grade4 as "10+", which is ambiguous.

Consider updating either the thresholds or the legend labels to be consistent:

-  if (count <= 10) return '3'
+  if (count < 10) return '3'

Or alternatively update the label in the legend to "11+" for consistency.


85-94: Consider extracting common theme properties

The newly added blueRed theme shares many properties with the blue theme. Consider creating a base theme object to reduce duplication.

const baseTheme = {
  background: '#1a2233',
  text: '#FFFFFF',
  meta: '#A6B1C1',
  grade0: '#202A37',
  grade1: '#46627b',
  grade2: '#5f87a8',
  grade3: '#4682b4',
}

const themes = {
  blue: {
    ...baseTheme,
    grade4: '#2a70d8',
  },
  blueRed: {
    ...baseTheme,
    grade4: '#e64a4a',
  }
}

129-129: Consider adding a default value for showTooltips

The new showTooltips property has been added without a default value, which could lead to inconsistent behavior if not explicitly set.

Consider providing a default value in the implementation:

interface Options {
  themeName?: keyof typeof themes
  customTheme?: Theme
  skipHeader?: boolean
  skipAxisLabel?: boolean
  username: string
  data: DataStruct
  fontFace?: string
  footerText?: string
  theme?: string
-  showTooltips?: boolean
+  showTooltips?: boolean
}

function getTheme(opts: Options): Theme {
  const { themeName } = opts
  const name = themeName ?? 'blue'
  return themes[name] ?? themes.blue
}

+// Add a helper to get the showTooltips option with a default value
+function getShowTooltips(opts: Options): boolean {
+  return opts.showTooltips ?? false
+}

216-217: Update function signature to reflect return value change

The drawYear function has been modified to collect and return cell data, but the function signature hasn't been updated to reflect this change. Consider adding a return type to make the function's behavior more predictable.

-function drawYear(ctx: CanvasRenderingContext2D, opts: DrawYearOptions) {
+interface CellData {
+  x: number;
+  y: number;
+  width: number;
+  height: number;
+  date: string;
+  count: number;
+}
+
+function drawYear(ctx: CanvasRenderingContext2D, opts: DrawYearOptions): CellData[] {

Also applies to: 243-252, 269-269


372-409: Consider performance optimizations for event handlers

The current mousemove event handler iterates through all cells on every mouse movement, which could be inefficient for large datasets.

Consider using a spatial data structure like a quadtree to improve performance, or at minimum, use early returns when a match is found:

canvas.addEventListener('mousemove', (e) => {
  // ...existing code...
  
  let found = false;
-  allCellsData.forEach(cell => {
+  for (const cell of allCellsData) {
    if (
      x >= cell.x &&
      x <= cell.x + cell.width &&
      y >= cell.y &&
      y <= cell.y + cell.height
    ) {
      // ...existing tooltip code...
      found = true;
+     break; // Exit the loop once we've found a match
    }
-  })
+  }
  
  // ...existing code...
});

412-435: Document the custom event for better API usability

The custom event 'heatmap-cell-click' is dispatched when a cell is clicked, but there's no documentation on how consumers should listen for this event.

Consider adding a JSDoc comment to document this feature:

+/**
+ * Draws GitHub-style contribution heatmap on the provided canvas element.
+ * 
+ * @param canvas - The canvas element to draw on
+ * @param opts - Configuration options
+ * 
+ * @example
+ * // Listen for cell click events
+ * canvas.addEventListener('heatmap-cell-click', (e: CustomEvent) => {
+ *   const { date, count } = e.detail;
+ *   console.log(`Clicked on ${date} with ${count} contributions`);
+ * });
+ */
export function drawContributions(canvas: HTMLCanvasElement, opts: Options) {
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d3d014 and 4cefabd.

📒 Files selected for processing (1)
  • frontend/src/utils/helpers/githubHeatmap.ts (10 hunks)
🔇 Additional comments (4)
frontend/src/utils/helpers/githubHeatmap.ts (4)

76-83: Great improvements to the color scale

The updated blue theme with more distinct color grades provides better visual contrast and helps users differentiate between activity levels more easily.


160-164: Box dimensions improvements

Increasing the box dimensions and header height provides better visual spacing and accommodates the new legend. The consistent use of these constants throughout the code is good practice.


281-305: Great addition of the legend visualization

The legend clearly shows what each color represents and helps users interpret the data correctly. This is a valuable enhancement to the visualization.

However, as mentioned earlier, ensure the legend labels accurately match the intensity thresholds defined in the getIntensity function for consistency.


342-356: Good implementation of cell data collection

The approach to collecting cell data from all years is well-implemented. The code checks if the cellsData returned from drawYear exists before adding it to the allCellsData array, which prevents potential errors.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
frontend/src/utils/helpers/githubHeatmap.ts (3)

300-300: Consider adding a return type annotation

The function is returning cell data, but the return type is not explicitly defined.

-function drawYear(ctx: CanvasRenderingContext2D, opts: DrawYearOptions) {
+function drawYear(ctx: CanvasRenderingContext2D, opts: DrawYearOptions): Array<{x: number, y: number, width: number, height: number, date: string, count: number}> {

437-461: Consider performance optimization for event handlers

While the click event implementation works well, there's potential for optimization. Instead of looping through all cells on every click, consider using a more efficient data structure like a grid or quadtree for hit detection with large datasets.

  // At the top of the file, add this import
+ import { quadtree } from 'd3-quadtree';

  // Then modify the click handler
  if (opts.showTooltips && typeof window !== 'undefined') {
    const tooltip = getOrCreateTooltip();
    
+   // Create a quadtree for efficient hit detection
+   const cellQuadtree = quadtree()
+     .x(d => d.x + d.width/2)
+     .y(d => d.y + d.height/2)
+     .addAll(allCellsData);
    
    // ... mousemove and mouseout handlers ...
    
    // 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;
      
+     // Find the nearest cell within the click radius
+     const radius = Math.max(boxWidth, boxMargin) * 2;
+     const cell = cellQuadtree.find(x, y, radius);
+     
+     if (cell && 
+         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);
+     }
    });
  }

417-420: Consider adding accessibility attributes to tooltip

The tooltip could benefit from improved accessibility.

  tooltip.innerHTML = `
-   <div>${date}</div>
-   <div>${cell.count} contribution${cell.count !== 1 ? 's' : ''}</div>
+   <div role="tooltip" aria-live="polite">
+     <div>${date}</div>
+     <div>${cell.count} contribution${cell.count !== 1 ? 's' : ''}</div>
+   </div>
  `
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4cefabd and 6c1bf49.

📒 Files selected for processing (1)
  • frontend/src/utils/helpers/githubHeatmap.ts (9 hunks)
🔇 Additional comments (9)
frontend/src/utils/helpers/githubHeatmap.ts (9)

21-27: Improved intensity scale with better distribution

The adjusted thresholds create a more balanced distribution of intensity levels, making the visualization more informative. This aligns well with the PR objective of enhancing the color scale for better differentiation of activity levels.


74-95: Great enhancement with theme improvements and addition of blueRed theme

The updated blue theme with better contrast and the new blueRed theme provide more visual options and improve readability. The use of red for highest intensity in the blueRed theme creates a clear visual distinction for high activity days.


130-130: Proper interface extension for tooltips

The addition of the showTooltips option to the interface is clean and follows good TypeScript practices by making it optional with the ? modifier.


160-166: Improved spacing and dimensions for better visibility

The increased box dimensions and spacing improve the overall readability of the heatmap. The header height increase accommodates the new legend.


170-198: Well-implemented tooltip management

The singleton pattern for tooltip management is a good approach, addressing previous review feedback. The cleanup function is particularly valuable for preventing memory leaks.


246-284: Efficient data collection for interactive features

The code efficiently collects cell data for tooltips and click events. The conditional check for opts.showTooltips ensures this only happens when needed, which is good for performance.


309-347: Clear and informative legend implementation

The implementation of the legend with color boxes and labels greatly improves user understanding of the heatmap. The labels accurately reflect the intensity thresholds defined earlier.


378-392: Effective management of cell data collection

The code properly collects and manages cell data from all years, which is essential for the tooltip and click functionality. The conditional check ensures backwards compatibility.


394-436: Well-implemented tooltip functionality

The tooltip implementation provides a good user experience with smooth appearance/disappearance and clear formatting. The event listeners are properly added and the tooltip positioning is user-friendly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
frontend/src/utils/helpers/githubHeatmap.ts (4)

404-472: Consider optimizing hit detection for better performance

While the event handling implementation is good, the hit detection in both mouseMove and click handlers uses a linear search through all cells, which could be inefficient for large heatmaps.

Consider optimizing the hit detection algorithm by using a spatial data structure or grid-based approach:

let allCellsData = []
+ // Create a map for faster cell lookup
+ const cellsByPosition = new Map()

// When collecting cell data
if (cellsData) {
  allCellsData = [...allCellsData, ...cellsData]
+  // Also store cells in a map using row/column as key
+  cellsData.forEach(cell => {
+    // Calculate grid position (row, col)
+    const col = Math.floor(cell.x / (boxWidth + boxMargin))
+    const row = Math.floor(cell.y / (boxWidth + boxMargin))
+    cellsByPosition.set(`${row},${col}`, cell)
+  })
}

// Then in hit detection
handleMouseMove = (e) => {
  // ...existing code...
  
-  let found = false
-  allCellsData.forEach(cell => {
-    if (
-      x >= cell.x &&
-      x <= cell.x + cell.width &&
-      y >= cell.y &&
-      y <= cell.y + cell.height
-    ) {
-      // ... tooltip code ...
-    }
-  })
+  // Calculate which cell we're hovering over
+  const col = Math.floor(x / (boxWidth + boxMargin))
+  const row = Math.floor(y / (boxWidth + boxMargin))
+  const cell = cellsByPosition.get(`${row},${col}`)
+  
+  if (cell) {
+    // ... tooltip code ...
+    found = true
+  }
}

This approach is more efficient for large datasets as it replaces the O(n) linear search with an O(1) lookup.


457-465: Consider adding more information to the click event

The click event currently provides the date and count, but you could enhance its utility by including additional information such as the intensity level.

const event = new CustomEvent('heatmap-cell-click', {
  detail: {
    date: cell.date,
    count: cell.count
+   intensity: getIntensity(cell.count),
+   position: { x: cell.x, y: cell.y }
  }
})

404-439: Use TypeScript type safety for event parameters

The event handlers are lacking proper TypeScript type annotations for the event parameter.

-handleMouseMove = (e) => {
+handleMouseMove = (e: MouseEvent) => {
  const rect = canvas.getBoundingClientRect()
  // rest of the code
}

419-428: Consider a more accessible tooltip format

The current tooltip design is simple but could be enhanced for better accessibility and user experience.

tooltip.innerHTML = `
-  <div>${date}</div>
-  <div>${cell.count} contribution${cell.count !== 1 ? 's' : ''}</div>
+  <div style="font-weight: bold;">${date}</div>
+  <div>
+    <span style="color: ${getTheme(opts)[`grade${getIntensity(cell.count)}`]}">■</span>
+    ${cell.count} contribution${cell.count !== 1 ? 's' : ''}
+  </div>
`

This format includes the color indicator matching the cell's color and improves readability with bold styling for the date.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c1bf49 and 0b01034.

📒 Files selected for processing (1)
  • frontend/src/utils/helpers/githubHeatmap.ts (9 hunks)
🔇 Additional comments (8)
frontend/src/utils/helpers/githubHeatmap.ts (8)

21-27: Well-tuned intensity scale for better differentiation

The modified thresholds now provide better contrast between the intensity levels, making it easier to distinguish between different contribution counts. The new scale aligns well with the legend labels added later in the code.


77-85: Improved blue theme with better contrast

The updated blue theme with a new background color and more distinct grade colors provides better visual contrast, making the heatmap easier to read.


86-95: Good addition of the blueRed theme

The new blueRed theme effectively highlights high-contribution days with the red color for the highest intensity, while maintaining the blue gradient for lower intensities. This provides users with more visual options that can help emphasize peak activity days.


130-130: LGTM: Clean interface extension

Good job extending the Options interface with an optional showTooltips property. This follows TypeScript best practices by making the feature opt-in.


161-166: Improved visual spacing and layout

The increased dimensions for boxWidth, boxMargin, and headerHeight improve the overall layout and readability of the heatmap. The cells are now more distinguishable and the additional header space accommodates the new legend.


171-212: Well-implemented tooltip and event cleanup functions

The tooltip implementation follows best practices by using a singleton pattern and providing proper cleanup functions. This addresses the concerns raised in previous reviews about potential memory leaks and multiple tooltip instances.


261-262: Good implementation of cell data collection

The cellsData collection approach is clean and efficient. It gathers only the necessary information for tooltips and click events, and only when the showTooltips option is enabled.

Also applies to: 288-297, 314-314


325-351: Excellent legend implementation

The legend implementation with clear labels corresponding to the intensity thresholds greatly improves the heatmap's usability. Users can now understand exactly what each color represents.

@srinjoy933
Copy link
Contributor Author

@arkid15r , @kasya any changes suggested from your side for this pr.please suggest and review this once please

@srinjoy933
Copy link
Contributor Author

@arkid15r any review for this please

@srinjoy933
Copy link
Contributor Author

@arkid15r ,@kasya please review this pr

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

Could you update description of the PR to include some picture references of the changes (before and after)? So far, after checking out your branch, I don't see any tooltips on the heat map.

ctx.fill()

ctx.fillStyle = theme.text
let label = ''
Copy link
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
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
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?

Copy link

sonarqubecloud bot commented Apr 9, 2025

@srinjoy933 srinjoy933 requested a review from kasya April 9, 2025 15:48
@srinjoy933
Copy link
Contributor Author

srinjoy933 commented Apr 9, 2025

i have made some changes can have a look on these @kasya , now i am only changing the colour contrast for different contribution and adding tooltip to heatmap for date specification

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enhance GitHub Heatmap with Interactive Tooltips and Clickable Cells
2 participants