amcharts / amcharts5

The newest, fastest, and most advanced amCharts charting library for JavaScript and TypeScript apps.
Other
353 stars 95 forks source link

[MOBILE DEVICE] Map is not rotating when event listeners are added to some children #1199

Closed erwanlfrt closed 11 months ago

erwanlfrt commented 11 months ago

This bug only appears on mobile devices. When a map chart has some children that are listening to events such as "pointerdown" or "click", the map is not rotating at all.

A basic example would be a map chart with a background rectangle and an event listener "pointerdown" on the background rectangle.

const map = root.container.children.push(MapChart.new(root, {
  panX: "rotateX",
  panY: "translateY",
  projection: geoMercator(),
  homeGeoPoint: { latitude: 2, longitude: 2 }
}))

// Add background color to map
const background = Rectangle.new(root, {
  fill: color(0xff0000),
  fillOpacity: 1
})

background.events.on('pointerdown', () => {
  // do something...
})
map.chartContainer.set('background', background)

Code pen: https://codepen.io/erwanlfrt/pen/rNPKegM -> (in laptop mode no problem but switch on mobile mode and you will have the bug).

NB: I have noticed that if you comment the pointerdown event, it works on both mobile and desktop mode BUT on mobile devices, if you try to scroll by starting over a country, the map won't rotate BUT if you start to scroll over the sea then map will rotate (and then after you can start scrolling over a country and it works again). There is an obvious problem with the management of pointer, click and drag events.

Environment (if applicable)

Quick bug analysis In file MapChart.js, I found this part of the code where the bug starts:

_handleChartDown(event) {
  this._downZoomLevel = this.get("zoomLevel", 1);
  let count = $object.keys(this.chartContainer._downPoints).length;
  if (count == 1) {
      // workaround to solve a problem when events are added to some children of chart container (rotation stops working)
      const downPoint = this.chartContainer._downPoints[1];
      if (downPoint && (downPoint.x == event.point.x && downPoint.y == event.point.y)) {
          count = 0;
      }
  }

The line const downPoint = this.chartContainer._downPoints[1]; is returning undefined on mobile devices which explain why the count variable is never set to zero.

In my case for example when I am using a mobile device, this.chartContainer._downPoints is an object with one key 3 and a down point associated as value. In desktop/laptop mode, this.chartContainer._downPoints is an object with one key 1 and a down point associated as value.

I guess each key in _downPoints is related to the pointerId value.

It's the first time that I am looking in amcharts code so I don't know what this _downPointsobject represents and how to fix this buggy workaround. Any help would be appreciated :)

martynasma commented 11 months ago

Adding pointer events on element's background seems wrong. I suggest you add your pointerdown event on on chart's chartContainer directly instead of background:

map.chartContainer.events.on('pointerdown', () => {
  // do something...
})
erwanlfrt commented 11 months ago

Thank you @martynasma for your suggestion. Your solutions works but it's a bit problematic if some children are listening to the same event because it's bubbling. It's still possible to call e.target.events.stopParentDispatch() for events such as pointerdown or pointerup but it's not working for click event.

martynasma commented 11 months ago

Well, you need those events bubbling up if you want to retain both drag rotation and custom pointer events.

Besides clicking map should not affect its rotation, so you should be good to go.