How to set labels for items of a KDChart pie diagram? - c++

Is there any way to set text labels for each item of a pie diagram, created using KDChart lib in Qt?
To be more specific, I'm not using the Model/View architecture in this particular case. I create it though KDChart::Widget and merely fill the chart using Widget::setDataCell().
Seemingly there are several ways to set text labels for axis, but I haven't encountered something similar for a pie diagram. Anyway it's not the thing I need. I want to set labels for certain points rather than for its axis. In apply to a pie diagram it would be something like titled sectors.
I thought that maybe with using KDChart::Legend with filled values I can achieve required behavior, but it haven't worked.
Here is a code sample, maybe it will help somewhat. But keep in mind that it's changed (cleared of cluttering lines) and I haven't tested its correctness:
KDChart::Widget* newChart = new KDChart::Widget;
newChart->setType( KDChart::Widget::Pie );
int curColNo = 0; // it's not a size_t 'coz setDataCell requires an int
for( QVector::const_iterator curValueIt = response.begin(); curValueIt != response.end(); ++curValueIt )
{
newChart->setDataCell( 0, curColNo, *curValueIt );
newChart->diagram()->setBrush( curColNo++, QBrush( m_responsesColors[curValueIt] ) );
m_legend->addDiagram( newChart->diagram() );
}
m_mainLayout.addWidget( newChart, m_curLayoutRowNo, m_curLayoutColNo );
One more thing - I tried to fill it with inconsistent column numbers (0,2,5,9,etc) and pie chart was drawn incorrectly - some sectors overlapped others. In other types of charts (bar chart, for example) all data was visualized correctly.
Do you have any ideas about item labels?
P.S. I've figured out what's wrong with filling Pie chart's columns with skipping some of them. If you fill columns inconsistently (skipping some of them), then just set those skipped columns' values to zero explicitly. It will fix problems with wrong pie chart's visualizing.
Probably KDChart should figure out about skipped columns by itself and set it to null automatically, but it won't. So do it yourself.
Hope, this will help someone.

I have found a solution by my own. Considering a small amount of info on KDChart library, I'm posting it here in hope it will help someone with similar problem.
The solution lies quite deeply in the KDChart hierarchy. You need to manually turn on labels display. I've created a separated function for it.
void setValuesVisible( KDChart::AbstractDiagram* diagram, bool visible ) throw()
{
const QFont font( QFont( "Comic", 10 ) ); // the font for all labels
const int colCount = diagram->model()->columnCount();
for ( int iColumn = 0; iColumn < colCount; ++iColumn )
{
//QBrush brush( diagram->brush( iColumn ) ); // here you can get a color of the specified column
KDChart::DataValueAttributes a( diagram->dataValueAttributes( iColumn ) );
KDChart::TextAttributes ta( a.textAttributes() );
ta.setRotation( 0 );
ta.setFont( font );
ta.setAutoRotate( true );
//ta.setPen( QPen( brush.color() ) ); // here you can change a color of the current label's text
ta.setVisible( visible ); // this line turns on labels display
a.setTextAttributes( ta );
a.setVisible( true );
diagram->setDataValueAttributes( iColumn, a);
}
diagram->update();
}
Keep in mind, that there is shorter solution - just set TextAttributes of the "global" DataValueAttributes (there is a method for it in the KDChart::AbstractDiagram class - AbstractDiagram::dataValueAttributes() without any params) if you don't need unique text parameters for each label.

Related

qwtplot: no plot update on replot

I use my plot classes based on qwtplot, code can be viewed here:
https://github.com/pospiech/code/tree/master/libdev/plot/plottools/trunk/src
This code is used in another application, here I create qwtplot classes (QMatrixPlot) and add data and call replot
plot2DAmplitude->setMatrixData(QVector<double>::fromStdVector(dataAmplitude),
xaxis.size(),
QwtInterval(xaxis.front(), xaxis.back()),
QwtInterval(yaxis.front(), yaxis.back()));
plot2DAmplitude->replot();
setMatrixData is the following code:
void setMatrixData(const QVector< double > &values, int numColumns, QwtInterval xAxisRange, QwtInterval yAxisRange)
{
m_MatrixRasterData->setInterval( Qt::XAxis, xAxisRange );
m_MatrixRasterData->setInterval( Qt::YAxis, yAxisRange );
double minValue = *std::min_element( std::begin(values), std::end(values) );
double maxValue = *std::max_element( std::begin(values), std::end(values) );
m_MatrixRasterData->setInterval( Qt::ZAxis, QwtInterval(minValue, maxValue) );
m_MatrixRasterData->setValueMatrix (values, numColumns);
d_spectrogram->setData( m_MatrixRasterData );
const QwtInterval zInterval = d_spectrogram->data()->interval( Qt::ZAxis );
setAxisScale( QwtPlot::yRight, zInterval.minValue(), zInterval.maxValue() );
QwtScaleWidget *axis = axisWidget( QwtPlot::yRight );
axis->setColorMap( zInterval, QColorMap::map(d_mapType) );
}
This works - once.
The second call with different data does nothing, although the same code is called. Only If I zoom in and out the data gets an update.
Any idea? Qwt is version 6.1.3. Once I finish a minimal example I will update this post.
QwtPlotRasterItem caches the rendered image. In your code you are changing the data behind the back of the item, so the item is not aware of the cache being invalid.
With QwtPlotRasterItem::invalidateCache() you can solve problems like this one.

