Comprehensive heat map analytics system for visualizing user behavior patterns, click distributions, and engagement hotspots on your website.
Heat map analytics provides visual representations of user behavior patterns on your website, helping you understand how users interact with your content, where they click, scroll, and focus their attention.
Visualize where users click on your pages, identifying popular elements and potential usability issues.
Track how far users scroll down your pages, identifying content engagement and drop-off points.
Track where users focus their attention using mouse movement and hover data.
// Initialize heatmap tracking
HeatmapAnalytics.init({
projectId: 'your-project-id',
tracking: {
clicks: true,
scrolls: true,
mouseMovement: true,
hovers: true
},
sampling: {
clickSampling: 1.0, // 100% of clicks
scrollSampling: 0.5, // 50% of scroll events
mouseSampling: 0.1, // 10% of mouse movements
hoverSampling: 0.2 // 20% of hover events
},
privacy: {
anonymizeIp: true,
respectDoNotTrack: true
}
});
// Configure data collection settings
HeatmapAnalytics.configure({
dataCollection: {
batchSize: 100, // Send data in batches of 100
flushInterval: 5000, // Send data every 5 seconds
maxRetries: 3, // Maximum retry attempts
compression: true // Compress data before sending
},
performance: {
throttleEvents: true, // Throttle events for performance
maxEventsPerSecond: 100, // Maximum events per second
debounceTime: 100 // Debounce time in milliseconds
}
});
// Track click events
document.addEventListener('click', function(event) {
const clickData = {
x: event.clientX,
y: event.clientY,
target: event.target.tagName,
targetClass: event.target.className,
targetId: event.target.id,
pageUrl: window.location.href,
timestamp: Date.now(),
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight
};
HeatmapAnalytics.track('click', clickData);
});
// Track scroll events
let lastScrollPosition = 0;
const scrollThreshold = 100; // Track every 100px
window.addEventListener('scroll', function() {
const currentScrollPosition = window.scrollY;
const scrollDirection = currentScrollPosition > lastScrollPosition ? 'down' : 'up';
if (Math.abs(currentScrollPosition - lastScrollPosition) >= scrollThreshold) {
const scrollData = {
scrollPosition: currentScrollPosition,
scrollDirection: scrollDirection,
pageHeight: document.documentElement.scrollHeight,
viewportHeight: window.innerHeight,
scrollPercentage: (currentScrollPosition / (document.documentElement.scrollHeight - window.innerHeight)) * 100,
timestamp: Date.now()
};
HeatmapAnalytics.track('scroll', scrollData);
lastScrollPosition = currentScrollPosition;
}
});
// Track mouse movement
let mouseTrackingEnabled = true;
let lastMousePosition = { x: 0, y: 0 };
const mouseThreshold = 50; // Track every 50px
document.addEventListener('mousemove', function(event) {
if (!mouseTrackingEnabled) return;
const currentPosition = { x: event.clientX, y: event.clientY };
const distance = Math.sqrt(
Math.pow(currentPosition.x - lastMousePosition.x, 2) +
Math.pow(currentPosition.y - lastMousePosition.y, 2)
);
if (distance >= mouseThreshold) {
const mouseData = {
x: event.clientX,
y: event.clientY,
pageUrl: window.location.href,
timestamp: Date.now(),
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight
};
HeatmapAnalytics.track('mouse', mouseData);
lastMousePosition = currentPosition;
}
});
# Backend data processing
class HeatmapProcessor:
def __init__(self, project_id):
self.project_id = project_id
self.click_data = []
self.scroll_data = []
self.mouse_data = []
def process_click_data(self, raw_data):
"""Process click data for heatmap visualization"""
processed_data = []
for click in raw_data:
# Normalize coordinates
normalized_x = self.normalize_x_coordinate(click['x'], click['viewportWidth'])
normalized_y = self.normalize_y_coordinate(click['y'], click['viewportHeight'])
processed_click = {
'x': normalized_x,
'y': normalized_y,
'target': click['target'],
'timestamp': click['timestamp'],
'page_url': click['pageUrl']
}
processed_data.append(processed_click)
return self.aggregate_click_points(processed_data)
def aggregate_click_points(self, click_data):
"""Aggregate click points for heatmap rendering"""
grid_size = 20 # 20x20 pixel grid
click_grid = {}
for click in click_data:
grid_x = int(click['x'] / grid_size)
grid_y = int(click['y'] / grid_size)
grid_key = f"{grid_x},{grid_y}"
if grid_key not in click_grid:
click_grid[grid_key] = {
'x': grid_x * grid_size,
'y': grid_y * grid_size,
'count': 0,
'intensity': 0
}
click_grid[grid_key]['count'] += 1
click_grid[grid_key]['intensity'] = min(click_grid[grid_key]['count'] / 10, 1.0)
return list(click_grid.values())
# Heatmap data schema
CREATE TABLE heatmap_clicks (
id SERIAL PRIMARY KEY,
project_id VARCHAR(255) NOT NULL,
page_url TEXT NOT NULL,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
viewport_width INTEGER NOT NULL,
viewport_height INTEGER NOT NULL,
target_tag VARCHAR(50),
target_class TEXT,
target_id VARCHAR(255),
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
session_id VARCHAR(255),
user_id VARCHAR(255)
);
CREATE TABLE heatmap_scrolls (
id SERIAL PRIMARY KEY,
project_id VARCHAR(255) NOT NULL,
page_url TEXT NOT NULL,
scroll_position INTEGER NOT NULL,
scroll_direction VARCHAR(10),
scroll_percentage DECIMAL(5,2),
page_height INTEGER NOT NULL,
viewport_height INTEGER NOT NULL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
session_id VARCHAR(255),
user_id VARCHAR(255)
);
CREATE TABLE heatmap_aggregated (
id SERIAL PRIMARY KEY,
project_id VARCHAR(255) NOT NULL,
page_url TEXT NOT NULL,
heatmap_type VARCHAR(20) NOT NULL, -- 'click', 'scroll', 'mouse'
grid_data JSONB NOT NULL,
date_created DATE DEFAULT CURRENT_DATE,
total_events INTEGER DEFAULT 0
);
// Heatmap visualization component
class HeatmapVisualizer {
constructor(canvas, data, options = {}) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.data = data;
this.options = {
radius: options.radius || 20,
intensity: options.intensity || 0.8,
colorScheme: options.colorScheme || 'default',
showGrid: options.showGrid || false
};
this.colors = this.getColorScheme(this.options.colorScheme);
this.render();
}
render() {
// Clear canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Draw background
this.drawBackground();
// Draw heatmap data
this.drawHeatmapData();
// Draw grid if enabled
if (this.options.showGrid) {
this.drawGrid();
}
}
drawHeatmapData() {
this.data.forEach(point => {
const gradient = this.ctx.createRadialGradient(
point.x, point.y, 0,
point.x, point.y, this.options.radius
);
const intensity = point.intensity || 0.5;
const color = this.getColorForIntensity(intensity);
gradient.addColorStop(0, color);
gradient.addColorStop(1, 'transparent');
this.ctx.fillStyle = gradient;
this.ctx.fillRect(
point.x - this.options.radius,
point.y - this.options.radius,
this.options.radius * 2,
this.options.radius * 2
);
});
}
getColorForIntensity(intensity) {
const colors = this.colors;
const index = Math.floor(intensity * (colors.length - 1));
return colors[Math.min(index, colors.length - 1)];
}
getColorScheme(scheme) {
const schemes = {
default: [
'rgba(0, 0, 255, 0.1)', // Blue (low intensity)
'rgba(0, 255, 0, 0.3)', // Green (medium-low)
'rgba(255, 255, 0, 0.5)', // Yellow (medium)
'rgba(255, 165, 0, 0.7)', // Orange (medium-high)
'rgba(255, 0, 0, 0.9)' // Red (high intensity)
],
blue: [
'rgba(0, 0, 100, 0.1)',
'rgba(0, 0, 200, 0.3)',
'rgba(0, 100, 255, 0.5)',
'rgba(0, 200, 255, 0.7)',
'rgba(0, 255, 255, 0.9)'
],
green: [
'rgba(0, 100, 0, 0.1)',
'rgba(0, 150, 0, 0.3)',
'rgba(0, 200, 0, 0.5)',
'rgba(0, 255, 0, 0.7)',
'rgba(100, 255, 100, 0.9)'
]
};
return schemes[scheme] || schemes.default;
}
}
// Interactive heatmap features
class InteractiveHeatmap extends HeatmapVisualizer {
constructor(canvas, data, options = {}) {
super(canvas, data, options);
this.setupInteractions();
}
setupInteractions() {
this.canvas.addEventListener('wheel', this.handleZoom.bind(this));
this.canvas.addEventListener('mousedown', this.handlePanStart.bind(this));
this.canvas.addEventListener('mousemove', this.handlePanMove.bind(this));
this.canvas.addEventListener('mouseup', this.handlePanEnd.bind(this));
this.canvas.addEventListener('click', this.handleClick.bind(this));
}
handleZoom(event) {
event.preventDefault();
const zoomFactor = event.deltaY > 0 ? 0.9 : 1.1;
this.zoom *= zoomFactor;
this.render();
}
handleClick(event) {
const rect = this.canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// Find data point at click position
const clickedPoint = this.findDataPointAt(x, y);
if (clickedPoint) {
this.showPointDetails(clickedPoint);
}
}
showPointDetails(point) {
const details = {
position: `(${point.x}, ${point.y})`,
clicks: point.count || 0,
intensity: point.intensity || 0,
target: point.target || 'Unknown'
};
// Display point details in tooltip
this.showTooltip(details);
}
}
# Get click heatmap data
GET /api/v1/heatmaps/clicks/{project_id}
Parameters:
- page_url: URL of the page
- start_date: Start date for data
- end_date: End date for data
- grid_size: Grid size for aggregation
Response:
{
"data": [
{
"x": 100,
"y": 200,
"count": 15,
"intensity": 0.75
}
],
"metadata": {
"total_clicks": 1250,
"unique_visitors": 450,
"date_range": "2024-01-01 to 2024-01-31"
}
}
# Get scroll heatmap data
GET /api/v1/heatmaps/scrolls/{project_id}
Parameters:
- page_url: URL of the page
- start_date: Start date for data
- end_date: End date for data
- bucket_size: Scroll position bucket size
Response:
{
"data": [
{
"scroll_position": 500,
"percentage": 25.5,
"user_count": 125,
"drop_off_rate": 0.15
}
],
"metadata": {
"average_scroll_depth": 65.2,
"total_sessions": 450,
"completion_rate": 0.35
}
}
# Update heatmap configuration
PUT /api/v1/heatmaps/config/{project_id}
{
"tracking": {
"clicks": true,
"scrolls": true,
"mouse_movement": true,
"hovers": false
},
"sampling": {
"click_sampling": 1.0,
"scroll_sampling": 0.5,
"mouse_sampling": 0.1,
"hover_sampling": 0.2
},
"privacy": {
"anonymize_ip": true,
"respect_do_not_track": true,
"data_retention_days": 365
}
}