DEV Community

loading...
Cover image for Integrating Heatmap.js with Mouse-over click in Angular

Integrating Heatmap.js with Mouse-over click in Angular

idrisrampurawala profile image Idris Rampurawala ・5 min read

A heatmap (or heat map) is a graphical representation of data where the individual values contained in a matrix are represented as colors. There are various types of heatmaps available, but we will stick with the heatmap used for depicting and analyzing user behavior. This type of heatmap renders user interaction density on a container (or an image). Generally, the area with red color means more density i.e. heavy user interactions.

heatmap.js is a lightweight, easy to use JavaScript library to visualize these user interactions! We will first integrate heatmap.js in our Angular-8 project then add a mouse-over click feature to fetch the points around the mouse pointer. The final product can be visualized in the following gif or demo link:
Angular-Heatmapjs implementation demo

Let's get started 😁

Prerequisites

  • We will assume that you have a basic knowledge of Angular framework
  • This post only aims to guide with the logic of implementation and hence showcasing only code snippets. For overall code implementation, check out my GitHub repository.

Integrating heatmap.js with Angular

This is rather a simple step once you look into the heatmap.js documentation.

1. Install heatmap.js

Heatmap is hosted on npm so we can easily install it via npm command
npm install heatmap.js

2. Preaparing our HTML template of component

We will first create a container in our HTML part of the component to load the heatmap graph.

<div id="heatmapContainer">
 <!-- Overlay is for the backgorund. You can even add image instead -->
 <div class="overlay"></div> 
</div>

3. Integrating with a component

Next is to create an instance of heatmap inside our component. h337 is the name of the global object registered by heatmap.js. We can use it to create heatmap instances. We will refer to this object by declaring a variable below our imports

import { Component, OnInit } from '@angular/core';

declare let h337: any; //heatmap.js global object

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
 gradientCfg = { // heatmap gradient color range
    '0.15': '#6ad180', // green
    '0.25': '#7cd573',
    '0.35': '#90d865',
    '0.45': '#a4da57',
    '0.55': '#badc48',
    '0.65': '#c9cf35',
    '0.75': '#d6c226',
    '0.80': '#e2b41c',
    '0.85': '#e2961d',
    '0.90': '#dd7826',
    '0.95': '#d25c30',
    '1.0': '#c24039' // highest red
  };
  heatmap: any = null; // heatmap instance
  coordinates: Array<Coordinate> = []; // heatmap coordinates array
  heatmapContainer: HTMLElement;  // heatmap container HTML element
...

Heatmap coordinates i.e. the data that will be passed to create heatmap instance will be of type:

export interface Coordinate {
  x: number;
  y: number;
  value: number;
}

We will have to create some dummy coordinates to render a promising heatmap graph. We can create a function like below that will generate a handful of coordinates.

generateCoordinates(): void {
  const extremas = [(Math.random() * 1000) >> 0, (Math.random() * 1000) >> 0];
  const max = Math.max.apply(Math, extremas);
  const min = Math.min.apply(Math, extremas);
  for (let i = 0; i < 1000; i++) {
    const x = (Math.random() * HEATMAP_WIDTH) >> 0;
    const y = (Math.random() * HEATMAP_HEIGHT) >> 0;
    const c = ((Math.random() * max - min) >> 0) + min;
    // add to dataset
    this.coordinates.push({ x: x, y: y, value: c });
  }
}

Lastly, we will create instance of heatmap.js h337.create() function passing dummy datasets created above.

ngOnInit(): void {
  this.generateCoordinates(); // this will create dummy coordindates
  const heatmapConfig = { // heatmap config object. For more info visit documentation
    container: document.querySelector('#heatmapContainer'),
    opacity: .8,
    radius: 7,
    visible: true,
    gradient: this.gradientCfg,
    backgroundColor: 'inherit'
  };
  this.heatmap = h337.create(heatmapConfig); // creating the instance
  this.heatmap.setData({ max: 30, data: this.coordinates }); // passing the dummy coordinates
...

Voila! We have created a heatmap from dummy dataset. Heatmap.js creates a canvas element in our container which will hold the heatmap graph.

Implementing Mouse-over click on the heatmap

We are done with the initial integration step now comes the tricky part of implementing mouse-over click functionality on the heatmap. The logic is to create a tooltip on the heatmap and a circular container (referred to as mousecircle in this post) around the mouse pointer to highlight the area of which the coordinates will be fetched when clicked. Let's get going.

1. HTML template setup

To track the mouse pointer movements, we will create some mouse listeners on our heatmap container, hence the heatmapContainer can be modified as

<div id="heatmapContainer" (mousemove)="heatmapMouseMove($event)" (mouseleave)="heatmapMouseOut()"
  (mouseenter)="heatmapMouseEnter()" (click)="mouseCircleClick($event)">
  <div class="overlay"></div>
</div>

📘 NOTE
We are using mouseenter/mouseleave over mouseover/mouseout because of 2 major reasons:

  1. mouseenter/mouseleave does not bubble.
  2. Transitions inside the element, to/from descendants, are not counted. This helps us avoid unncessary blackouts on tooltip and mouseover containers as they are inside the heatmap contianer.

2. Adding tooltip and mousecircle

The logic is to add the tooltip and mousecircle on ngOnInit() via Renderer2 so it appears on top of our heatmap rendered canvas.

ngOnInit(): void {
  // heatmap integration code
  ...
  this.heatmapContainer = document.querySelector('#heatmapContainer');
  this.tooltip = this.renderer.createElement('div'); // tooltip element variable
  this.renderer.addClass(this.tooltip, 'heatmap-tooltip');
  this.renderer.setStyle(this.tooltip, 'display', 'none');
  this.renderer.setStyle(this.tooltip, 'transform', 'translate(39px, 489px)');
  this.mouseCircle = this.renderer.createElement('div'); // mousecircle element variable
  this.renderer.addClass(this.mouseCircle, 'mouseCircle');
  this.renderer.setStyle(this.mouseCircle, 'display', 'none');
  this.renderer.setStyle(this.mouseCircle, 'transform', 'translate(39px, 489px)');
  this.renderer.appendChild(this.heatmapContainer, this.tooltip);
  this.renderer.appendChild(this.heatmapContainer, this.mouseCircle);
}

Our logic for all the mouse listeners added in the HTML template above can be summarised as:

mouseenter
This event will track whether the mouse pointer is inside our heatmap container.
mouseleave
This event will track whether the mouse pointer is moved out of our heatmap container. If it turns out to be true, then we will immediately hide our tooltip and mousecircle containers
mousemove
This event will continuously update the coordinates of our tooltip and mousecircle containers whenever mouse pointer is moved inside our heatmap container.

3. Fetching the coordinates on mouse click

The last part is to fetch all the heatmap coordinates inside the mousecircle area. The idea is to compare the radius of the circle with the distance of its center from the XY coordinates clicked for each of the heatmap coordinates. If it lies inside the mousecircle area then just check if it is present in heatmap coordinate.

You can check my GitHub repository for the implementation code.

Useful Links ⭐


If you like my post do not forget to hit ❤️ or 🦄

See ya! until my next post 😋

Discussion

pic
Editor guide