Draw a pie chart in MFC TeeChart

My English is no very good, so please forgive me. I have added my data successfully in a pie chart, but the pie chart doesn't show with only data shown in the control.
The properties of the control seem like have been configured appropriately. I don't know where is the problem since I have spent whole my night on it.
BOOL CStatInfPieDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
char temp1[100];
char temp2[100];
CString str;
// TODO: Add extra initialization here
CSeries series = (CSeries)statInfPie.Series(0);
int size = stationInfList.size();
series.put_ColorEachPoint(true);
srand(time(NULL));
for (int i = 0; i < size; i++) {
sprintf(temp1, "%s/%d ", iptostr(stationInfList[i].netaddrA), toCidr(stationInfList[i].netmaskA));
sprintf(temp2, "%s/%d", iptostr(stationInfList[i].netaddrB), toCidr(stationInfList[i].netmaskB));
strcat(temp1, temp2);
str = CString(temp1);
series.Add(stationInfList[i].bcountAToB + stationInfList[i].bcountBToA, str, RGB(rand() % 255, rand() % 255, rand() % 255));
memcpy(temp1, "\0", sizeof(temp1));
memcpy(temp2, "\0", sizeof(temp2));
}
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
The code sample above initializes my dialog which contains the TeeChart control. I add data through function Add(). Array temp1 and array temp2 is my description inf. After I compile and run my program, the result shows in the picture blow.
TeeChart tries to make space for the long labels and the Legend, automatically reducing the diameter of the Pie. In this case the result is extreme; the Pie is left with no radius.
That can be resolved in one of several ways:
The latest version of TeeChart (AX) includes a property called InsideSlice for PieMarks.
ie.TChart1.Series(0).asPie.PieMarks.InsideSlice = True
For older versions of TeeChart, where this property is not available you can manually set the Arrowlength (the connector to the Mark) to a negative value:
ie. TChart1.Series(0).Marks.ArrowLength = -20
The Series Marks can be setup to render multiline, taking up less width:
ie. TChart1.Series(0).Marks.MultiLine = True
If the Legend is in the Chart with very long labels that can also be counter productive to chart readability. The Legend can be set to Visible false or told to not resize the Chart Series (the Pie) to fit.
ie. TChart1.Legend.ResizeChart = False
or can be positioned below the Pie
ie. TChart1.Legend.Alignment = laBottom
A thought to design will be required here. Showing long Point Value labels (the Series Marks) and repeating some of the information in the Legend is taking up a great deal of the working space where the Chart could be shown. If the Legend were to be placed below the Chart and the Panel were sized accordingly and perhaps were to use information that doesn't duplicate the Series Marks' information (using a different Legend Text Style) plus setting up the Series Marks with Multiline, with a shorter Arrowlength, then the overall result should be very readable.

QwtPlot automatically resizes during AutoScale

I'm using libqwt-6.1.1 to display variable data in a QwtPlot element (as histogram) using auto scale for y-axis. Depending on the actual data and the number of tics, the autoscale-run considers reasonable, the minimum height of the whole QwtPlot element often increases. This kills my whole layout design and frequently makes the window larger than the screen.
Now 2 questions:
1) How can I prevent the QwtPlot from being automatically enlarged?
2) How does the minimum size calculation actually work? (I got lost somewhere in QwtPlotLayout::activate() while trying to follow the calculation steps.)
Finally, the solution was simpler than expected ...
Overloading QwtPlot::sizeHint() and QwtPlot::minimumSizeHint() (which are virtual, fortunately) making both return an small constant value did the trick, i.e.
QSize myPlot::sizeHint() const { return QSize(100, 100); }
QSize myPlot::minimumSizeHint() const { return QSize(100, 100); }
The factor that has an effect on the height and depends on auto scaling is the number of major ticks of the vertical scales ( tickcount * font height ).
The calculation of the ticks depends on the given stepSize. When the stepSize is set to 0 ( = default setting ) it is calculated from the maxMajor setting ( default: 8 ), what is an upper limit for the number of the major ticks.
So f.e. you could use a stepSize to 0 and set a reasonable maxMajor setting for your use case. Then use a QLayout setup, where the plot gets not always shrinked to its minimum height.

