Customizing Vizzu Charts - Mouse-wheel zooming
Tags: webdev, javascript, vizzu, dataviz
Published: 2023-04-20 07:33:43

Adding mouse-wheel zooming to a Vizzu chart in JavaScript can be useful for users who want to zoom in and out of a dense chart. In this how-to, we will create a Zoomer class that will handle the zooming functionality and then add event listeners to the Vizzu chart to connect the events to our Zoomer instance. And then, we will improve its performance by adding throttle to the zoom event calls.

{% vimeo 818763953 %}

Here are the steps to add mouse-wheel zooming over a Vizzu chart in JavaScript:

## Step 1: Import Vizzu and data

First, we need to import the Vizzu library and an example data set as well, which we will visualize:
```javascript
import Vizzu from 'https://cdn.jsdelivr.net/npm/vizzu@latest/dist/vizzu.min.js';
import { data } from 'https://lib.vizzuhq.com/0.7/assets/data/chart_types_eu.js'  
```

## Step 2: Create the chart

We can create the chart using the following code:

```javascript
let chart = await (new Vizzu('vizzu-container', { data })).initializing;
```
This creates a new Vizzu chart instance with the provided data.

## Step 3: Initialize the Chart with Zoom Range

Next, we create a line chart with an initial x-axis range.

```javascript
chart.animate({
  config: {
    x: {
      set: 'Year',
      range: {
        min: 0.5,
        max: 20.5
      }
    },
    y: 'Value 5 (+/-)',
    geometry: 'line'
  }
}, 0);
```

Now that we have a static chart, we can add the zoom functionality.

## Step 4: Create the Zoomer class and instance

The Zoomer class will be used to track the zoom level of the chart:

```javascript
class Zoomer {
  constructor(min, max) {
    this.min = min;
    this.max = max;
    this.finalMin = min;
    this.finalMax = max;
    this.pos = null;
  }

  trackPos(pos) {
    this.pos = pos;
  }

  zoom(factor) {
    let ref = this.min + this.pos * (this.max - this.min);
    this.min = ref - (1 + factor) * (ref - this.min);
    this.max = ref + (1 + factor) * (this.max - ref);
    if (this.min < this.finalMin) this.min = this.finalMin;
    if (this.max > this.finalMax) this.max = this.finalMax;
  }
};
```

The constructor takes the initial minimum and maximum value of the x-axis range. The `trackPos` method is used to track the mouse position, and the zoom method is used to zoom in or out based on the mouse wheel delta.

Now, we can create a new Zoomer instance with an initial zoom range of 0.5 to 20.5:

```javascript
let zoomer = new Zoomer(0.5, 20.5);
```

## Step 5: Add event listeners to the chart

We need to add event listeners to the chart to detect mouse-wheel and mouse-move events and prevent the default scroll event on the container element:

```javascript
let container = document.getElementById('vizzu-container');

container.addEventListener('wheel', event => {
  event.preventDefault();
})

chart.on('wheel', event => {
  // Zoom event handling
});

chart.on('mousemove', event => {
  // Mouse move event handling
});
```

## Step 6: Handle the Zoom Event

When a zoom event is triggered, we update the zoom level using the Zoomer object and trigger an animation to update the chart with the new zoom range.

```javascript
chart.on('wheel', event => {
  zoomer.zoom(- event.data.delta / 200);
  chart.animate(
    { x: { range: {
      min: zoomer.min,
      max: zoomer.max
    } } },
    { duration: '50ms', easing: 'linear' });
});
```

## Step 7: Handle the Mouse Move Event

When a mouse move event is triggered, we update the position of the Zoomer object to reflect the current mouse position.

```javascript
chart.on('mousemove', event => {
  zoomer.trackPos(event.data.coords.x);
});
```

With these steps in place, your Vizzu chart should now be able to handle mouse-wheel zooming events. However, you will notice that zooming events can be called before the chart animation can finish, delaying the zoom effect. To prevent this behavior, we will need to throttle the zoom call.

## Step 8: Create the Throttle class

The Throttle class will be used to limit the number of zoom events that are processed at any given time.

```javascript
class Throttle {
  constructor() {
    this.finished = true;
    this.next = null;
  }
  call(func) {
    if (!this.finished) {
      this.next = func;
      return;
    }
    else {
      this.finished = false;
      func().then(() => {
        this.finished = true;
        if (this.next !== null) {
          let f = this.next;
          this.next = null;
          this.call(f);
        }
      })
    }
  }
}

let throttle = new Throttle();
```
The call method takes a promise-returning function as an argument and calls it if the promise returned by the previous function has resolved. Otherwise, it stores the function and waits for the current function's promise to finish.

## Step 9: Adding the throttle to the Zoom Event

We can now modify the zoom event to call the zoom method via our new throttle instance:

```javascript
chart.on('wheel', event => {
  zoomer.zoom(- event.data.delta / 200);
  throttle.call(() =>
    chart.animate(
      { x: { range: {
        min: zoomer.min,
        max: zoomer.max
      } } },
      { duration: '50ms', easing: 'linear' })
  );
});
```

With these additional steps, your Vizzu chart can handle mouse-wheel zooming events flawlessly.

Working example:
{% codepen https://codepen.io/Vizzu/pen/zYJMYqp %}