amcharts / amcharts5

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

Chart wrongly sized when in scaled container #812

Closed s4b2x closed 1 year ago

s4b2x commented 1 year ago

The chart size behaves weird when it is inside a scaled (transform: scale(0.5); 50% scaling only as an example) container:

https://codepen.io/sebu10n/pen/ZEMQeWZ

This works fine in "@amcharts/amcharts5": "5.1.7" - not anymore in v5.2 or later - meaning the chart just gets scaled like any other element.

amcharts now applies a wrongly calculated size to the canvas elements and some other (div) elements:

grafik

In this case .container-500x500 is 500px x 500px, .chart-container-scaled is the same size but scaled. The inner elements get the scaled size applied what scales them down one step too far.

Environment

I noticed the bug using node, nuxt, vuetify... but it can be reproduced with vanilla JS.

Bug started at around amcharts@5.2

Pauan commented 1 year ago

Thanks for the detailed report.

It seems that scale applies to child elements too, so the scale is first applied to the chart-container-scaled element (reducing its size by half) and then it's applied a second time to the chartdiv, so the chart ends up being a quarter of its original size.

So even though the size is correctly calculated by amCharts, the browser behavior of scale causes it to break:

https://stackoverflow.com/questions/27051600/css-transform-scale-dont-scale-child-element

Why are you using transform: scale(0.5) in the first place?

s4b2x commented 1 year ago

Why are you using transform: scale(0.5) in the first place?

The reason I use scale (not exactly by 0.5 - that was just an example) is this: I am building a web application that lets the user configure a slideshow. Some slides may contain charts (preferaby amcharts - awesome features!). The config page has a preview area with the current slide. That area is not fullscreen because we need a navigation, inputs for adjusting the slide contents and so on. But the preview should of course show the slide as it would look like in fullscreen, just smaller. That is the perfect usecase for transform: scale().

So even though the size is correctly calculated by amCharts, the browser behavior of scale causes it to break

I know how transform: scale() works. It doesn't get applied "a second time" to child elements. transform just applies to the complete element and all its properties. Imagine a div that's transform: translateed. It wouldn't make sense if its content would stay unmoved.

See also the middle part of this introductory sentence from the Mozilla Web Docs:

By modifying the coordinate space, CSS transforms change the shape and position of the affected content without disrupting the normal document flow.

The image in the second DIV in my example below gets scaled along with the rest of the DIV. If now I would measure the displayed size and apply this as an inline style we would have the same symptom like I described in my first post. See the last DIV below.

grafik https://codepen.io/sebu10n/pen/wvEWGOL

Pauan commented 1 year ago

I know how transform: scale() works. It doesn't get applied "a second time" to child elements. transform just applies to the complete element and all its properties.

Let me explain what I mean. amCharts displays in a <canvas>, and amCharts needs to determine the size of the canvas. It uses getBoundingClientRect() to figure out the width / height, and then uses that for the canvas.

So in your example, amCharts uses getBoundingClientRect() on the chartdiv and the width / height is 250px. So it sets the canvas's width / height to 250px.

However, because the parent has scale(0.5) the browser resizes the canvas so that it's 125px even though it has specified 250px. That's why it is being displayed too small.

Imagine a div that's transform: translateed. It wouldn't make sense if its content would stay unmoved.

The content does stay unmoved. The transform CSS style only changes the visual appearance, but the actual size / position stays unchanged. It's purely a visual illusion. Here is an example demonstrating that:

https://codepen.io/team/amcharts/pen/JjaKevB/ae1cffeb57a9bcbfe13a8e67c2f0f2cb

Even though the red box has been "resized" with scale(0.5) it isn't actually resized, which is why there is a gap between the red and green box. This is different from width: 50%; height: 50% (which actually resizes the box).

It's very difficult for amCharts to support transform, because getBoundingClientRect() always takes into account transform, and there isn't any way to prevent that. Other libraries have the same problem. We can't use clientWidth / clientHeight because that has other bugs which break things.

And it's basically impossible for us to support things like rotation / skew.

In addition, because scale doesn't actually resize the element (it only changes the visual appearance), that means it doesn't trigger resize observers, which means that amCharts won't be able to detect any changes to the scale, which means it can't properly redraw the canvas.

That also means that hit detection is completely messed up if you use transform: translate, and there's nothing we can do to fix that.

chunlampang commented 1 year ago

I got this issue too. Will amcharts provide a option to choose clientWidth/clientHeight or getBoundingClientRect?

s4b2x commented 1 year ago

The content does stay unmoved. The transform CSS style only changes the visual appearance, but the actual size / position stays unchanged. It's purely a visual illusion.

Of course I meant "visually unmoved".

However, because the parent has scale(0.5) the browser resizes the canvas so that it's 125px even though it has specified 250px. That's why it is being displayed too small.

That sounds like it was a browser bug, which it is not. It is the intended behaviour that an element with all its child elements gets transformed as one.

It's very difficult for amCharts to support transform, because getBoundingClientRect() always takes into account transform, and there isn't any way to prevent that. https://github.com/okonet/react-container-dimensions/issues/5 have the same problem. We can't use clientWidth / clientHeight because that has other bugs which break things.

That's a pity. Especially when considering that it worked back then in v5.1.7:

v5.1.7 v5.2.31
grafik grafik

That also means that hit detection is completely messed up if you use transform: translate, and there's nothing we can do to fix that.

Hit detection is not an issue for us because the slides won't be interactive.

For now I have pinned the node module version to 5.1.7. Maybe we will implement a workaround for the getBoundingClientRect "feature" in our project.

Pauan commented 1 year ago

That's a pity. Especially when considering that it worked back then in v5.1.7:

It worked because we used clientWidth, but that causes other scaling issues. Things like clientWidth are very old, which is why it doesn't support transform, but that also means it has other issues which getBoundingClientRect doesn't have.

After doing a lot of brainstorming, we decided upon this solution:

let scale = 2;

const root = am5.Root.new("chartdiv", {
  calculateSize: (dimensions) => {
    return {
      width: dimensions.width * scale,
      height: dimensions.height * scale,
    };
  },
});

This will cause the chart to display twice as big, to compensate for the scale(0.5).

You will have to adjust the scale yourself, based on the transform, and you will have to manually call root.resize() when changing the transform.

This new feature will be in the next version.

s4b2x commented 1 year ago

Thankyou, this will be fine!

martynasma commented 1 year ago

Iplemented in 5.3.7.

[5.3.7] - 2023-03-09

Added

Changed

Fixed

Full change log.

Download options.

Make sure you clear your browser cache after upgrading. And feel free to contact us again if you are still experiencing this issue.

s4b2x commented 1 year ago

@Pauan @martynasma Well... I'm not still experiencing this issue... but..

After upgrading (and clearing the cache of course) the chart contents seem to be "corrected" two times which leads to the result that it is scaled as if it wasn't scaled at all - but limited to the area of the scaled chart.

unscaled slide scaled slide in preview area
grafik grafik

(Please ignore the missing graph - the data I'm testing with actually only has timestamps atm.)

The devtools show different sizes on the canvas attributes and style:

grafik

Btw.: both ways from the docs (scale factor or clientWidth/-Height) have the same result.

s4b2x commented 1 year ago

@Pauan @martynasma I forked my codepen to show the problem and how it should look like in my opinion:

https://codepen.io/sebu10n/pen/ExeEpeZ

Interesting discovery: When you click on "Embed" in a codepen, you can scale the result. That works fine with amCharts. They use an iframe. Maybe we have to use an iframe as well.