raphael pie chart always blue when there is only 1 value (how to set color of a pie with one slice)

I am having an issue with raphael pie charts. The data I am using is dynamic, and in some instances, only 1 value is returned, meaning the whole chart is filled, as it is the ONLY slice. The problem is that when there is only 1 value, it ignores my color designation.
For example: Below is the creation of a raphael pie chart with 2 values, and each slice has the proper color designated in the "colors" section:
var r = Raphael("holder");
r.piechart(160, 136, 120, [100,200],{colors: ["#000","#cecece"]});
This works fine, and I get two properly sized slices, one black, and one grey.
However the example below creates one full pie, ALWAYS filled with blue, regardless of my color setting.
var r = Raphael("holder");
r.piechart(160, 136, 120, [100],{colors: ["#000"]});
In this situation, I really need that full pie to be black, as it is set in "colors"
Am I doing something wrong, or is this a bug?
INMO its a bug cause when the pie got only one slice its color is hard coded...
Here is how I solved it (all I did is use the colors arg if it exist...)
in g.pie.js after line 47 add this
var my_color = chartinst.colors[0];
if(opts.colors !== undefined){
my_color = opts.colors[0];
}
then in the following line (line 48 in the original js file)
series.push(paper.circle(cx, cy, r).attr({ fill: chartinst.colors[0]....
replace the chartinst.colors[0] with my_color
that's it
if (len == 1) {
var my_color = chartinst.colors[0];
if(opts.colors !== undefined){
my_color = opts.colors[0];
}
series.push(paper.circle(cx, cy, r).attr({ fill: my_color, ....
You've probably figured this out on your own since this question is already a day old... but you can "trick" Raphael into rendering a black unit by special-casing datasets of one to add an infinitesimal second value. So, given an array data with your data points...
if ( data.length == 1 )
data.push( 0.000001 );
canvas.piechart(250, 250, 120, data, {colors: ["#000", "#CECECE", "#F88" /*, ... */ ] });
The tiny sliver will still be rendered as a single-pixel line in the 180 degree position, but you could probably fudge that by playing with your color palette.
Yes, it's a trick. I don't believe gRaphael's behavior is buggy so much as poorly implemented (single-element datasets are obviously special cased since they produce a circle instead of a path as they would in all other cases).
Easy way for me without edit g.pie.js
var r = Raphael('st_diagram');
r.piechart(140, 140, 137, 100, 0.0001],{
colors:['#9ae013','#9ae013'],
strokewidth: 0
});

Is there a better way to load in a big animation?

Maybe not really big, but a hundred frames or something. Is the only way to load it in by making an array and loading each image individually?
load_image() is a function I made which loads the images and converts their BPP.
expl[0] = load_image( "explode1.gif" );
expl[1] = load_image( "explode2.gif" );
expl[2] = load_image( "explode3.gif" );
expl[3] = load_image( "explode4.gif" );
...
expl[99] = load_image( "explode100.gif" );
Seems like their should be a better way.. at least I hope.
A common technique is spritesheets, in which a single, large image is divided into a grid of cells, with each cell containing one frame of an animation. Often, all animation frames for any game entity are placed on a single, sometimes huge, sprite sheet.
maybe simplify your loading with a utility function that builds a filename for each iteration of a loop:
LoadAnimation(char* isFileBase, int numFrames)
{
char szFileName[255];
for(int i = 0; i < numFrames; i++)
{
// append the frame number and .gif to the file base to get the filename
sprintf(szFileName, "%s%d.gif", isFileBase, i);
expl[i] = load_image(szFileName);
}
}
Instead of loading as a grid, stack all the frames in one vertical strip (same image). Then you only need to know how many rows per frame and you can set a pointer to the frame row offset. You end up still having contiguous scan lines that can be displayed directly or trivially chewed off into separate images.