I'm building an application that plots measurement data using the QtCharts library. Some important things that should be mentioned are:
I am dealing with time series.
The measurement data is obtained offline, which means it has already been obtained. My program just reads it from the file and displays it.
I am working with a for-loop that takes the data points as chunks of a fixed size (say 10000) and adds them to the scatter series. This way, the data is being "replayed" and the user can see the progression of the measurement data at many intermediate time points and not just after all data points have been plotted.
I am working with a whole lot of data points, on the order of millions.
At every iteration of the loop a new QScatterSeries is created, data points are appended, the scatter series is added to the chart and then the chart view is repainted. In the beginning it is quite fast, but as time progresses the number of points to be painted increases and the painting process becomes slower and slower.
I know for a fact that I can reimplement the paintEvent of the chart view class to get it to redraw only part of itself. I think I can make it faster by updating only the parts that have new data points. I will just calculate the coordinates of the region where new data points were added and use the paintEvent. But how do I do it? I tried using the setClipRect() method of QPainter, but couldn't do it. Thanks in advance.
QGraphicsView/QGraphicsItem don't implement it.
They don't because it's unfortunately generally impossible: the displayed items (e.g. series) are scaled and antialiased. In the old days when everything was displayed in device units, and was drawn directly on the window, doing partial updates made sense: you'd first use a fast blit to shift the existing plot, and then draw new points.
Alas, in any non-trivial scene viewer, the relationship between the horizontal device (widget) units and the item (chart/series) units is not 1:1, so there's no general way to scroll the data in device units and have it still make sense in chart units.
There are is a workaround: Scroll by some larger-than-needed (rounded up) number of device units, then back-calculate what scroll it yields in chart units, and then offset the chart by such a number (i.e. move the X axis range accordingly).
This could be implemented with relative ease in the sources of the QGraphicsView system, as the chart builds on it. There's no way to "bolt this on" externally - you need to add such support directly to QGraphicsView and QGraphicsItem infrastructure, and then have the QChartView leverage it. If you're not in the habit of building your own Qt, you can of course copy the necessary classes over to your codebase and rename them.
Related
I can't seem to find a guide on Qt's widget / layout system that explains how it all fits together. Only examples of what to do in typical use-cases.
Slightly more specifically:
When my widget (which has a QGridLayout of child widgets) is resized, I want to:
layout the children, so obtain their sizes,
do some stuff before everything gets repainted
have the children painted, presumably by calling update()
How do I slot number 2 between 1 and 3? If the answer is, "Your design is bad, don't do this", then this is my specific problem:
I have a PlotFrame class that shows Cartesian graphs. It houses a PlotPane (the main area of the graph) and a dynamic number of axes. The PlotPane and Axis classes are QWidgets laid out with a QGridLayout and I'm using explicit double buffering so I can display transient mouse-driven features on top of the base buffered image.
When the PlotFrame is resized, the number 2 in the list above is this:
Create the buffered image of the axes whose ticks and labels dynamically depend on the length of the axis on the screen, this also calculates positions of grid lines for display in the main PlotPane.
Get the grid line positions just calculated and pass them to the PlotPane.
Create the buffered image of the PlotPane.
Once this is done, I'm happy to have Qt do its asynchronous stuff, and to deal with any transient graphical features in each of the widgets' paintEvent() functions.
Thanks in advance. I'm want to get a better understanding of how this all works but don't really want to be digging in the source code if I can help it. I find that Qt's own docs lack depth / are confusing to navigate. There are some great guides I've found (eg. flylib.com) but they are well out of date, therefore misleading. I'm currently using Qt6.
EDIT: After further experimentation, it seems that the parent widget (PlotFrame) resizeEvent() gets called after its children have been laid out and before they are painted. Is this so? Can I rely on this?
I read the following in the Qt Documentation.
Qt documentation on QPainter
The original question on SO, that I looked into.
So, I had two classes, with their own paint() functions. The paint functions would be called upon receiving their respective paint events, that were triggered on different and independent actions by the user. This worked fine.
Now for some reason, I need to show and update both the objects at the same time.
So simply, adding both of the items to the scene does not work. Only one of them is shown and updated. Refactoring the code is not an issue for me. I can re-arrange the two classes so that they are both drawn from one paint().
But this really makes me wonder then, and this is my question (for which I've googled a bit too), how are scenes with many dozens of objects then painted simultaneously (at least they give an illusion of concurrency)? Using threads somehow or through some time-based interleaving?
Maybe it's a silly question. I dunno.
It is indeed a silly question about an imaginary problem that doesn't not exist in reality. The graphics view will schedule consecutive draws for the items in the order needed to produce the desired result. Now if your code doesn't implement the desired result, that's a whole different subject. There is no concurrency, those are consecutive operations that only take place in the main thread.
If your drawing is very complex, draw using a secondary thread on a QImage and use the QImage as cache to draw your items in their respective paint functions.
Now for some reason, I need to show and update both the objects at the
same time.
What might that reason be? What does "at the same time" mean? In a single frame? Is a millisecond apart too much to qualify for "at the same time"?
Re QWidget painting: The paint events are delivered to individual widgets by the widget compositor. The way it works with the default raster back end is as follows: The topmost widget in the hierarchy is backed by a QImage. When any of the sub-widgets are to be repainted, the compositor delivers composite paint events to the widgets that overlay the area to be repainted. This is done sequentially as the compositor traverses the widget graph.
Re QGraphicsItem painting: The paint "events" are delivered to individual items by the scene. The items to be painted are selected basing on what area needs updating, what items were explicitly marked for update, etc. The painter is set up to correctly composite the item with the rest of the scene. The calls to paint are done sequentially as the scene traverses the item graph.
It would be, in general, impossible to do these in parallel due to data dependencies, and the fact that there are no requirements for the paintEvent or paint to be thread-safe.
Your problem is not directly related to this at all, you need to show a complete code example that reproduces your issue. Most likely your implementation of the item ignores some of the requirements for the item's behavior.
I am building an editor using C++/Qt which has a click-and-drag feel to it. The behavour is similar to schematic editors (Eagle, KiCAD, etc), Microsoft Visio, or other programs where you drag objects from a toolbar into a central editing area.
My problem is that when the user clicks inside the custom widget I want to be able to select the instance of the box-like object and manipulate it. There will also be lines connecting the boxes together. However, I can't decide on an efficient method for selecting those objects.
I have two main thoughts on how to do the programming for this: The first is that the widget which is drawing the entire editor would simply encapsulate every one of the instances of the box. The other is to have each instance of the box (which is in my Model) carry with it an instance of a QWidget which would handle rendering the box (which would be in my View...but it would end up being strongly attached to the model). As for the lines connecting them, since they don't have a square bounding boxes they will have to be rendered by the containing widget.
So here is the summary of how I see this being done:
The editor widget turns into a container which holds the widgets and the widgets process their own click events. The potential issues here are that I don't know how to make the custom widget turn into a layout which lets click-and-drag functionality.
The editor widget takes care of all the rendering and processes the mouse clicks (the easier way in that I don't have to worry about layout...its just selecting the instances efficiently that I don't know what would be best).
So, now that there is a bit of background, for the 2nd method I plan on having each box-like instance having a bounding rectangle and the lines being represented by 3-4 pixel wide bounding rectangle segments (they are at 90 degree angles). I could iterate through every single box and line, but that seems really inefficient.
The big question: Is there some sort of data structure I can hold rectangles in and link them to widgets (or anything else for that matter) and then give it two coordinates (such as mouse coordinates) and have it spit me out the bounding box or linked object that those coordinates are inside of?
It sounds like your real question is about finding a good way to implement your editor, not the specifics of rectangle intersection performance.
You may be interested in Qt's "Diagram Scene" example project, which demonstrates the QGraphicsScene API. It sounds like a good fit for the scenario you describe. (The full source for the example ships with Qt.)
The best part is that you still don't have to implement hit testing yourself, because the API already provides what you are looking for (e.g., QGraphicsScene::itemAt()).
It's worth noting that internally, QGraphicsScene uses a simple iterative method to perform hit tests. As others have pointed out, this isn't going to be a serious bottleneck unless your scenes have a lot of individual items.
This is related to one of my other questions.
If I am tiling a large image by creating a separate QGraphicsItem (with the raster data as its pixmap), how do I keep track of the QGraphicsItem's position within the scene? Obviously for raster data, it is important to keep all the tiles "touching" to make a continuous image and they also have to be in the right place so the image doesnt look jumbled.
Does each tile have to have positioning methods that move it in relation to it's neighbors on the top/left/bottom/right? This seems kind of clunky. Is there a better way to make them all move together?
In other words, if I pan the scene with scroll bars, or pick up the image and drag/move it around in the scene, I want all the tiles to also move and stay in the right position relative to each other.
What is the best approach for controlling the layout, which tiles need to be rendered (i.e. only the visible ones), and populating the data only once it is needed? Also, once a tile has been rendered, is the data from it ever dropped, and repopulated from the image file, say if it stays out of view for a while, then comes back later if someone pans to that section?
There are (more than) 2 ways of doing this:
Use QGraphicsItemGroup which
handles grouping of your tile items
for you. It moves, selects, updates
it's group members as if they are
one. I've never used it but from the
doc, it seems to work with typical
applications.
Paint the tiles yourself in the
paint() of one custom item. This
gives you total control on how to
place and draw the tiles while the
item truly acts as one item since it
is, well, one item. This is what I
do.
I have a ListView-like control that displays a list of items of various heights. The contents of the list, and the heights of the items can change – a background thread is populating the list and calculating the layout of each item, possibly even while the user is scrolling the content.
Which brings me to my question: How do I display a useful vertical scrollbar for this view? I’ve seen cases (notably web browsers) where the slider “jumps away” from the mouse cursor while the user is dragging it, the result of the underlying content growing in height. I don’t want that.
So far
Instead of the slider representing the viewport height relative to the content height, maybe it could represent a point in a timeline instead? (The items are sorted by timestamp). This would at least prevent the scrollbar from changing as item layouts are calculated.
Get rid of the scrollbar altogether and use a forward/backward rocker switch like the one used in Picasa (the further the slider is pulled upwards or downwards, the faster the view is scrolled, until the user releases the slider). If I take this route, are there any controls you can recommend?
I am using Qt, but this applies to UI design in general.
IMO the fundamental problem with a classic scrollbar is that due to background population, the valid range is changing - and thus, the meaning of a scrollbar position changes.
If you can predict the full range of items, you can still provide a scrollbar and replace yet-unknown items with "loading...".
Otherwise, a rocker (is that an official name?) would be the next best thing to use.
However, since you have a dedicated scale (timeline), it might be better to have separate buttons that jump a dedicated time (e.g. one minute, one hour, one day, ..). For a fancier look, you could create a rocker with "hot" areas that jump for a specific time, whereas the areas inbetween are interpolated (linear or or logarithmic, depending on the scale to cover).
i.e. line this (drawing just the "backward" half):
--------------------------
|##|XXXXXXX|##|XXXXXXX|##|
--------------------------
-1h -1m -1s