littlerave
Posts: 37
Joined: Sun May 16, 2021 4:17 pm

Zoom by specific amount at specific point

In my app I have a fileupload with which the user can upload an SVG file, which will then get turned into a number of form input fields and a highcharts map. Series data can be updated with the form, and countries can be set as active or inactive, which will then add or remove the entry from the series.data array. Hence, everything is dynamic here.

Once this is saved and uploaded to the database, it will be displayed in a different highcharts map on another page. Here I would like to zoom in on all active countries upon initialization, yet I can't find out how to get the position to center on the point I have calculated. The series comes from the database so I cannot set it directly in the chart options, which by then already exist.

Upon inspecting the chart object in the dev tools on the page itself, I've noticed it contains a lot more properties than both the class definition in Angular as well as the documentation. Luckily I stumbled on a post mentioning this, which got offered the solution to extend the interface, which I did.

So I wrote this interface to get the mapView of the chart:

Code: Select all

import { Chart, MapView } from 'highcharts';

export interface MapChart extends Chart {
    mapView: MapView;
}

As well as a bounds interface, which looks like the bounds property of the Highcharts.Point, because the point misses this property in the class definition as well, and I could not find an already existing class definition:

Code: Select all

export interface Bounds {
    x1: number,
    x2: number,
    midX: number,
    y1: number,
    y2: number,
    midY: number
}

I have then extended the Highcharts.Point interface, because it was missing the bounds property, as mentioned:

Code: Select all

import { Point } from 'highcharts';
import { Bounds } from './bounds';

export interface MapPoint extends Point {
    bounds: Bounds;
}

Then I setup the chart callback to give me a reference to the Highcharts.chart property, casting it to my MapChart:

Code: Select all

this.chartCallback = (chart: Highcharts.Chart) => { this.chart = chart as MapChart }

Finally I added a load event handler to set the zoom and location:

Code: Select all

this.chartOptions.chart!.events = { load: () => {
  if (!this.chart) { return; }

  const padding: number = 100;
  // Get all points, which reference an active country and cast them to the enhanced MapPoint interface so we can get their bounds.
  let points: MapPoint[] = this.chart.series[0].data.filter((point: Highcharts.Point) => !!point.value)
                                                    .map((point: Highcharts.Point) => point as MapPoint);
  
  // Use Math.min/max to find the smallest and largest bounds for x and y value, and set a getter to return the midpoint we want to focus on.
  let bounds: Bounds = { x1: points.length ? Math.min(...points.map((point: MapPoint) => point.bounds.x1)) : 0,
                         x2: points.length ? Math.max(...points.map((point: MapPoint) => point.bounds.x2)) : this.chart.mapView.center[0] * 2,
                         get midX(): number { return (this.x1 + this.x2) / 2; },
                         y1: points.length ? Math.min(...points.map((point: MapPoint) => point.bounds.y1)) : 0,
                         y2: points.length ? Math.max(...points.map((point: MapPoint) => point.bounds.y2)) : this.chart.mapView.center[1] * 2,
                         get midY(): number { return (this.y1 + this.y2) / 2; } };
  
  // Calculate the zoom level using the previously calculated bounds to zoom into, i.e. their percentage size of the whole map.
  // Then use the larger number from x and y values as zoom level so nothing will get cut off. Also add a padding.
  let zoomLevel: number = Math.max((bounds.x2 - bounds.x1 + padding) / (this.chart.mapView.center[0] * 2), (bounds.y2 - bounds.y1 + padding) / (this.chart.mapView.center[1] * 2));
  
  // Use the zoom level and center to zoom onto the rectangle.
  this.chart.mapZoom(zoomLevel, bounds.midX, bounds.midY, bounds.midX, bounds.midY);
} }

While the zoom level works properly, the point to zoom on doesn't. It merely zooms into the center of the whole map, I've tried various values but to no avail. I am not sure what projectionX and projectionY even mean and what I need them to set to if I don't have any projection. I've tried undefined, but this just zoomed into the bottom left corner. The chartX and chartY value don't seem to do anything, although it seems those would be the properties to set.

I am aware this is a deprecated function, but I cannot use the replacement MapView.zoomBy as that one doesn't work with a linear zoom value, i.e. 1 = .5, 2 = .75, 3 = .875, etc. I have no idea what I should set there to zoom in by 83% for example.

I've also tried the MapView.setView method, which does set the position properly but initialized with an already very zoomed in state, even if I set it to 0. I assume it does so as it is working with longitude and latitude, so it automatically zooms in on 360 width and 180 height. Since my map is 8675 high, 180 is of course very zoomed in. It seems this function also works with a non-linear zoom factor, which, if I set it to -1, seems to zoom out twice as far, whereas when I set it to -2, it seems to be four times as much. Again, something I cannot use. Also, if that is the case, I do not understand why the longitude and latitude values to center work with the regular values of my map, i.e. 2621.5 and 4966.5. I would have assumed having to recalculate them to values between -180/+180 and -90/+90.

How can I zoom into a defined rectangle? Which zoom functionality do I have to use, and how?
littlerave
Posts: 37
Joined: Sun May 16, 2021 4:17 pm

Re: Zoom by specific amount at specific point

I've also tried adding the information to the chart options I'm using. Then redraw. But the redraw method generally seems to do absolutely nothing. So I don't know if this would theoretically work if I could redraw the map.

Code: Select all

this.chartOptions.mapView = { center: [bounds.midX, bounds.midY], zoom: zoomLevel };
this.chart.redraw();

I've also tried setting the adjusted chartOptions with Highcharts.chart(...) but I guess that just tries to create a new chart, thus complains about no div existing to render to.

I've tried the update method of the mapView, which didn't do anything.

Code: Select all

