PDA

View Full Version : Scaling physical units in tick labels and axis title.



IThu8uu1
31st July 2013, 16:00
Hello,

I'm using Qwt to plot measurement data (distance) against time. The plot is updated continuously with new data and only the last x samples (x is set by the user) are plotted. So far this works just fine. The x-value is an integer value encoding time, where one digit is (usually) worth 50µs (also user adjustable, but always known to me).

For now the x-axis is labelled with the raw integer value. I'd like for the user to be able to choose a physical unit (µs, ms, s, min) for labeling or chose a unit automatically.

I've fiddled around with a custom QwtScaleDraw and got that to work for fixed values, but that is quite ugly and apparently QwtThermo can convert between physical units automagically: "By default, the scale and range run over the same interval of values. QwtAbstractScale::setScale() changes the interval of the scale and allows easy conversion between physical units. (http://skozlovf.github.io/doxygen-qmi-style/qwt/class_qwt_thermo.html)"

However, I don't understand how this works. I read the QwtThermo source and tried to figure out what is going on but to no avail. I tried to set QwtPlot::setAxisScale() with scaled values ([0]), but that didn't work.
Could someone tell me if this is possible and if so, how to go about it?

Pertaining to automatic scaling, I know I have to implement a custom QwtScaleEngine dealing properly with time: [0], [1].

Thank you for your time & best regards

[0] http://www.qtcentre.org/threads/32570-Unit-conversion-in-QwtPlot
[1] http://qt-project.org/forums/viewthread/12173

Uwe
31st July 2013, 19:55
I've fiddled around with a custom QwtScaleDraw and got that to work for fixed values, but that is quite ugly ...
Overloading QwtScaleDraw::label() is the recommended way to modify the representation of a tick label - what is wrong with this ?


... and apparently QwtThermo can convert between physical units automagically
As the result of a misdesign of the Qwt classes you had to set the scale ranges twice - sold in the docs as a feature ... :rolleyes:
This API had been there since Qwt 1.0, but finally with Qwt 6.1 this is history and now you only have to set the scale range once ( QwtAbstractScale::setScale() ).


Pertaining to automatic scaling, I know I have to implement a custom QwtScaleEngine dealing properly with time:
In Qwt 6.1 you find QwtDateScaleEngine/QwtDateScaleDraw. They are used in the simpleplot or stockchart examples ( or playground/timescale ). The autoscaler is not working very well yet (TODO), but finding the ticks for a given range is supposed to work properly.
I also have a scale engine for time intervals ( = time elapsed since ) - what is probably, what you are looking for - on my TODO list, but I have not yet started with this one.

Uwe

IThu8uu1
1st August 2013, 16:39
Thank you for your reply.


Overloading QwtScaleDraw::label() is the recommended way to modify the representation of a tick label - what is wrong with this ?

The problem is, when I do autoscaling, that each label text is rendered on its own but the entire axis has the same physical unit. It may occur that the start of the axis is scaled to - for example - seconds, whereas the right end of the scale could be in minutes. I thought of a few ways to handle this, but they all are ugly in one respect or another. So I was wondering if there is another (software-) layer in qwt in which I could hook into. Of course I could take the physical unit from the axis title and put it directly at the label, but this still leaves me with different units at one axis.



As the result of a misdesign of the Qwt classes you had to set the scale ranges twice - sold in the docs as a feature ... :rolleyes:
This API had been there since Qwt 1.0, but finally with Qwt 6.1 this is history and now you only have to set the scale range once ( QwtAbstractScale::setScale() ).

This rather confused me, because QwtPlot has no QwtAbstractScale. This guy (http://www.qtcentre.org/threads/32570-Unit-conversion-in-QwtPlot) basically did what I want to do, but I don't understand how QwtAbstractScale::setScale() can help me with QwtPlot or what I need to do to get similar/same functionality to a QwtPlot instance. Do you have any pointers for me?



In Qwt 6.1 you find QwtDateScaleEngine/QwtDateScaleDraw. They are used in the simpleplot or stockchart examples ( or playground/timescale ). The autoscaler is not working very well yet (TODO), but finding the ticks for a given range is supposed to work properly.
I also have a scale engine for time intervals ( = time elapsed since ) - what is probably, what you are looking for - on my TODO list, but I have not yet started with this one.

I had a look at QwtDateScaleDraw and you are right. This is not exactly what I was looking for. My timestamps are just offsets from the start of the measurement. Their relation to wall clock time is irrelevant/not ascertainable. Additionally, QwtDateScaleDraw can only handle time (differences) down to milliseconds, whereas I need (at least) microseconds.
I'm not sure how to do auto-scaling of units in a live plot. Probably I have to start with the currently right-most tick value and use that to determine to physical unit (i.e. seconds or minutes, µs or ms) for the axis in this moment (If my "time" would be negative, I would have to start at the left most tick label, of course). Then I have to set the axis title, which includes the unit, and I can determine the text for all other labels. it My biggest problem up until now was, that it is not known to which tick a value belongs to when QwtScaleDraw::label() is called. But in QwtDateScaleDraw::intervalType() I saw that I can access all (major) ticks via scaleDiv() and then scale and render the labels accordingly. Is this correct?

Again, thank you for your time and best regards.

Uwe
1st August 2013, 21:56
I'm afraid I still didn't get exactly - beside that you are talking about an axis showing the time since some startpoint - what you want to do.
Maybe it helps if you tell me an example interval ( f.e in microseconds ) and explain where you expect to have the ticks.

Uwe

IThu8uu1
11th August 2013, 15:00
I wanted to label a time axis. My problem is three-fold:
1) The axis can be of different units (milliseconds, seconds, minutes) or automatic selection of an "appropriate" unit.

