|
Multiple Axis with different ranges on the YAxis |
Posted by Davide on Aug-04-2020 17:28 |
|
Hi,
I am working on a Qt C++ application for data analysis. I already managed to create charts and to plot the data on them. In particular I have two different situation on my charts: once where I have one single XYChart where I plot all the datasets using also multiple axes if needed, and one with a MultiChart objects and with one XYChart for every dataset. Now my problem is that I want to achieve something like the image I attached (I'm sorry if it's not so clear but I hope you can get the point with some explanation, I took the image on the net and it has a bad resolution).
What I would like to achieve is that I want the user to choose the percentage of the available height of the window object that a single dataset takes, like the image above. In fact if you look at the yAxes you can see that some yAxes has tha same lenght of the plotArea (that looking at the docs is the default options), but then other yAxis has shorter lenght and then datasets is plotted on the chart accordingly to the corresponding yAxis. Moreover I would like to choose also the position of the dataset on the chart, for example I would like to say that the dataset uses a specific percentage of the available height and that it starts, for example, from the center of the chart, and eventually overlap the datasets (as you can see with the datasets in the bottom, I hope you understand what I meant).
Actualy, for every dataset, I create a different LineLayer object and I give to the LineLayer a different yAxis. Then I set the properties of the yaxis:
...
c->yAxis()->setTitlePos(YAXIS_TITLE_ALIGNAMENT, YAXIS_TITLE_GAP);
c->yAxis()->setTickLength(MAJOR_TICK_LENGHT, MINOR_TICK_LENGHT);
c->yAxis()->setTickDensity(MAXIMUM_TICK_LENGHT);
c->yAxis()->setLinearScale( ... );
...
After I set all the dataset and all the properties, I set the chart:
...
m_ChartViewer->setChart(m);
...
m is always a MultiChart that in one case contains only one XYChart and in another contains one XYChart for all the datasets.
I tried to search on the examples but I cannot find anything that allows me to modify the yAxis as I said so I'm searching for hints or workflow to consider to achieve that.
I told you about the two different "situations" only because I thought that I could use one XYChart for every LineLayer and try to set the plotArea background-color to Chart::Transparent and then working with the position of the XYChart objects inside the MultiChart, but of course it would be a little complex to handle.
Maybe is there an easier way using only one XYChart and working with yAxis Settings that I am not considering?
If you need more information or any clarification please tell me.
Thanks in advance!
Davide
|
Re: Multiple Axis with different ranges on the YAxis |
Posted by Peter Kwan on Aug-04-2020 21:50 |
|
Hi Davide,
For an XYChart, by default, when you add an y-axis, the length of the y-axis will be the same as the height of the plot area.
You can shorten and move the y-axis vertically by adding top and bottom margins to the y-axis. Consider the example below:
https://www.advsofteng.com/doc/cdcpp.htm#multibar.htm
Note that the y-axis does not go all the way to the top of the plot area. There is around 20 pixels of gap which the example use for the legend entries. The code to do this is:
// Reserve 20 pixels at the top of the y-axis for the legend box
c->yAxis()->setTopMargin(20);
See:
https://www.advsofteng.com/doc/cdcpp.htm#Axis.setMargin.htm
By setting the top and bottom margins, you can control the axis length and position.
Regards
Peter Kwan |
Re: Multiple Axis with different ranges on the YAxis |
Posted by Davide on Aug-05-2020 15:33 |
|
Hi Peter,
Thanks a lot for your anwer, you put me in the right direction. I tried the setMargin function and understand how it works and it's perfect for what I need to do. Anyway I still have one problem.
As you can see from the image I still have the yAxis that goes from the bottom to the top of the plot area, even if I set the margin.Is that because I put the title of the axis at the bottom? I would like that the yAxis is equal to the scale that it shows, so that in this case the red yAxis should finish at around 4000, where I drawn the horizontal blue line.
This is my code (actually this is the code to create new yAxis when I have more than one layer):
...
Axis *yaxis = c->addAxis(Chart::Left, DISTANCE_FROM_YAXSIS*(chartNum-1));
yaxis->setLabelStyle(LABEL_STYLE,LABEL_FONT)->setFontAngle(90,false);
yaxis->setColors(static_cast<int>(pChartStruct->canali.at(k)->color),
static_cast<int>(pChartStruct->canali.at(k)->color), static_cast<int>(pChartStruct->canali.at(k)->color), static_cast<int>(pChartStruct->canali.at(k)->color));
layer->setUseYAxis(yaxis); //layer is a LineLayer object
TextBox *title = yaxis->setTitle(pChartStruct->canali.at(k)->name, TITLE_STYLE,FONT_TITLE, static_cast<int>(pChartStruct->canali.at(k)->color));
title->setBackground(0x000000);
title->setFontAngle(90,false);
/*YAXIS_TITLE_ALIGNAMENT = 1 , YAXIS_TITLE_GAP = -15 */
yaxis->setTitlePos(YAXIS_TITLE_ALIGNAMENT,YAXIS_TITLE_GAP);
yaxis->setTickLength(MAJOR_TICK_LENGHT,MINOR_TICK_LENGHT);
yaxis->setTickDensity(MAXIMUM_TICK_LENGHT);
if(pChartStruct->canali.at(k)->Autoscale == 0)
{
yaxis->setLinearScale(pChartStruct->canali.at(k)->minScale, pChartStruct->canali.at(k)->maxScale);
}
else
{
double delta = (pArrayDatas->at(l)->infos.maxValue - pArrayDatas->at(l)->infos.minValue) * PERCENTAGE_AUTOSCALE;
yaxis->setLinearScale(pArrayDatas->at(l)->infos.minValue - delta, pArrayDatas->at(l)->infos.maxValue + delta);
}
yaxis->setRounding(false, false);
int h = c->getHeight();
yaxis->setMargin(0, h/2); //here i set the margin
...
Thanks a lot Peter!
Regards,
Davide
|
Re: Multiple Axis with different ranges on the YAxis |
Posted by Peter Kwan on Aug-05-2020 22:05 |
|
Hi Davide,
The setMargin only shift the axis scale. It does not modify the axis "stem" length. For your case, you may set the axis stem color to Chart::Transparent (the first argument in Axis.setColors), so you do not see it. Then you can use BaseChart.addLine to add a red line in its place to the length you want.
For the axis title, I am not too sure where you want to put the title. In your image, there does not seem to be any space for the axis title along the 4000 to 14000 scale. Is the current position (at the bottom of the plot area) the desired position? If you prefer to put the axis title somewhere else, you can use BaseChart.addText to add text as the axis title, as opposed to using Axis.setTitle. The BaseChart.addText provides greater level of control on where the title appears.
Regards
Peter Kwan |
Re: Multiple Axis with different ranges on the YAxis |
Posted by Davide on Aug-06-2020 14:54 |
|
Thanks a lot Peter! I will try using BaseChart.addLine and BaseChart.addText so that I can put everything where I want. Thanks again!
Regards,
Davide |
Re: Multiple Axis with different ranges on the YAxis |
Posted by Davide on Aug-24-2020 16:29 |
|
Hi Peter, I'm sorry for troubling you again.
With your suggested method I can now modify the lenght and the position of the yAxis, but I'm having a problem with ticks. As you can see in the image I have attached, the values on the yaxis are positioned in some way that I don't know and I don't control. I would like to get at least the minimum and maximum values on the yAxis, but if there is space it would be better to have some other values in between.
I'm using the setLinearScale() function.
I tried with setTickDensity, setting the majorTickSpacing to 10000 and in this way I always have the minimum and maximum values, but I never get values in between. Instead if I set a value like 40, I always have the minimum value and some middle values, but not the maximum. Is there a way to get that specific behaviour?
Thanks in advance,
Regards,
Davide
|
Re: Multiple Axis with different ranges on the YAxis |
Posted by Peter Kwan on Aug-24-2020 22:15 |
|
Hi Davide,
You may try to remove setTickDensity and setRounding. The default should be OK. If you want axis scale that fits the data better without additional margins, you may use setAutoScale:
// No top and bottom extensions, and no zero affinity.
myAxis()->setAutoScale(0, 0, 0);
Regards
Peter Kwan |
Re: Multiple Axis with different ranges on the YAxis |
Posted by Davide on Aug-26-2020 14:58 |
|
That works like a charm but unfortunately I have to use the setLinearScale function because I need to have specific min and max values. Anyway that's not a big problem, I can handle that, but I'm now facing another problem.
As you can see from the image when I set the two axis where the first occupies let's say from top to 80% of the height of the chart and the second from the 80% of the chart to the bottom, I have the minimum value of one axis that overlaps the maximum value of the other one.
I searched on the libraries but I didn't find a function like setTickTextPosition or something similar. Can you suggest some function or some workaround for that?
I suppose the best behavoiur would be that the values are printed "inside" their axis and are not allowed to go outside... Probably the best solution is to use addText function, but I was wondering if there is something already available to use.
Thanks again Peter,
Regards,
Davide
|
Re: Multiple Axis with different ranges on the YAxis |
Posted by Peter Kwan on Aug-26-2020 22:52 |
|
Hi Davide,
If you use setLinearScale to specify the min/max values, you can also specify the positions of the labels by providing the "tick increment" parameter in setLinearScale. This is useful if the labels are regularly spaced.
See:
https://www.advsofteng.com/doc/cdcpp.htm#Axis.setLinearScale.htm
If you leave out the tick increment, ChartDirector will auto-scale and auto-label the axis using the given min/max values as the starting point (instead of using the actual data values as the starting point). ChartDirector will always use nice numbers (like 10, 20, 50, and not 18.170921 as the tick increment). It may "round" the axis scale to ensure it starts and ends at a labelled position.
If the rounding is not allowed, the end-points may have no labels. You can add them back with your own code, but there is a risk they will overlap with the automatic labels generated by ChartDirector. For example, if ChartDirector puts a label at y = 100, but your axis end point is 100.5 and your code adds a label there, it may overlap with the y = 100 label.
If you must configure the axis to use irregular numbers at the end points and with non-regularly spaced labels, you can use Axis.addLabel to add the labels at the positions you want.
By default, the labels are center-aligned with the tick. You can change the alignment, but all labels will have the same alignment. The following is an example:
https://www.advsofteng.com/tutorials/real_time_chart_viewport/real_time_chart_viewport.html
In the above, note that the y-axis labels are not center aligned relative to the tick. They are bottom-aligned instead. The top y label will then move outside the y-axis range. In the above example, we add a top margin for the y-axis and we use that space for the legends.
https://www.advsofteng.com/doc/cdcpp.htm#Axis.setLabelAlignment.htm
https://www.advsofteng.com/doc/cdcpp.htm#Axis.setMargin.htm
However, for your case, because the scale and labels are specified by your code (instead of auto-scaling or zoomable/scrollable by the user), so your code knows where are the labels. In this case, the BaseChart.addText can be a good way to add the labels. With this API, you can control the alignment of individual labels, so you can top-align one label and bottom-align the other label.
Regards
Peter Kwan |
Re: Multiple Axis with different ranges on the YAxis |
Posted by Davide on Sep-01-2020 17:56 |
|
Hi Peter,
Thank you very much for all your help. Everything is working fine with your suggestions and now I get exactly what I needed. After a lot of test I just found out a small problem.
You can see it in the pic. Basically I use the setLinearScale(3000, 16000), but the DoubleArray of the LineLayer also contains values under 3000 and over 16000, and when the position of the Y-Axis of the layer is for example top-aligned with the plot area (like in this case) also values that are not in the Y-Axis range are drawn (you can see it weel for values under 3000).
Is there a way to adjust this problem?
I had a similar problem in the past with the plotarea and I adjust it using setClipping, in fact values over 16000 are not displayed because Y axis is top-aligned.
Thanks again Peter,
Regards,
Davide.
|
Re: Multiple Axis with different ranges on the YAxis |
Posted by Peter Kwan on Sep-02-2020 01:44 |
|
Hi Davide,
Unluckily, there is no built-in API to "clip" the chart to the axis range. ChartDirector can only clip the chart to the plotarea.
I am thinking, it we just clip the line to the axis range, for your case, the line will appear to be broken in the middle of the plot area. It will look like the chart is missing some data points.
Anyway, if clipping the line is what you need, you may consider to adjust your data before passing them to ChartDirector. The simplest method is to just replaces all points < 3000 with 3000. So the out of bound points will appear as a short horizontal line segment at y = 3000. Then add a black line at y = 3000 to cover the horizontal line segment. The line will then appear as broken at y = 3000.
Regards
Peter Kwan |
Re: Multiple Axis with different ranges on the YAxis |
Posted by Davide on Oct-02-2020 14:56 |
|
Thanks a lot for your support Peter. You helped me a lot.
I want to specify what I have done in the end so that it may be helpful for somebody else.
I splitted my original layer into 2 layer:
- one colored layer that containes the values that are in the range
- one transparent layer that contains the values that are not in the range
In this way If I track the cursor on the chart I always have the supposed values even if datas are not visible!
Regards,
Davide |
|