this.chart.mapView.update({ center: [bounds.midX, bounds.midY], zoom: zoomLevel }, true);
jakub.s
Posts: 1222
Joined: Fri Dec 16, 2022 11:45 am

Re: Zoom by specific amount at specific point

Hi,

Thanks for the question!

Firstly, I would suggest we create a simpler version of a Highcharts Maps demo (without TypeScript and without Angular) so that we focus on solving the zoom issue first.

After we solve this, we can try integrating it into your Angular project with TypeScript, but let's take it one step at a time.

I've created a simple Highcharts Maps demo here: https://jsfiddle.net/BlackLabel/jd2bsrk3/ from which we can start our work.

In order to zoom to a certain part of the map you can use the following methods:
- mapView.setView - https://api.highcharts.com/class-refere ... ew#setView
- mapView.zoomBy - https://api.highcharts.com/class-refere ... iew#zoomBy

Here's a simple example of the zoomBy method: https://jsfiddle.net/BlackLabel/4kmgf1wx/

If you decide to use the setView method, you'll have to choose not only zoom, but also the center property which is an array of 2 numbers - longitude and latitude.

Let me know if that helps.

If not, please share a simple JSFiddle demo with your map where you struggle to achieve a certain result.

Best regards!
Jakub
Highcharts Developer
littlerave
Posts: 37
Joined: Sun May 16, 2021 4:17 pm

Re: Zoom by specific amount at specific point

Thank you for the response.

As mentioned, I've tried those methods already.

The current map I'm testing on is 6263 x 8675 and I need to zoom in to a rectangle with the dimensions 777 x 903 with the center at 2621.5 / 4966.5, i.e. I need to zoom in horizontally to 0,1240619511416254% of the whole map and vertically to 0,1040922190201729%. I choose the larger value, so I don't zoom in too much and cut off vertical parts of the area I want to zoom on.
Please keep in mind that this is all dynamic and needs to be calculated and different for every map the user uploads and every country that is active on the uploaded map, so I cannot set this directly in the chartOptions. It needs to dynamically be set when the series has been loaded from the database.

The setView method does center on the correct point, but the zoom is all wrong. I would assume that 0 is no zoom at all, but with that number it is already zoomed very far in. I can set a zoom of -1 to zoom out by 100% (which is still more zoomed in than I need). If I set it to -2 it seems to zoom out by another 100% in relation to the -1, i.e. it is now zoomed out by 300%, thus it is not a linear zoom factor but always in relation to my current one and it seems to always go by steps of 1, so even if I set it to -3, only the -1 is in relation to my current level, the -2 is then in relation to the -1 and the -3 is in relation to the -2, so I'm now zoomed out by 700%. How would I ever calculate the exact zoom number I need to land at the exact rectangle? I don't even know how far zoomed in it is in the first place ...

The zoomBy method has the same zoom issue. I can set it to 1 to zoom in to 50% of the current zoom status. How do I ever calculate a specific rectangle from this? If 1 is 50% and 2 is 75% and 3 is 87.5% ... the zoom factor is not linear and thus I am unable to calculate the exact zoom level for this specific rectangle I have.

I need a zoom method that has a linear zoom functionality, which your deprecated zoom method does, a value between 0 and 1 for zooming in. Which is why this method does zoom properly. Unfortunately it doesn't center correctly.
jakub.s
Posts: 1222
Joined: Fri Dec 16, 2022 11:45 am

Re: Zoom by specific amount at specific point

Thanks!
How would I ever calculate the exact zoom number I need to land at the exact rectangle? I don’t even know how far zoomed in it is in the first place ...

If you want to zoom into a specific region of the map, with desired bounds in terms of projected units given as { x1, y1, x2, y2 } you can use fitToBounds method. Please check the demo below to see an example of that method usage.

Live demo: https://jsfiddle.net/BlackLabel/7emtqsh1/
API Reference: https://api.highcharts.com/class-refere ... itToBounds

To check the initial zoom, you can check the value of chart.mapView.zoom variable.
Live demo: https://jsfiddle.net/BlackLabel/xfhzk2re/
The zoomBy method has the same zoom issue. I can set it to 1 to zoom in to 50% of the current zoom status. How do I ever calculate a specific rectangle from this? If 1 is 50% and 2 is 75% and 3 is 87.5% ... the zoom factor is not linear and thus I am unable to calculate the exact zoom level for this specific rectangle I have.

Using zoomBy method you cannot think of zoom in terms of a specific rectangle you want to zoom-in. As you can read in the API docs, the first parameter of zoomBy method is called howMuch, which means the amount of zoom to apply.

API Reference: https://api.highcharts.com/class-refere ... iew#zoomBy

This zoom factor is linear, but not in every case.

As you can see in this demo: https://jsfiddle.net/BlackLabel/o38fLe1v/ if you have set a width of the chart, which allows to use linear zoom, it will behave like a linear, but if you will set a small width, like here: https://jsfiddle.net/BlackLabel/5hy7qo26/ the zoom won’t be linear, due to provide the best zoom experience (related to the chart dimensions).
I need a zoom method that has a linear zoom functionality, which your deprecated zoom method does, a value between 0 and 1 for zooming in. Which is why this method does zoom properly. Unfortunately it doesn’t center correctly.

If you want to zoom-in into a specific coordinates, you can pass a second parameter in zoomBy method as coords, which are optional map coordinates to keep fixed during the zoom.

Best regards!
Jakub
Highcharts Developer
littlerave
Posts: 37
Joined: Sun May 16, 2021 4:17 pm

Re: Zoom by specific amount at specific point

Sorry for the late reply. The zoomToBounds method worked perfectly in my case. It even allows for a padding so I don't have to calculate that in, which is perfect. Thanks.

Return to “Highcharts Maps”