How do I set text within pie chart slices done with QPainter? - c++

I am just starting out qt and have attempted to make a pie chart and I have the following code
QPainter painter(this);
QPen pen;
QRectF size;
pen.setColor(Qt::white);
pen.setWidth(0);
painter.setPen(pen);
if(this->height()>this->width()){
size = QRectF(5,5,this->width()-10, this->width()-10);
}
else{
size = QRectF(5,5,this->height()-10, this->height()-10);
}
double sum = 0.0, startAngle = 0.0;
double angle = 0.0, endAngle, percent;
for(auto i = 0; i < qvValues.size(); i++){
sum += qvValues[i];
}
for(auto i = 0; i < qvValues.size(); i++){
percent = qvValues[i] / sum;
angle = percent *360.0;
endAngle = startAngle + angle;
painter.setBrush(qvColors[i]);
painter.drawPie(size, startAngle*16, angle*16);
painter.drawText(size,"text");
startAngle = endAngle;
}
And am getting the following result
pie chart
My question is, how would one be able to use the drawText() method in the QPainter class to add text within each of the pie slices? Specifically would I need to get the coordinates of the slices pass them into drawText()? If so how would you access its x and y?

Related

Finding zoom center coordinates on a zoomed-in image

I'm attempting to implement mouse cursor centered zoom in/out on an image. For simplicity, assume I only need to zoom in/out in the horizontal axis. I am using Qt with QPainter to draw my scene and QWidget:: mouseWheelEvent override to calculate a zoom factor and a zoom position.
To put it into words before showing the code, the zoom factor, and zoom position define the transformation needed to transform from the original image into the zoomed image (to put it another way, I don't combine matrices or something similar, but calculate the absolute transformation in the paint event), as can be finally seen here:
void MyWidget::paintEvent(QPaintEvent* e)
{
QPainter painter{this};
painter.translate(m_zoomCenterX, 0.);
painter.scale(m_zoomFactor, 1.);
painter.translate(-m_zoomCenterX, 0.);
// content margins are all 0 btw
painter.drawImage(contentsRect(), m_image);
}
In the mouse wheel event, I try to calculate the scaled difference between the zoom center and the new position which I add to the zoom center. Zoom factor is computed simply by multiplying with a constant according to the angle delta. The final if-else branching does some boundaries checking (which is something I'd like to do so the image stays stretched in the view and doesn't move somewhere I don't want to):
void MyWidget::wheelEvent(QWheelEvent* e)
{
const QRect rect = contentsRect();
// m_zoomCenterX is first initialized to 0 in the MyWidget constructor, m_zoomFactor to 1.
const qreal diff = (e->pos().x() - m_zoomCenterX) / m_zoomFactor;
qDebug() << diff;
m_zoomCenterX += diff;
static constexpr const qreal ZOOM_IN_FACTOR = 1.1,
ZOOM_OUT_FACTOR = .9;
m_zoomFactor *= e->angleDelta().y() > 0. ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
if (m_zoomFactor < 1.)
{
m_zoomFactor = 1.;
m_zoomCenterX = 0.;
}
else if (m_zoomFactor > 5.)
{
m_zoomFactor = 5.;
}
if (m_zoomCenterX < 0)
{
m_zoomCenter = 0;
}
else if (m_zoomCenterX >= rect.width())
{
m_zoomCenterX = rect.width() - 1;
}
update();
}
This doesn't seem to work once I try to zoom in/out after let's say zooming in (see picture:
, sorry if it's not much), the zoom center position is likely not correctly computed but I'm not sure why. The qDebug line gives me non-zero differences after consecutive scrollings when the cursor is on a new position, but after the first scrolling it should be zero...
I guess it must be something trivial but I'm quite stuck at the moment.
Well I got it working. I use inverse matrix to compute the new zoom center, but once zoomed in, it will not match the mouse position, so the entire image must be translated additionally by the inverted_position - mouse_position. Here's the final code for any future wanderers. m_extraTranslation is obviously initialised 0 in the constructor.
QMatrix MyWidget::computeScaleMatrix() const noexcept
{
QMatrix scaleMatrix{1., 0., 0., 1., 0., 0.};
scaleMatrix.translate(-m_extraTranslation, 0.);
scaleMatrix.translate(m_zoomCenter.x(), 0.);
scaleMatrix.scale(m_zoomFactor, 1.);
scaleMatrix.translate(-m_zoomCenter.x(), 0.);
return scaleMatrix;
}
void MyWidget::wheelEvent(QWheelEvent* e)
{
const QMatrix invScaleMatrix = computeScaleMatrix().inverted();
const QPointF invPos = invScaleMatrix.map(e->pos());
m_zoomCenter.setX(invPos.x());
static constexpr const qreal ZOOM_IN_FACTOR = 1.1,
ZOOM_OUT_FACTOR = .9;
m_zoomFactor *= e->angleDelta().y() > 0. ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
m_extraTranslation = invPos.x() - e->pos().x();
// here I'm making sure the image corners do not wander off inside the widget, this is necessary to do for zooming out
const QMatrix scaleMatrix = computeScaleMatrix();
const qreal startX = scaleMatrix.map(QPointF{0., 0.}).x(),
endX = scaleMatrix.map(QPointF(contentsRect().width() - 1, 0.)).x();
if (startX > 0.)
{
m_extraTranslation += startX;
}
else if (endX < contentsRect().width() - 1)
{
m_extraTranslation -= contentsRect().width() - endX - 1;
}
if (m_zoomFactor < 1.)
{
m_zoomFactor = 1.;
m_zoomCenter = {0, 0};
m_extraTranslation = 0;
}
else if (m_zoomFactor > 5.)
{
m_zoomFactor = 5.;
}
if (m_zoomCenter.x() < 0)
{
m_zoomCenter.setX(0);
}
else if (m_zoomCenter.x() >= contentsRect().width())
{
m_zoomCenter.setX(contentsRect().width() - 1);
}
update();
e->accept();
}
void MyWidget::paintEvent(QPaintEvent* e)
{
QPainter painter{this};
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.setMatrix(computeScaleMatrix()));
painter.drawImage(contentsRect(), m_image);
}
Extending this for the Y axis should be trivial since it would follow the same logic.

gluPartialDisk in osg/osgEarth

I've been trying to create an openGL gluDisk like object in osgEarth. So far I've attempted to do the following (Edited, this is the correct answer):
void ViewDriver::drawCircZone(double lat, double lon, double innerRadius, double outerRadius, QColor color, double beginAngle, double endAngle){
GeometryFactory g;
osg::ref_ptr<osgEarth::Geometry> outerCircleGeom = g.createArc(osg::Vec3d(lat, lon, 0), convertFromMetersToMercDeg(outerRadius), beginAngle, endAngle);
osg::ref_ptr<osgEarth::Geometry> innerCircleGeom = g.createArc(osg::Vec3d(lat, lon, 0), convertFromMetersToMercDeg(innerRadius), beginAngle, endAngle);
osg::Vec3dArray* outerCircArray = outerCircleGeom->createVec3dArray();
osg::Vec3dArray* innerCircArray = innerCircleGeom->createVec3dArray();
Vec3dVector* diskVec = new Vec3dVector;
for(int i = 0; i < outerCircArray->size() - 1; i++){
diskVec->push_back((*outerCircArray)[i]);
}
//This is important for closing the shape and not giving it a Pac-Man-like mouth
diskVec->push_back((*outerCircArray)[0]);
//This is how you make a "hole", by iterating backwards
for(int i = innerCircArray->size() - 1; i >= 0; i--){
diskVec->push_back((*innerCircArray)[i]);
}
osg::ref_ptr<osgEarth::Symbology::Ring> diskRing = new Ring(diskVec);
diskRing->close();
osg::ref_ptr<Feature> circFeature = new Feature(diskRing, view->getMapViewer()->geoSRS);
Style circStyle;
circStyle.getOrCreate<PolygonSymbol>()->outline() = true;
circStyle.getOrCreate<PolygonSymbol>()->fill()->color() = Color(color.red()/255.0, color.green()/255.0, color.blue()/255.0, 1.0);
circStyle.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN;
circStyle.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_DRAPE;
osg::ref_ptr<FeatureNode> circNode = new FeatureNode(circFeature, circStyle);
circNode->setDynamic(true);
view->getMapNode()->addChild(circNode);
}
What stumped me originally was that I don't have a lot of graphics knowledge. Somewhere I read that when drawing outlines, do it in clockwise direction. When drawing outlines in counter clockwise direction they will "cut-out" or create a "hole" when combined with clockwise-drawn points. I was filling the "hole" outline with the smaller circle's point in a clockwise direction originally when testing that method which was why it didn't work.
void ViewDriver::drawCircZone(double lat, double lon, double innerRadius, double outerRadius, QColor color, double beginAngle, double endAngle){
GeometryFactory g;
osg::ref_ptr<osgEarth::Geometry> outerCircleGeom = g.createArc(osg::Vec3d(lat, lon, 0), convertFromMetersToMercDeg(outerRadius), beginAngle, endAngle);
osg::ref_ptr<osgEarth::Geometry> innerCircleGeom = g.createArc(osg::Vec3d(lat, lon, 0), convertFromMetersToMercDeg(innerRadius), beginAngle, endAngle);
osg::Vec3dArray* outerCircArray = outerCircleGeom->createVec3dArray();
osg::Vec3dArray* innerCircArray = innerCircleGeom->createVec3dArray();
Vec3dVector* diskVec = new Vec3dVector;
for(int i = 0; i < outerCircArray->size() - 1; i++){
diskVec->push_back((*outerCircArray)[i]);
}
//This is important for closing the shape and not giving it a Pac-Man-like mouth
diskVec->push_back((*outerCircArray)[0]);
for(int i = innerCircArray->size() - 1; i >= 0; i--){
diskVec->push_back((*innerCircArray)[i]);
}
osg::ref_ptr<osgEarth::Symbology::Ring> diskRing = new Ring(diskVec);
diskRing->close();
osg::ref_ptr<Feature> circFeature = new Feature(diskRing, view->getMapViewer()->geoSRS);
Style circStyle;
circStyle.getOrCreate<PolygonSymbol>()->outline() = true;
circStyle.getOrCreate<PolygonSymbol>()->fill()->color() = Color(color.red()/255.0, color.green()/255.0, color.blue()/255.0, 1.0);
circStyle.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN;
circStyle.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_DRAPE;
osg::ref_ptr<FeatureNode> circNode = new FeatureNode(circFeature, circStyle);
circNode->setDynamic(true);
view->getMapNode()->addChild(circNode);
}

QtCharts - Background, foreground display

I want to display a QGraphicsRectItem in my QChartView. But the rectangle is displayed behind the lines series in the chart.
I've tried to do a setZValue(10), for example, on my QGraphicsRectItem and setZValue(0) on my QChart but it is still displayed behind.
Obviously I want the informations in the rectangle to be displayed in front of the series of the chart.
Constructor
StatisticsChartView::StatisticsChartView(QWidget *parent, QChart *chart)
: QChartView(chart, parent)
{
/* Create new chart */
_chart = new QChart();
chart = _chart;
_chart->setAnimationOptions(QChart::AllAnimations);
/* Default granularity */
m_iGranularity = DEFAULT_GRANULARITY;
/* Creating ellipse item which will display a circle when the mouse goes over the series */
m_ellipse = new QGraphicsEllipseItem(_chart);
penEllipse.setColor(QColor(0, 0, 0));
penBorder.setWidth(1);
m_ellipse->setPen(penEllipse);
/* Creating text item which will display the x and y value of the mouse position */
m_coordX = new QGraphicsSimpleTextItem(_chart);
m_coordY = new QGraphicsSimpleTextItem(_chart);
penBorder.setColor(QColor(0, 0, 0));
penBorder.setWidth(1);
m_coordX->setPen(penBorder);
m_coordY->setPen(penBorder);
m_rectHovered = new QGraphicsRectItem(_chart);
m_rectHovered->setBrush(QBrush(Qt::yellow));
m_coordHoveredX = new QGraphicsSimpleTextItem(m_rectHovered);
m_coordHoveredY = new QGraphicsSimpleTextItem(m_rectHovered);
penBorder.setColor(QColor(0, 0, 0));
penBorder.setWidth(1);
m_coordHoveredX->setPen(penBorder);
m_coordHoveredY->setPen(penBorder);
m_lineItemX = new QGraphicsLineItem(_chart);
m_lineItemY = new QGraphicsLineItem(_chart);
penLine.setColor(QColor(0, 0, 0));
penLine.setStyle(Qt::DotLine);
m_lineItemX->setPen(penLine);
m_lineItemY->setPen(penLine);
/* Enable zooming in the rectangle drawn with the left click of the mouse, zoom out with right click */
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
mousePressed = 0;
seriesHovered = false;
setMouseTracking(true);
_chart->setAcceptHoverEvents(true);
_chart->setZValue(50);
m_ellipse->setZValue(10); //so it is displayed over the series
m_coordHoveredX->setZValue(20); //so it is displayed over others
setRenderHint(QPainter::Antialiasing);
setChart(_chart);
}
Creation of series
void StatisticsChartView::drawCurve(bool bDrawScale)
{
int w = WIDTH;
int h = HEIGHT;
/* Creating series */
QLineSeries *lineFalse = new QLineSeries();
QLineSeries *lineAutomatic = new QLineSeries();
QLineSeries *lineOk = new QLineSeries();
QLineSeries *lineFalsePositive = new QLineSeries();
QLineSeries *lineManualTreatement = new QLineSeries();
QLineSeries *lineFalseNegative = new QLineSeries();
QList<QLineSeries*> lineSeriesList;
lineSeriesList << lineFalse << lineAutomatic << lineOk << lineFalsePositive << lineManualTreatement << lineFalseNegative;
QList<QString> nameSeriesList;
nameSeriesList << "False" << "Automatic" << "Ok" << "FalsePositive" << "ManualTreatement" << "FalseNegative";
QList<QVector<GraphPoint>> graphPointList;
graphPointList << gpFalse << gpDetected << gpOk << gpDetectedNotOk << gpManualTreatement << gpFalseNegative;
double graphX = 100.0 / (m_iGranularity);
bool pointsVisible = true;
for (int n = 0; n < lineSeriesList.count(); ++n)
{
/* Adding points to line series */
for (int i = 0; i < m_iGranularity + 1; ++i)
{
lineSeriesList[n]->append(i * graphX, (float)(graphPointList[n][i]).fValue * 100);
lineSeriesList[n]->setPointsVisible(pointsVisible);
lineSeriesList[n]->setName(nameSeriesList[n]);
}
}
_chart->legend()->setVisible(true);
_chart->legend()->setAlignment(Qt::AlignBottom);
/* Setting axis X and Y */
axisX = new QValueAxis();
axisY = new QValueAxis();
axisX->setRange(0, 100);
axisY->setRange(0, 100);
/* Adding line series to the chart and attaching them to the same axis */
for (int j = 0; j < lineSeriesList.count(); ++j)
{
_chart->addSeries(lineSeriesList[j]);
_chart->setAxisX(axisX, lineSeriesList[j]);
_chart->setAxisY(axisY, lineSeriesList[j]);
connect(lineSeriesList[j], SIGNAL(hovered(QPointF, bool)), this, SLOT(onSeriesHovered(QPointF, bool)));
}
_chart->resize(w, h);
return;
}
Drawing rectangle on chart
void StatisticsChartView::onSeriesHovered(QPointF point, bool state)
{
seriesHovered = state;
/* Updating the size of the rectangle */
if (mousePressed == 0 && seriesHovered == true)
{
/* x and y position on the graph */
qreal x = _chart->mapToPosition(point).x();
qreal y = _chart->mapToPosition(point).y();
/* x and y value on the graph from 0 to 100 for ou graph */
qreal xVal = point.x();
qreal yVal = point.y();
qreal maxX = axisX->max();
qreal minX = axisX->min();
qreal maxY = axisY->max();
qreal minY = axisY->min();
/* We don't want to display value outside of the axis range */
if (xVal <= maxX && xVal >= minX && yVal <= maxY && yVal >= minY)
{
m_coordHoveredX->setVisible(true);
m_coordHoveredY->setVisible(true);
m_rectHovered->setVisible(true);
m_ellipse->setVisible(true);
m_rectHovered->setRect(x - 31, y - 31, 30, 30);
qreal rectX = m_rectHovered->rect().x();
qreal rectY = m_rectHovered->rect().y();
qreal rectW = m_rectHovered->rect().width();
qreal rectH = m_rectHovered->rect().height();
/* We're setting the labels and nicely adjusting to chart axis labels (adjusting so the dot lines are centered on the label) */
m_coordHoveredX->setPos(rectX + rectW / 4 - 3, rectY + 1);
m_coordHoveredY->setPos(rectX + rectW / 4 - 3, rectY + rectH / 2 + 1);
/* Setting value to displayed with four digit max, float, 1 decimal */
m_coordHoveredX->setText(QString("%1").arg(xVal, 4, 'f', 1, '0'));
m_coordHoveredY->setText(QString("%1").arg(yVal, 4, 'f', 1, '0'));
m_ellipse->setRect(QRectF::QRectF(x, y, 10, 10));
m_ellipse->setPos(x, y);
m_ellipse->setBrush(QBrush(Qt::red));
}
else
{
/* We're not displaying information if out of the chart */
m_coordHoveredX->setVisible(false);
m_coordHoveredY->setVisible(false);
m_rectHovered->setVisible(false);
m_ellipse->setVisible(false);
}
}
else
{
/* We're not displaying information if series aren't hovered */
m_coordHoveredX->setVisible(false);
m_coordHoveredY->setVisible(false);
m_rectHovered->setVisible(false);
m_ellipse->setVisible(false);
}
}
You should try using a series especially for your rectangle.
Setting it as the last series, on your chart, to be above the other lines. And adding a legend or a callout for the text.

GLUT timer function

I am trying to get my tokens on a board game to fall slowly. Right now, they fall, but they fall so fast. How could I implement the timer function in my code? Right now I do a loop, that updates the y coordinate of glTranslate. But it is still too fast! the top y is the y coordinate where I press on the screen, and the bottomy is the coordinates of the lowest open spot for a token.
col =0;
double bottomy = 0;
int row = 0;
circlex = (double)x / width ;
circley = (double)y / height ;
row = board.getRow(col) + 1;
bottomy = 500 - (25*row);
for( double topy = y ; topy <= bottomy; topy += 2 ){
glTranslatef(circlex, circley, 0.0f);
circley += .0000000000000000001;
display();
}
r = board.makeMove(col);
You can use glutTimerFunc to execute a function at a regular time period. This has the signature
void glutTimerFunc(unsigned int msecs,
void (*func)(int value),
value);
For example if your drawing function was
void UpdateTokens(int time);
Then you could call an update every 0.5 seconds with the following call (where current_time was the current simulation time)
glutTimerFunc(500, UpdateTokens, current_time);
For more precise timing, I would recommend using <chrono> instead, and performing your timing using things like std::chrono::duration with a std::chrono::steady_clock.
The actual problem here is how glut works. Basically, the user only gets a image presented at the end of the main loop. As long as you do not return from the mouse function, nothing is presented on screen. You can solve the problem by transferring the work to the display function and distribute the translation across multiple frames:
global variables:
double circlex = 0, circley = 0, bottomy = 0;
bool isfalling = false;
int topy = 0;
mouse_func:
if (isfalling == false) //Prevents the user from clicking during an animation
{
circlex = (double)x / width ;
circley = (double)y / height ;
int row = board.getRow(col) + 1;
bottomy = 500 - (25*row);
topy = y;
isfalling = true;
}
display_func:
if (isfalling)
{
circley += .0000000000000000001;
topy += 2;
if (topy >= bottomy)
isfalling = false;
}
glTranslatef(circlex, circley, 0.0f);
display();

vtk 6.x, Qt: 3D (line, surface, scatter) plotting

I am working on a Qt (4.7.4) project and need to plot data in 2D and 3D coordinate systems. I've been looking into vtk 6.1 because it seems very powerful overall and I will also need to visualize image data at a later point. I basically got 2D plots working but am stuck plotting data in 3D.
Here's what I tried: I'm using the following piece of code that I took from one of vtk's tests ( Charts / Core / Testing / Cxx / TestSurfacePlot.cxx ). The only thing I added is the QVTKWidget that I use in my GUI and its interactor:
QVTKWidget vtkWidget;
vtkNew<vtkChartXYZ> chart;
vtkNew<vtkPlotSurface> plot;
vtkNew<vtkContextView> view;
view->GetRenderWindow()->SetSize(400, 300);
vtkWidget.SetRenderWindow(view->GetRenderWindow());
view->GetScene()->AddItem(chart.GetPointer());
chart->SetGeometry(vtkRectf(75.0, 20.0, 250, 260));
// Create a surface
vtkNew<vtkTable> table;
float numPoints = 70;
float inc = 9.424778 / (numPoints - 1);
for (float i = 0; i < numPoints; ++i)
{
vtkNew<vtkFloatArray> arr;
table->AddColumn(arr.GetPointer());
}
table->SetNumberOfRows(numPoints);
for (float i = 0; i < numPoints; ++i)
{
float x = i * inc;
for (float j = 0; j < numPoints; ++j)
{
float y = j * inc;
table->SetValue(i, j, sin(sqrt(x*x + y*y)));
}
}
// Set up the surface plot we wish to visualize and add it to the chart.
plot->SetXRange(0, 9.424778);
plot->SetYRange(0, 9.424778);
plot->SetInputData(table.GetPointer());
chart->AddPlot(plot.GetPointer());
view->GetRenderWindow()->SetMultiSamples(0);
view->SetInteractor(vtkWidget.GetInteractor());
view->GetInteractor()->Initialize();
view->GetRenderWindow()->Render();
Now, this produces a plot but I can neither interact with it not does it look 3D. I would like to do some basic stuff like zoom, pan, or rotate about a pivot. A few questions that come to my mind about this are:
Is it correct to assign the QVTKWidget interactor to the view in the third line from the bottom?
In the test, a vtkChartXYZ is added to the vtkContextView. According to the documentation, the vtkContextView is used to display a 2D scene but here is used with a 3D chart (XYZ). How does this fit together?
The following piece of code worked for me. No need to explicitly assign an interactor because that's already been taken care of by QVTKWidget.
QVTKWidget vtkWidget;
vtkSmartPointer<vtkContextView> view = vtkSmartPointer<vtkContextView>::New();
vtkSmartPointer<vtkChartXYZ> chart = vtkSmartPointer<vtkChartXYZ>::New();
// Create a surface
vtkSmartPointer<vtkTable> table = vtkSmartPointer<vtkTable>::New();
float numPoints = 70;
float inc = 9.424778 / (numPoints - 1);
for (float i = 0; i < numPoints; ++i)
{
vtkSmartPointer<vtkFloatArray> arr = vtkSmartPointer<vtkFloatArray>::New();
table->AddColumn(arr.GetPointer());
}
table->SetNumberOfRows(numPoints);
for (float i = 0; i < numPoints; ++i)
{
float x = i * inc;
for (float j = 0; j < numPoints; ++j)
{
float y = j * inc;
table->SetValue(i, j, sin(sqrt(x*x + y*y)));
}
}
view->SetRenderWindow(vtkWidget.GetRenderWindow());
chart->SetGeometry(vtkRectf(200.0, 200.0, 300, 300));
view->GetScene()->AddItem(chart.GetPointer());
vtkSmartPointer<vtkPlotSurface> plot = vtkSmartPointer<vtkPlotSurface>::New();
// Set up the surface plot we wish to visualize and add it to the chart.
plot->SetXRange(0, 10.0);
plot->SetYRange(0, 10.0);
plot->SetInputData(table.GetPointer());
chart->AddPlot(plot.GetPointer());
view->GetRenderWindow()->SetMultiSamples(0);
view->GetRenderWindow()->Render();
You might want read the detailed description in vtkRenderViewBase
QVTKWidget *widget = new QVTKWidget;
vtkContextView *view = vtkContextView::New();
view->SetInteractor(widget->GetInteractor());
widget->SetRenderWindow(view->GetRenderWindow());