When a fixed unit is selected to value passed in QwtScaleDraw::label() just has to be multiplied by a fixed factor, which in turn is based on the selected unit. However, automatically selecting an appropriate unit depends on the current offset from which I'm plotting. If the plot goes from 0s to 30s, [s] is the unit I want. If any tick label is above 60s I want minutes instead and above 60 minutes the unit should be hours.

2) If two adjacent labeled ticks are separated by less than the selected unit I additionally want to show the next smaller unit.

For example: let's say the plot is "3 minutes wide" and the QwtScaleEngine (I'm using the "normal", linear one right now) decides to place 5 major tick labels I do not want to display 0, 1, 1, 2, 2 but rather 0:xx, 1:xx, 1:xx, 2xx, 3:xx. If the plot windows is "10 minutes" wide and 5 major tick labels are shown labels like 0, 2, 4, 6, 8 are fine.

3) Because of 1) and 2) the axis title showing the unit has to change based not only on the selected unit but also on the actual values in the plot.

Additionally, the axis title has to be changed not only on user interaction but on the "wideness" the user selects for the plot and the time the plot has been running.

The way I have implemented this right now is by passing an enum value to a custom QwtScaleDraw::label() sub-class, which selects the proper unit if the automatic mode is selected (for example: minute resolution). After that, based on the distance between two major tick labels, it is decided whether the next smaller unit has to be displayed or not (i.e. the selection between [min] or [min:ss]). Before returning a properly formatted QwtText, a signal is emitted carrying the selected unit for updating the axis title.

Although this works, I'm not happy with that code at all: it is very convoluted and letting a QwtScaleDraw decide on the axis label is bad code. Another (not so important) issue is that QwtScaleDraw::label() is const, so that I have to always emit a signal containing the selected unit and not only when the unit has changed (since I cannot modify state by saving an updated unit).

I'm not placing the labels myself right now, but that would be the next step. Obviously, the a custom QwtScaleEngine would have to know about the user-selected unit, because I need custom placement only for [min:ss] or [h:mm] units; [s] or [min] units can be placed with the QwtLinearScaleEngine. But I fear that a custom QwtScaleEngine would duplicate code I wrote for QwtScaleDraw. I think what I'm doing right now in QwtScaleDraw should rather happen in QwtLinearScale, but I don't see a way of telling a QwtScaleDraw which units to use or change the QwtScaleDraw depending on the selected unit.

Do you have any ideas on constructing this sensibly?

Uwe
12th August 2013, 08:59
If the plot goes from 0s to 30s, [s] is the unit I want. If any tick label is above 60s I want minutes instead and above 60 minutes the unit should be hours.
QwtDateScaleEngine implements a method intervalType() to classify a time interval and run different algorithms depending on it. The - not yet existing - QwtTimeIntervalScaleEngine would have to do the same.


If two adjacent labeled ticks are separated by less than the selected unit I additionally want to show the next smaller unit.
QwtDateScaleDraw also implements a method intervalType() to classify a time interval.The - not yet existing - QwtTimeIntervalScaleDraw would have to do the same.


Because of 1) and 2) the axis title showing the unit has to change based not only on the selected unit but also on the actual values in the plot.
You could connect a slot to QwtScaleWidget::scaleChange() signal and adjust your title using QwtDateScaleDraw::intervalType().

My plan is to have QwtTimeIntervalScaleEngine/QwtTimeIntervalScaleDraw in the next non-maintenance release of Qwt, but that won't be soon. If you need it soon I recommend to have a look at QwtDateScaleEngine/QwtDateScaleDraw - many algos you need for your new classes can be found there.

Uwe