Skip to content

Commit 1601c7d

Browse files
committed
add mtext() method to Plot, extend text() with [float angle]
1 parent c003791 commit 1601c7d

11 files changed

Lines changed: 530 additions & 34 deletions

QtSLiM/QtSLiMGraphView_CustomPlot.cpp

Lines changed: 183 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ void QtSLiMGraphView_CustomPlot::freeData(void)
8888
for (std::vector<double> *sizebuffer : size_)
8989
delete sizebuffer;
9090

91+
for (std::vector<double> *anglebuffer : angle_)
92+
delete anglebuffer;
93+
9194
plot_type_.clear();
9295
x1data_.clear();
9396
y1data_.clear();
@@ -101,6 +104,7 @@ void QtSLiMGraphView_CustomPlot::freeData(void)
101104
alpha_.clear();
102105
line_width_.clear();
103106
size_.clear();
107+
angle_.clear();
104108
xadj_.clear();
105109
yadj_.clear();
106110
image_.clear();
@@ -431,6 +435,7 @@ void QtSLiMGraphView_CustomPlot::addABLineData(double *a_values, double *b_value
431435
alpha_.push_back(alpha);
432436
line_width_.push_back(lwd);
433437
size_.push_back(nullptr); // unused for abline
438+
angle_.push_back(nullptr); // unused for abline
434439
xadj_.push_back(-1); // unused for abline
435440
yadj_.push_back(-1); // unused for abline
436441
image_.push_back(QImage()); // unused for abline
@@ -456,6 +461,7 @@ void QtSLiMGraphView_CustomPlot::addLineData(double *x_values, double *y_values,
456461
alpha_.push_back(alpha);
457462
line_width_.push_back(lwd);
458463
size_.push_back(nullptr); // unused for lines
464+
angle_.push_back(nullptr); // unused for lines
459465
xadj_.push_back(-1); // unused for lines
460466
yadj_.push_back(-1); // unused for lines
461467
image_.push_back(QImage()); // unused for lines
@@ -481,6 +487,7 @@ void QtSLiMGraphView_CustomPlot::addRectData(double *x1_values, double *y1_value
481487
alpha_.push_back(alpha);
482488
line_width_.push_back(lwd);
483489
size_.push_back(nullptr); // unused for rects
490+
angle_.push_back(nullptr); // unused for rects
484491
xadj_.push_back(-1); // unused for rects
485492
yadj_.push_back(-1); // unused for rects
486493
image_.push_back(QImage()); // unused for rects
@@ -506,6 +513,7 @@ void QtSLiMGraphView_CustomPlot::addSegmentData(double *x1_values, double *y1_va
506513
alpha_.push_back(alpha);
507514
line_width_.push_back(lwd);
508515
size_.push_back(nullptr); // unused for segments
516+
angle_.push_back(nullptr); // unused for segments
509517
xadj_.push_back(-1); // unused for segments
510518
yadj_.push_back(-1); // unused for segments
511519
image_.push_back(QImage()); // unused for segments
@@ -514,6 +522,32 @@ void QtSLiMGraphView_CustomPlot::addSegmentData(double *x1_values, double *y1_va
514522
update();
515523
}
516524

525+
void QtSLiMGraphView_CustomPlot::addMarginTextData(double *x_values, double *y_values, std::vector<QString> *labels, int data_count,
526+
std::vector<QColor> *color, std::vector<double> *alpha,
527+
std::vector<double> *size, double *adj, std::vector<double> *angle)
528+
{
529+
plot_type_.push_back(QtSLiM_CustomPlotType::kMarginText);
530+
x1data_.push_back(x_values);
531+
y1data_.push_back(y_values);
532+
x2data_.push_back(nullptr); // unused for text
533+
y2data_.push_back(nullptr); // unused for text
534+
labels_.push_back(labels);
535+
data_count_.push_back(data_count);
536+
symbol_.push_back(nullptr); // unused for text
537+
color_.push_back(color);
538+
border_.push_back(nullptr); // unused for text
539+
alpha_.push_back(alpha);
540+
line_width_.push_back(nullptr); // unused for text
541+
size_.push_back(size);
542+
angle_.push_back(angle); // unused for text
543+
xadj_.push_back(adj[0]);
544+
yadj_.push_back(adj[1]);
545+
image_.push_back(QImage()); // unused for text
546+
547+
rescaleAxesForDataRange();
548+
update();
549+
}
550+
517551
void QtSLiMGraphView_CustomPlot::addPointData(double *x_values, double *y_values, int data_count,
518552
std::vector<int> *symbol, std::vector<QColor> *color, std::vector<QColor> *border,
519553
std::vector<double> *alpha, std::vector<double> *lwd, std::vector<double> *size)
@@ -531,6 +565,7 @@ void QtSLiMGraphView_CustomPlot::addPointData(double *x_values, double *y_values
531565
alpha_.push_back(alpha);
532566
line_width_.push_back(lwd);
533567
size_.push_back(size);
568+
angle_.push_back(nullptr); // unused for points
534569
xadj_.push_back(-1); // unused for points
535570
yadj_.push_back(-1); // unused for points
536571
image_.push_back(QImage()); // unused for points
@@ -541,7 +576,7 @@ void QtSLiMGraphView_CustomPlot::addPointData(double *x_values, double *y_values
541576

542577
void QtSLiMGraphView_CustomPlot::addTextData(double *x_values, double *y_values, std::vector<QString> *labels, int data_count,
543578
std::vector<QColor> *color, std::vector<double> *alpha,
544-
std::vector<double> *size, double *adj)
579+
std::vector<double> *size, double *adj, std::vector<double> *angle)
545580
{
546581
plot_type_.push_back(QtSLiM_CustomPlotType::kText);
547582
x1data_.push_back(x_values);
@@ -556,6 +591,7 @@ void QtSLiMGraphView_CustomPlot::addTextData(double *x_values, double *y_values,
556591
alpha_.push_back(alpha);
557592
line_width_.push_back(nullptr); // unused for text
558593
size_.push_back(size);
594+
angle_.push_back(angle); // unused for text
559595
xadj_.push_back(adj[0]);
560596
yadj_.push_back(adj[1]);
561597
image_.push_back(QImage()); // unused for text
@@ -580,6 +616,7 @@ void QtSLiMGraphView_CustomPlot::addImageData(double *x_values, double *y_values
580616
alpha_.push_back(alpha);
581617
line_width_.push_back(nullptr); // unused for image
582618
size_.push_back(nullptr); // unused for image
619+
angle_.push_back(nullptr); // unused for image
583620
xadj_.push_back(-1); // unused for image
584621
yadj_.push_back(-1); // unused for image
585622
image_.push_back(image);
@@ -655,6 +692,9 @@ void QtSLiMGraphView_CustomPlot::drawGraph(QPainter &painter, QRect interiorRect
655692
case QtSLiM_CustomPlotType::kRects:
656693
drawRects(painter, interiorRect, i);
657694
break;
695+
case QtSLiM_CustomPlotType::kMarginText:
696+
drawMarginText(painter, interiorRect, i);
697+
break;
658698
case QtSLiM_CustomPlotType::kPoints:
659699
drawPoints(painter, interiorRect, i);
660700
break;
@@ -877,6 +917,117 @@ void QtSLiMGraphView_CustomPlot::drawLines(QPainter &painter, QRect interiorRect
877917
}
878918
}
879919

920+
void QtSLiMGraphView_CustomPlot::drawMarginText(QPainter &painter, QRect interiorRect, int dataIndex)
921+
{
922+
double *xdata = x1data_[dataIndex];
923+
double *ydata = y1data_[dataIndex];
924+
std::vector<QString> &labels = *labels_[dataIndex];
925+
int pointCount = data_count_[dataIndex];
926+
std::vector<QColor> &textColors = *color_[dataIndex];
927+
std::vector<double> &textAlphas = *alpha_[dataIndex];
928+
std::vector<double> &textAngles = *angle_[dataIndex];
929+
std::vector<double> &pointSizes = *size_[dataIndex];
930+
double xadj = xadj_[dataIndex];
931+
double yadj = yadj_[dataIndex];
932+
933+
// move clipping area outward to encompass our entire parent widget
934+
QRect bounds = rect();
935+
936+
painter.save();
937+
painter.setClipRect(bounds, Qt::ReplaceClip);
938+
939+
//QtSLiMFrameRect(interiorRect, Qt::green, painter);
940+
941+
// set up to get the font and font info
942+
double lastPointSize = -1;
943+
QFont labelFont;
944+
double capHeight = 0;
945+
946+
for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex)
947+
{
948+
double user_x = xdata[pointIndex];
949+
double user_y = ydata[pointIndex];
950+
951+
if (std::isfinite(user_x) && std::isfinite(user_y))
952+
{
953+
// for mtext(), coordinates inside the plot area are in [0,1]
954+
QString &labelText = labels[pointIndex];
955+
double x = user_x * interiorRect.width() + interiorRect.x();
956+
double y = user_y * interiorRect.height() + interiorRect.y();
957+
958+
//qDebug() << "labelText ==" << labelText << ", user_x ==" << user_x << ", user_y ==" << user_y << ", x ==" << x << ", y ==" << y;
959+
960+
// translate the painter so (x, y) is at (0, 0)
961+
painter.save();
962+
painter.translate(x, y);
963+
x = 0;
964+
y = 0;
965+
966+
double pointSize = pointSizes[pointIndex % pointSizes.size()];
967+
968+
if (pointSize != lastPointSize)
969+
{
970+
labelFont = QtSLiMGraphView::labelFontOfPointSize(pointSize);
971+
capHeight = QFontMetricsF(labelFont).capHeight();
972+
painter.setFont(labelFont);
973+
974+
lastPointSize = pointSize;
975+
}
976+
977+
QColor textColor = textColors[pointIndex % textColors.size()];
978+
double alpha = textAlphas[pointIndex % textAlphas.size()];
979+
980+
if (alpha != 1.0)
981+
textColor.setAlphaF(alpha);
982+
983+
painter.setPen(textColor);
984+
985+
QRect labelBoundingRect = painter.boundingRect(QRect(), Qt::TextDontClip | Qt::TextSingleLine, labelText);
986+
987+
// labelBoundingRect is useful for its width, which seems to be calculated correctly; its height, however, is oddly large, and
988+
// is not useful, so we use the capHeight from the font metrics instead. This means that vertically centered (yadj == 0.5) is
989+
// the midpoint between the baseline and the capHeight, which I think is probably the best behavior.
990+
double labelWidth = labelBoundingRect.width();
991+
double labelHeight = capHeight;
992+
double labelX = x - SLIM_SCREEN_ROUND(labelWidth * xadj);
993+
double labelY = y - SLIM_SCREEN_ROUND(labelHeight * yadj);
994+
995+
//qDebug() << " labelBoundingRect ==" << labelBoundingRect << ", labelWidth ==" << labelWidth << ", labelHeight ==" << labelHeight << ", capHeight ==" << capHeight;
996+
//qDebug() << " labelX ==" << labelX << ", labelY ==" << labelY;
997+
998+
// rotate the coordinate system around (x, y); for example, -10.0 is 10 degrees clockwise
999+
double textAngle = textAngles[pointIndex];
1000+
1001+
if (textAngle != 0.0)
1002+
painter.rotate(-textAngles[pointIndex]);
1003+
1004+
#if 0
1005+
// draw the axes for the text around the pivot point
1006+
QPainterPath linePath;
1007+
1008+
linePath.moveTo(-50, 0);
1009+
linePath.lineTo(50, 0);
1010+
linePath.moveTo(0, -50);
1011+
linePath.lineTo(0, 50);
1012+
painter.strokePath(linePath, QPen(Qt::green, 1.0));
1013+
#endif
1014+
1015+
// flip vertically so the text is upright, and then use -labelY since we're flipped
1016+
painter.scale(1.0, -1.0);
1017+
1018+
painter.drawText(QPointF(labelX, -labelY), labelText);
1019+
1020+
painter.restore();
1021+
}
1022+
else
1023+
{
1024+
// a NAN or INF value for x or y is not plotted
1025+
}
1026+
}
1027+
1028+
painter.restore();
1029+
}
1030+
8801031
void QtSLiMGraphView_CustomPlot::drawRects(QPainter &painter, QRect interiorRect, int dataIndex)
8811032
{
8821033
double *x1data = x1data_[dataIndex];
@@ -1016,6 +1167,7 @@ void QtSLiMGraphView_CustomPlot::drawText(QPainter &painter, QRect interiorRect,
10161167
int pointCount = data_count_[dataIndex];
10171168
std::vector<QColor> &textColors = *color_[dataIndex];
10181169
std::vector<double> &textAlphas = *alpha_[dataIndex];
1170+
std::vector<double> &textAngles = *angle_[dataIndex];
10191171
std::vector<double> &pointSizes = *size_[dataIndex];
10201172
double xadj = xadj_[dataIndex];
10211173
double yadj = yadj_[dataIndex];
@@ -1032,8 +1184,18 @@ void QtSLiMGraphView_CustomPlot::drawText(QPainter &painter, QRect interiorRect,
10321184

10331185
if (std::isfinite(user_x) && std::isfinite(user_y))
10341186
{
1187+
QString &labelText = labels[pointIndex];
10351188
double x = plotToDeviceX(user_x, interiorRect);
10361189
double y = plotToDeviceY(user_y, interiorRect);
1190+
1191+
//qDebug() << "labelText ==" << labelText << ", user_x ==" << user_x << ", user_y ==" << user_y << ", x ==" << x << ", y ==" << y;
1192+
1193+
// translate the painter so (x, y) is at (0, 0)
1194+
painter.save();
1195+
painter.translate(x, y);
1196+
x = 0;
1197+
y = 0;
1198+
10371199
double pointSize = pointSizes[pointIndex % pointSizes.size()];
10381200

10391201
if (pointSize != lastPointSize)
@@ -1053,7 +1215,6 @@ void QtSLiMGraphView_CustomPlot::drawText(QPainter &painter, QRect interiorRect,
10531215

10541216
painter.setPen(textColor);
10551217

1056-
QString &labelText = labels[pointIndex];
10571218
QRect labelBoundingRect = painter.boundingRect(QRect(), Qt::TextDontClip | Qt::TextSingleLine, labelText);
10581219

10591220
// labelBoundingRect is useful for its width, which seems to be calculated correctly; its height, however, is oddly large, and
@@ -1064,22 +1225,32 @@ void QtSLiMGraphView_CustomPlot::drawText(QPainter &painter, QRect interiorRect,
10641225
double labelX = x - SLIM_SCREEN_ROUND(labelWidth * xadj);
10651226
double labelY = y - SLIM_SCREEN_ROUND(labelHeight * yadj);
10661227

1067-
//qDebug() << "labelText ==" << labelText << ", user_x ==" << user_x << ", user_y ==" << user_y << ", x ==" << x << ", y ==" << y;
10681228
//qDebug() << " labelBoundingRect ==" << labelBoundingRect << ", labelWidth ==" << labelWidth << ", labelHeight ==" << labelHeight << ", capHeight ==" << capHeight;
10691229
//qDebug() << " labelX ==" << labelX << ", labelY ==" << labelY;
10701230

1071-
// we need to correct for the fact that the coordinate system is flipped; text would draw upside-down
1072-
// we do that by transforming labelY and then turning off the world matrix, to turn off the flipping
1073-
// note that labelX transformed is unchanged, since the device coordinate origin is 0 anyway
1074-
QPointF transformedPoint = painter.transform().map(QPointF(labelX, labelY));
1231+
// rotate the coordinate system around (x, y); for example, -10.0 is 10 degrees clockwise
1232+
double textAngle = textAngles[pointIndex];
1233+
1234+
if (textAngle != 0.0)
1235+
painter.rotate(-textAngles[pointIndex]);
1236+
1237+
#if 0
1238+
// draw the axes for the text around the pivot point
1239+
QPainterPath linePath;
1240+
1241+
linePath.moveTo(-50, 0);
1242+
linePath.lineTo(50, 0);
1243+
linePath.moveTo(0, -50);
1244+
linePath.lineTo(0, 50);
1245+
painter.strokePath(linePath, QPen(Qt::green, 1.0));
1246+
#endif
10751247

1076-
labelY = transformedPoint.y();
1248+
// flip vertically so the text is upright, and then use -labelY since we're flipped
1249+
painter.scale(1.0, -1.0);
10771250

1078-
//qDebug() << " transformedPoint ==" << transformedPoint << ", labelY ==" << labelY;
1251+
painter.drawText(QPointF(labelX, -labelY), labelText);
10791252

1080-
painter.setWorldMatrixEnabled(false);
1081-
painter.drawText(QPointF(labelX, labelY), labelText);
1082-
painter.setWorldMatrixEnabled(true);
1253+
painter.restore();
10831254
}
10841255
else
10851256
{

QtSLiM/QtSLiMGraphView_CustomPlot.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ enum class QtSLiM_CustomPlotType : int {
3434
kSegments,
3535
kRects,
3636
kPoints,
37+
kMarginText,
3738
kText,
3839
kABLines, // from abline()
3940
kHLines, // from abline()
@@ -75,6 +76,9 @@ class QtSLiMGraphView_CustomPlot : public QtSLiMGraphView
7576
void addLineData(double *x_values, double *y_values, int data_count,
7677
std::vector<QColor> *color, std::vector<double> *alpha,
7778
std::vector<double> *lwd);
79+
void addMarginTextData(double *x_values, double *y_values, std::vector<QString> *labels, int data_count,
80+
std::vector<QColor> *color, std::vector<double> *alpha, std::vector<double> *size,
81+
double *adj, std::vector<double> *angle);
7882
void addPointData(double *x_values, double *y_values, int data_count,
7983
std::vector<int> *symbol, std::vector<QColor> *color, std::vector<QColor> *border,
8084
std::vector<double> *alpha, std::vector<double> *lwd, std::vector<double> *size);
@@ -85,7 +89,8 @@ class QtSLiMGraphView_CustomPlot : public QtSLiMGraphView
8589
int data_count, std::vector<QColor> *color,
8690
std::vector<double> *alpha, std::vector<double> *lwd);
8791
void addTextData(double *x_values, double *y_values, std::vector<QString> *labels, int data_count,
88-
std::vector<QColor> *color, std::vector<double> *alpha, std::vector<double> *size, double *adj);
92+
std::vector<QColor> *color, std::vector<double> *alpha, std::vector<double> *size,
93+
double *adj, std::vector<double> *angle);
8994

9095
void addLegend(QtSLiM_LegendPosition position, int inset, double labelSize, double lineHeight,
9196
double graphicsWidth, double exteriorMargin, double interiorMargin);
@@ -128,6 +133,7 @@ public slots:
128133
std::vector<std::vector<double> *> alpha_; // one alpha per point, OR one alpha for all points
129134
std::vector<std::vector<double> *> line_width_; // one lwd per point, OR one lwd for all points
130135
std::vector<std::vector<double> *> size_; // one size per point, OR one size for all points
136+
std::vector<std::vector<double> *> angle_; // one angle per point, OR one angle for all points
131137
std::vector<double> xadj_; // one xadj for all points
132138
std::vector<double> yadj_; // one yadj for all points
133139
std::vector<QImage> image_; // one QImage per image data; QImage() if unused
@@ -138,6 +144,7 @@ public slots:
138144
void drawHLines(QPainter &painter, QRect interiorRect, int dataIndex);
139145
void drawVLines(QPainter &painter, QRect interiorRect, int dataIndex);
140146
void drawLines(QPainter &painter, QRect interiorRect, int dataIndex);
147+
void drawMarginText(QPainter &painter, QRect interiorRect, int dataIndex);
141148
void drawSegments(QPainter &painter, QRect interiorRect, int dataIndex);
142149
void drawRects(QPainter &painter, QRect interiorRect, int dataIndex);
143150
void drawPoints(QPainter &painter, QRect interiorRect, int dataIndex);

0 commit comments

Comments
 (0)