Problems scrolling large QGraphicsScene with QGraphicsView
-
Hi.
I've just ran into a problem that's more or less the same as:
http://www.qtcentre.org/threads/45375-QGraphicsScene-coordinate-space-larger-than-INT_MAX-but-QGraphicsView-scroll-brokenThat is, I can't properly scroll through a large QGraphicsScene because scrollbar positions are 32-bit integers. My case is slightly different from the above, though, as:
All scene coordinates actually fit into an int, so I can scroll just fine in a "normal" case. However, the view also supports scaling, and when I zoom in problem start to occur, as (apparently) the scrollbars work in scaled coordinates. So, the range will be set to something like sceneRect().left()<scale>, sceneRect().right()<scale>. These values will easily overflow when converted to ints even though the raw coordinates won't.
The actual scene is typically quite a bit smaller than the actual value range - sceneRect().left() is larger than zero. As such, I might shift the scene coordinate system and everything could be fine. However, the scene may also be extended during the lifetime of the view by adding data to the left or to the right, or alternatively, shrunk by removing data from either end. This is much easier if the value range is such that there is room for new data on both sides.
Anyhow, this means that all would be fine if I could add an extra translation stage between the scrollbar values and scene coordinates. Also, the problem would essentially be solved if I could make the scrollbar work in unscaled coordinates.
But how do I actually do this? I mean, one simple answer is "use an external scrollbar", but I've experimented with this, and just got frustrated. It simply doesn't seem like the QGraphicsView is designed to allow external scrolling, even though the documentation talks likely about using translate() to navigate the scene. The main problem is that there appears to be no direct way (like a signal) to catch updates affecting the view that are NOT done though the main navigation setup, like scaling, scene extensions etc - and I have a lot of that scattered throughout the code.
Any thoughts, anyone?
-
toralf,
What about scaling down ALL your scene coordinates into a range which works fine with the scroll bars and which also is extendable enough for the future?
The scene coordinates are specified in floating point values so multiplying all used scene coordinates by a sufficient small factor (e.g. 1.0e-6) should not lead to any precision problems. What this means is to have a coordinate transformation which has to be performed when going from your physical coordinates to the scene coordinates and the inverse transformation when going from scene to physical; multiplying by 1.0e-6 or, for the inverse, by 1.0e6 for example.
Peer
-
I'm not sure if pure scaling would help. I mean, if I scale down the scene coordinates, won't I have to scale up more when transforming to view/device coordinates? This would mean going back where I came from, as the sliders operate on scaled (to view/device) coordinates.
Or am I overlooking something? -
Ok, maybe I do not fully understand what you are trying to achive, so first make sure I understand.
You have a scene with items which gets visualised by a view which supports zooming. The items in the scene are placed in such a way that the size of the scene does not exceed the integer range.
If you now zoom in, the scroll bars appear and here you say that the scroll bar does not work properly anymore, right?
Do you have a small example project which shows this problem?
-
[quote author="PeerS" date="1346248524"]Ok, maybe I do not fully understand what you are trying to achive, so first make sure I understand.
You have a scene with items which gets visualised by a view which supports zooming. The items in the scene are placed in such a way that the size of the scene does not exceed the integer range.
[/quote]
Yes, that's right.[quote author="PeerS" date="1346248524"]
If you now zoom in, the scroll bars appear and here you say that the scroll bar does not work properly anymore, right?
[/quote]
More or less, yes. The scrollbars are there all the time as the policy is Qt::ScrollBarAlwaysOn, but initially, the sliders (or handles if you like) fill the entire bars and scrolling isn't possible (because I call fitInView() with the entire sceneRect.) Then as I zoom in, the sliders get smaller, and the scrollable range is increased, just as expected. But then, at a certain point, the horizontal one returns to the "full" size, and scrolling is once again no possible. Closer inspection seems to reveal that this happens because maximum() and/or minimum() has overflowed. Again, the problem seems to be that QGraphicsView internally calls something like
@horizontalScrollbar()->setRange(sceneRect().left()*scale, sceneRect().right()*scale);@[quote author="PeerS" date="1346248524"]
Do you have a small example project which shows this problem?
[/quote]
No. Maybe I'll try to create one, but I don't have time right now ;-/The real application is a type of graph, rather like the one described in the post I referred to earlier. I mention problems with the horizontal scroll bar, but I'm assuming one could have the same issue with the vertical one - it's just that I don't have very large values along that axis.
-
[quote author="toralf" date="1346251193"]
@horizontalScrollbar()->setRange(sceneRect().left()*scale, sceneRect().right()*scale);@
[/quote]
I think this is the interesting point here if we assume that the value scale becomes larger as you zoom in (can you confirm this?). The scene rect is usually given by its content, so let us assume that we have a rect inside the scene with a position and size of QRectF(QPOintF(0.0, 0.0), QSizeF(1.0e8, 1.0e8)), so rather large.The maximum integer value is 2^31-1, so sceneRect().right() * scale should be smaller than this maximum value. If you zoomed in by a factor 'f' then the maximum possible sceneRect().right() would be:
sceneRect().right() * scale < maxInt
=> sceneRect().right() < maxInt / f
=> f < maxInt / sceneRect().right()
so a maximum zoom factor of around 21.47 is possible. With a larger zoom factor the resulting value for the maximum scroll bar value exceeds the integer range.
So the right scene rect value has to be smaller than the maximum integer value divided by the zoom factor. This also means that if you keep sceneRect().right() small you can have larger zoom factors. And small sceneRect().right() values can be achieved by content with small coordinates and sizes in the scene coordinate system. This is what I meant when saying to scale down the scene content, the coordinates and size values are getting smaller in the scene coordinate system resulting in a smaller sceneRect().right() value.
Does this make any sense?
-
And btw, in my experience it is usually necessary to limit zoom factors for zooming in and out ;)
-
[quote author="PeerS" date="1346252016"][quote author="toralf" date="1346251193"]
@horizontalScrollbar()->setRange(sceneRect().left()*scale, sceneRect().right()*scale);@
[/quote]
I think this is the interesting point here if we assume that the value scale becomes larger as you zoom in (can you confirm this?).
[/quote]
Well, I think I can confirm it after inspecting how the scrollbar attributes are updated as I zoom in, but I'd be quite happy to be proved wrong ;-)[quote author="PeerS" date="1346252016"]
The scene rect is usually given by its content, so let us assume that we have a rect inside the scene with a position and size of QRectF(QPOintF(0.0, 0.0), QSizeF(1.0e8, 1.0e8)), so rather large.The maximum integer value is 2^31-1, so sceneRect().right() * scale should be smaller than this maximum value. If you zoomed in by a factor 'f' then the maximum possible sceneRect().right() would be:
sceneRect().right() * scale < maxInt
=> sceneRect().right() < maxInt / f
=> f < maxInt / sceneRect().right()
so a maximum zoom factor of around 21.47 is possible. With a larger zoom factor the resulting value for the maximum scroll bar value exceeds the integer range.
So the right scene rect value has to be smaller than the maximum integer value divided by the zoom factor. This also means that if you keep sceneRect().right() small you can have larger zoom factors. And small sceneRect().right() values can be achieved by content with small coordinates and sizes in the scene coordinate system. This is what I meant when saying to scale down the scene content, the coordinates and size values are getting smaller in the scene coordinate system resulting in a smaller sceneRect().right() value.
[/quote]
Yes, but the problem is that the this would also make the actual graphics as displayed on the screen a lot smaller at a given scale value, so I would have to zoom in more to see what I saw earlier. Thus achieving absolutely nothing. Or am I missing something very fundamental here?[quote author="PeerS" date="1346252016"]
Does this make any sense?
[/quote]
Except for the above reservation, yes. And, perhaps you are on to something - maybe I can adjust the scene coordinates and zoom limits to get a workable setup. I think I'll have to offset rather than scale, though.However, it still bugs me that I can't find a simple way to take more control of the scrolling. Not to mention that the scrollbars just ought to use doubles for the range and position values....
-
[quote author="toralf" date="1346253807"]
Yes, but the problem is that the this would also make the actual graphics as displayed on the screen a lot smaller at a given scale value, so I would have to zoom in more to see what I saw earlier. Thus achieving absolutely nothing. Or am I missing something very fundamental here?
[/quote]
Yes, here you have a misunderstanding.Let us assume that we have a scene which is 1000 units wide. Let us further assume that the part of the scene rect which is displayed by the view is 10 units wide. This means we have a scale factor of 100.
Let us now assume that we have a scene which is 100 units wide, a scale factor of still 100 results in a visible width of 1 unit in the view.
In both cases we see 1% of our width, the scale factor is the same!!
If we now take the formula we talked about earlier then we see, that the maximum possible zoom factor is larger for the second case:
- f_1max = maxInt / 1000 => maxInt = 1000 * f_1max
- f_2max = maxInt / 100 => maxInt = 100 * f_2max
=> 1000 * f_1max = 100 * f_2max
=> 10 * f_1max = f_2max
So in this case the maximum zoom factor for case where we have a scene width of 100 is ten time larger than for the scene which has a width of 1000.
-
Not time to follow up on this in the past few days... But,
[quote author="PeerS" date="1346254991"][quote author="toralf" date="1346253807"]
Yes, but the problem is that the this would also make the actual graphics as displayed on the screen a lot smaller at a given scale value, so I would have to zoom in more to see what I saw earlier. Thus achieving absolutely nothing. Or am I missing something very fundamental here?
[/quote]
Yes, here you have a misunderstanding.Let us assume that we have a scene which is 1000 units wide. Let us further assume that the part of the scene rect which is displayed by the view is 10 units wide. This means we have a scale factor of 100.
Let us now assume that we have a scene which is 100 units wide, a scale factor of still 100 results in a visible width of 1 unit in the view.
In both cases we see 1% of our width, the scale factor is the same!!
[/quote]
Maybe I'm a bit slow, but I still don't follow you. The problem is perhaps that I don't so much want specific factors or fractions, but instead wish to display a specific amount of data in terms of some fixed underlying range or related to real-world features.Let's for the sake of argument say that your 1000 units wide scene represents a real-world area spanning 1000 m in one direction, meaning one pixel represents 1 m at scale 1. Now, if I use a scale factor of 100, 1 m is 100 pixels on screen, or one pixel is 1 m/100 = 1 cm.
Then I scale my coordinates so that the width is 100. Since I still want it to represent the same real-world data, that must mean one unit becomes equivalent to 10 m, right? Now, if I display this at scale 100, won't I get 100 pixels on screen for 10 m in the real world? Then one pixel represents 10 m/100 = 10 cm, not the 1 cm I had earlier. If I actually want my 1 cm-per-pixel display, I will have to scale 10 times more, i.e. use a factor of 1000. Which means I'm back to square one in terms of scaled scrollbar range, since 1000100 = 1001000...
[quote author="PeerS" date="1346254991"]
If we now take the formula we talked about earlier then we see, that the maximum possible zoom factor is larger for the second case:- f_1max = maxInt / 1000 => maxInt = 1000 * f_1max
- f_2max = maxInt / 100 => maxInt = 100 * f_2max
=> 1000 * f_1max = 100 * f_2max
=> 10 * f_1max = f_2max
So in this case the maximum zoom factor for case where we have a scene width of 100 is ten time larger than for the scene which has a width of 1000.
[/quote]
-
[quote author="toralf" date="1346763514"]
... If I actually want my 1 cm-per-pixel display ...
[/quote]Ok, you want or need to keep the same maximum resolution, I did not assume this so far, probably did not read you initial description well enough. Then yes, you are right, if you want the same maximum resolution then it does not matter what scaling you use, no scaling can increase the resolution, the limit is given by the maximum integer value used for the 32 bit range values.
So you have two choices:
- Accept that there is a resolution limit and make sure that the user can only zoom as far into your scene as the resolution allows it.
- Use a different mechanism for the scrolling, therefore replacing the scroll bars provided by the framework. Possibilities here could be to use a scroll bar implementation which uses 64 bit range values as described in the post you cited, or implementing your own scroll handling.
I would first investigate 1). From my experience it is seldom necessary or good to zoom in this far, however of course there are scenarios where you want to do exactly that.