Skip to content

Commit c8457a6

Browse files
committed
add Plot method setBorderless() for plots without axes/ticks/labels
1 parent 2052275 commit c8457a6

25 files changed

Lines changed: 413 additions & 120 deletions

QtSLiM/QtSLiMGraphView.cpp

Lines changed: 133 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,15 @@ QtSLiMGraphView::QtSLiMGraphView(QWidget *p_parent, QtSLiMWindow *controller) :
7575
connect(controller, &QtSLiMWindow::controllerTickFinished, this, &QtSLiMGraphView::controllerTickFinished);
7676
connect(controller, &QtSLiMWindow::controllerRecycled, this, &QtSLiMGraphView::controllerRecycled);
7777

78-
x0_ = 0.0;
79-
x1_ = 1.0;
80-
y0_ = 0.0;
81-
y1_ = 1.0;
78+
original_x0_ = 0.0;
79+
original_x1_ = 1.0;
80+
original_y0_ = 0.0;
81+
original_y1_ = 1.0;
82+
83+
x0_ = original_x0_;
84+
x1_ = original_x1_;
85+
y0_ = original_y0_;
86+
y1_ = original_y1_;
8287

8388
showXAxis_ = true;
8489
allowXAxisUserRescale_ = true;
@@ -283,21 +288,30 @@ QComboBox *QtSLiMGraphView::newButtonInLayout(QHBoxLayout *p_layout)
283288

284289
QRect QtSLiMGraphView::interiorRectForBounds(QRect bounds)
285290
{
291+
// The interiorRect is the area which QtSLiMGraphView clips the plotting within. So it is the live plot
292+
// area, within the axes of the plot. In a borderless plot, it is the full interior area of the window,
293+
// but afer setting up clipping drawContents() will inset the interiorRect by the margins given to
294+
// setBorderless(); the data area will be inset by the margins, but not clipped to the margins. This is
295+
// conceptually similar to the 4% expansion of the axis ranges done in bordered plots.
286296
QRect interiorRect = bounds;
287-
288-
// For now, 10 pixels margin on a side if there is no axis, 40 pixels margin if there is an axis
289-
290-
if (showXAxis_)
297+
298+
// If the plot is borderless, there is no inset for the interior rect at all
299+
if (is_borderless_)
300+
return interiorRect;
301+
302+
// For now, 10 pixels margin on a side if there is no axis, 40 pixels margin if there is an axis
303+
304+
if (showXAxis_)
291305
interiorRect.adjust(50, 0, -10, 0);
292-
else
306+
else
293307
interiorRect.adjust(10, 0, -10, 0);
294-
295-
if (showYAxis_)
308+
309+
if (showYAxis_)
296310
interiorRect.adjust(0, 50, 0, -10);
297-
else
311+
else
298312
interiorRect.adjust(0, 10, 0, -10);
299-
300-
return interiorRect;
313+
314+
return interiorRect;
301315
}
302316

303317
double QtSLiMGraphView::plotToDeviceX(double plotx, QRect interiorRect)
@@ -1150,6 +1164,50 @@ void QtSLiMGraphView::drawLegendInInteriorRect(QPainter &painter, QRect interior
11501164
}
11511165
}
11521166

1167+
void QtSLiMGraphView::setBorderless(bool isBorderless, double marginLeft, double marginTop, double marginRight, double marginBottom)
1168+
{
1169+
// Convert to/from a borderless plot; this is used only by custom plots
1170+
is_borderless_ = isBorderless;
1171+
1172+
borderless_margin_left_ = marginLeft;
1173+
borderless_margin_top_ = marginTop;
1174+
borderless_margin_right_ = marginRight;
1175+
borderless_margin_bottom_ = marginBottom;
1176+
1177+
// x0/y0/x1/y1 do not change, but changing our borderless value reconfigures the axis ranges; unless they have been
1178+
// explicitly set by the user in the QtSLiM UI, they get re-evaluated based upon the original axis range
1179+
if (!xAxisIsUIRescaled_)
1180+
{
1181+
// we generally try to maintain original_x0_ and original_x1_ in sync with x0_ and x1_, but this is the
1182+
// only place where original_x0_ and original_x1_ actually matter outside of where they are locally set
1183+
// up -- the only place where we re-set x0_ and x1_ back to their original values, because we want to
1184+
// re-generate a new axis range based upon a changed is_borderless_ value
1185+
x0_ = original_x0_;
1186+
x1_ = original_x1_;
1187+
1188+
//std::cout << "is_borderless_ == " << (is_borderless_ ? "T" : "F") << std::endl;
1189+
//std::cout << " BEFORE: x0_ == " << x0_ << ", x1_ == " << x1_ << ", xAxisMin_ == " << xAxisMin_ << ", xAxisMax_ == " << xAxisMax_ << std::endl;
1190+
1191+
configureAxisForRange(x0_, x1_, xAxisMin_, xAxisMax_, xAxisMajorTickInterval_, xAxisMinorTickInterval_,
1192+
xAxisMajorTickModulus_, xAxisTickValuePrecision_);
1193+
1194+
//std::cout << " AFTER: x0_ == " << x0_ << ", x1_ == " << x1_ << ", xAxisMin_ == " << xAxisMin_ << ", xAxisMax_ == " << xAxisMax_ << std::endl;
1195+
}
1196+
1197+
if (!yAxisIsUIRescaled_)
1198+
{
1199+
// we generally try to maintain original_y0_ and original_y1_ in sync with y0_ and y1_, but this is the
1200+
// only place where original_y0_ and original_y1_ actually matter outside of where they are locally set
1201+
// up -- the only place where we re-set y0_ and y1_ back to their original values, because we want to
1202+
// re-generate a new axis range based upon a changed is_borderless_ value
1203+
y0_ = original_y0_;
1204+
y1_ = original_y1_;
1205+
1206+
configureAxisForRange(y0_, y1_, yAxisMin_, yAxisMax_, yAxisMajorTickInterval_, yAxisMinorTickInterval_,
1207+
yAxisMajorTickModulus_, yAxisTickValuePrecision_);
1208+
}
1209+
}
1210+
11531211
void QtSLiMGraphView::drawContents(QPainter &painter)
11541212
{
11551213
// Set to a default color of black; I thought Qt did this for me, but apparently not
@@ -1202,7 +1260,11 @@ void QtSLiMGraphView::drawContents(QPainter &painter)
12021260
// We clip the interior drawing to the interior rect, so outliers get clipped out
12031261
painter.save();
12041262
painter.setClipRect(interiorRect, Qt::IntersectClip);
1205-
1263+
1264+
// When drawing borderless, we have margins by which we inset interiorRect, but we clip to bounds
1265+
if (is_borderless_)
1266+
interiorRect.adjust(borderless_margin_left_, borderless_margin_bottom_, -borderless_margin_right_, -borderless_margin_top_);
1267+
12061268
drawGraph(painter, interiorRect);
12071269

12081270
painter.restore();
@@ -1211,21 +1273,30 @@ void QtSLiMGraphView::drawContents(QPainter &painter)
12111273
if (!cachingNow_)
12121274
{
12131275
// Overdraw axes, ticks, and axis labels, if requested
1214-
if (showXAxis_)
1215-
drawXAxis(painter, interiorRect);
1216-
1217-
if (showYAxis_)
1218-
drawYAxis(painter, interiorRect);
1219-
1220-
if (showFullBox_)
1221-
drawFullBox(painter,interiorRect);
1222-
1223-
if (showXAxis_ && showXAxisTicks_)
1224-
drawXAxisTicks(painter, interiorRect);
1225-
1226-
if (showYAxis_ && showYAxisTicks_)
1227-
drawYAxisTicks(painter, interiorRect);
1228-
1276+
if (!is_borderless_)
1277+
{
1278+
if (showXAxis_)
1279+
drawXAxis(painter, interiorRect);
1280+
1281+
if (showYAxis_)
1282+
drawYAxis(painter, interiorRect);
1283+
1284+
if (showFullBox_)
1285+
drawFullBox(painter, interiorRect);
1286+
1287+
if (showXAxis_ && showXAxisTicks_)
1288+
drawXAxisTicks(painter, interiorRect);
1289+
1290+
if (showYAxis_ && showYAxisTicks_)
1291+
drawYAxisTicks(painter, interiorRect);
1292+
}
1293+
else
1294+
{
1295+
// when borderless, the "full box", if requested, frames the full bounds
1296+
if (showFullBox_)
1297+
QtSLiMFrameRect(bounds, Qt::black, painter);
1298+
}
1299+
12291300
// Overdraw the legend
12301301
if (legendVisible_)
12311302
drawLegendInInteriorRect(painter, interiorRect);
@@ -1582,10 +1653,14 @@ void QtSLiMGraphView::contextMenuEvent(QContextMenuEvent *p_event)
15821653
xAxisTickValuePrecision_ = choices[4].toInt();
15831654
xAxisMinorTickInterval_ = xAxisMajorTickInterval_ / xAxisMajorTickModulus_;
15841655
xAxisIsUserRescaled_ = true;
1656+
xAxisIsUIRescaled_ = true;
15851657

15861658
// for now, these are the same, except in custom plots
1587-
x0_ = xAxisMin_;
1588-
x1_ = xAxisMax_;
1659+
original_x0_ = xAxisMin_;
1660+
original_x1_ = xAxisMax_;
1661+
1662+
x0_ = original_x0_;
1663+
x1_ = original_x1_;
15891664

15901665
invalidateDrawingCache();
15911666
update();
@@ -1608,10 +1683,14 @@ void QtSLiMGraphView::contextMenuEvent(QContextMenuEvent *p_event)
16081683
yAxisTickValuePrecision_ = choices[4].toInt();
16091684
yAxisMinorTickInterval_ = yAxisMajorTickInterval_ / yAxisMajorTickModulus_;
16101685
yAxisIsUserRescaled_ = true;
1686+
yAxisIsUIRescaled_ = true;
16111687

16121688
// for now, these are the same, except in custom plots
1613-
y0_ = yAxisMin_;
1614-
y1_ = yAxisMax_;
1689+
original_y0_ = yAxisMin_;
1690+
original_y1_ = yAxisMax_;
1691+
1692+
y0_ = original_y0_;
1693+
y1_ = original_y1_;
16151694

16161695
invalidateDrawingCache();
16171696
update();
@@ -1632,7 +1711,8 @@ void QtSLiMGraphView::contextMenuEvent(QContextMenuEvent *p_event)
16321711
yAxisMax_ = (double)newPower;
16331712

16341713
// for now, these are the same, except in custom plots
1635-
y1_ = yAxisMax_;
1714+
original_y1_ = yAxisMax_;
1715+
y1_ = original_y1_;
16361716

16371717
invalidateDrawingCache();
16381718
update();
@@ -1754,7 +1834,8 @@ void QtSLiMGraphView::setXAxisRangeFromTick(void)
17541834
xAxisTickValuePrecision_ = 0;
17551835

17561836
// for now, these are the same, except in custom plots
1757-
x1_ = xAxisMax_;
1837+
original_x1_ = xAxisMax_;
1838+
x1_ = original_x1_;
17581839
}
17591840
else
17601841
{
@@ -1766,18 +1847,30 @@ void QtSLiMGraphView::setXAxisRangeFromTick(void)
17661847
xAxisTickValuePrecision_ = 0;
17671848

17681849
// for now, these are the same, except in custom plots
1769-
x1_ = xAxisMax_;
1850+
original_x1_ = xAxisMax_;
1851+
x1_ = original_x1_;
17701852
}
17711853
}
17721854

17731855
void QtSLiMGraphView::configureAxisForRange(double &dim0, double &dim1, double &axisMin, double &axisMax,
17741856
double &majorTickInterval, double &minorTickInterval,
17751857
int &majorTickModulus, int &tickValuePrecision)
17761858
{
1777-
// We call down to our R-inspired axis calculation methods to figure out a good axis layout.
1778-
// The call here, to _GScale(), is parallel to the point in R's plot.window() function where
1779-
// it calls down to GScale() for each of the two axes.
1859+
if (is_borderless_)
1860+
{
1861+
// For borderless plots, we leave the axis range untouched; the axis range does not get outset.
1862+
// However, drawContents() will inset the interiorRect by the margins given to setBorderless();
1863+
// that provides a similar way of framing the data with margins around them.
1864+
axisMin = dim0;
1865+
axisMax = dim1;
1866+
}
1867+
else
17801868
{
1869+
// We call down to our R-inspired axis calculation methods to figure out a good axis layout.
1870+
// The call here, to _GScale(), is parallel to the point in R's plot.window() function where
1871+
// it calls down to GScale() for each of the two axes. Note that this branch modifies dim0
1872+
// and dim1; we keep the "original_" versions of the axis ranges so that we can back that out
1873+
// in setBorderless().
17811874
int nDivisions;
17821875

17831876
//qDebug() << "configureAxisForRange() : original dim0 ==" << dim0 << ", dim1 ==" << dim1;

QtSLiM/QtSLiMGraphView.h

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ class QtSLiMGraphView : public QWidget
106106
virtual QString aboutString(void) = 0;
107107
virtual void drawGraph(QPainter &painter, QRect interiorRect);
108108

109+
void setBorderless(bool isBorderless, double marginLeft, double marginTop, double marginRight, double marginBottom);
110+
109111
bool writeToFile(QString fileName);
110112

111113
public slots:
@@ -186,17 +188,25 @@ public slots:
186188
size_t tallyGUIMutationReferences(const std::vector<Haplosome *> &haplosomes, int muttype_index);
187189

188190
// Properties; initialized in the constructor, these defaults are just zero-fill
189-
// Note that the bounds in user coordinates (x0_/x1_/y0_/y1_) are now separate from
190-
// the axis limits (xAxisMin_, ...), but at present they are always the same except
191-
// for custom plots. That may change, going forward, to improve axis behavior.
191+
// The bounds in user coordinates (x0_/x1_/y0_/y1_) are the actual extents of the
192+
// axes; this is somewhat unrelated to the positions at which tick and labels are
193+
// drawn, which are prettified. The axis limits (xAxisMin_, xAxisMax_, ...) express
194+
// the limits of tick marks; they are the conceptual limits of the axes, but
195+
// typically there are margins of 4% or so outside of that range so the data isn't
196+
// right up against the edges of the plot. The original axis limits provided
197+
// by the user are kept in original_x0_ etc., since x0_ and xAxisMin_ do not
198+
// necessarily reflect the original values; they can get changed for aesthetics.
199+
// See QtSLiMGraphView::setBorderless() for the reason we need the original values.
200+
double original_x0_ = 0.0, original_x1_ = 0.0, original_y0_ = 0.0, original_y1_ = 0.0;
192201
double x0_ = 0.0, x1_ = 0.0, y0_ = 0.0, y1_ = 0.0; // coordinate space bounds
193202

194203
double axisLabelSize_ = 15;
195204
double tickLabelSize_ = 10;
196205

197206
bool showXAxis_ = false;
198207
bool allowXAxisUserRescale_ = false;
199-
bool xAxisIsUserRescaled_ = false;
208+
bool xAxisIsUserRescaled_ = false; // the user gave a custom axis scale in any way (such as in createPlot())
209+
bool xAxisIsUIRescaled_ = false; // the user gave a custom axis scale in the QtSLiM UI, which has precedence
200210
bool showXAxisTicks_ = false;
201211
double xAxisMin_ = 0.0, xAxisMax_ = 0.0;
202212
double xAxisMajorTickInterval_ = 0.0, xAxisMinorTickInterval_ = 0.0;
@@ -210,7 +220,8 @@ public slots:
210220

211221
bool showYAxis_ = false;
212222
bool allowYAxisUserRescale_ = false;
213-
bool yAxisIsUserRescaled_ = false;
223+
bool yAxisIsUserRescaled_ = false; // the user gave a custom axis scale in any way (such as in createPlot())
224+
bool yAxisIsUIRescaled_ = false; // the user gave a custom axis scale in the QtSLiM UI, which has precedence
214225
bool showYAxisTicks_ = false;
215226
double yAxisMin_ = 0.0, yAxisMax_ = 0.0;
216227
double yAxisMajorTickInterval_ = 0.0, yAxisMinorTickInterval_ = 0.0;
@@ -242,6 +253,15 @@ public slots:
242253

243254
bool tweakXAxisTickLabelAlignment_ = false;
244255

256+
// borderless plots show no axes, no ticks, no labels; this feature is used only for custom plots
257+
// for borderless plots, the plot region is the whole window area, and the data range fills the plot
258+
// that is inset by the margins given here, measured in pixels, to allow a bit of overflow
259+
bool is_borderless_ = false;
260+
double borderless_margin_left_ = 0.0;
261+
double borderless_margin_right_ = 0.0;
262+
double borderless_margin_top_ = 0.0;
263+
double borderless_margin_bottom_ = 0.0;
264+
245265
// Prefab additions properties
246266
int histogramBinCount_ = 0;
247267
bool allowBinCountRescale_ = false;

QtSLiM/QtSLiMGraphView_1DSampleSFS.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,22 @@ QtSLiMGraphView_1DSampleSFS::QtSLiMGraphView_1DSampleSFS(QWidget *p_parent, QtSL
4040
histogramBinCount_ = 20; // this is also the haplosome sample size
4141
allowBinCountRescale_ = false;
4242

43-
x0_ = 0;
44-
x1_ = histogramBinCount_;
43+
original_x0_ = 0;
44+
original_x1_ = histogramBinCount_;
45+
46+
x0_ = original_x0_;
47+
x1_ = original_x1_;
4548

4649
xAxisMin_ = x0_;
4750
xAxisMax_ = x1_;
4851
xAxisHistogramStyle_ = true;
4952
xAxisTickValuePrecision_ = 0;
5053

51-
y0_ = -0.05; // on log scale; we want a frequency of 1 to show slightly above baseline
52-
y1_ = 3.0; // on log scale; maximum power of 10
54+
original_y0_ = -0.05; // on log scale; we want a frequency of 1 to show slightly above baseline
55+
original_y1_ = 3.0; // on log scale; maximum power of 10
56+
57+
y0_ = original_y0_;
58+
y1_ = original_y1_;
5359

5460
yAxisMin_ = y0_;
5561
yAxisMax_ = y1_;
@@ -245,7 +251,8 @@ void QtSLiMGraphView_1DSampleSFS::changeSampleSize(void)
245251
{
246252
histogramBinCount_ = newSampleSize;
247253
xAxisMax_ = histogramBinCount_;
248-
x1_ = histogramBinCount_; // the same as xAxisMax_, for base plots
254+
original_x1_ = histogramBinCount_; // the same as xAxisMax_, for base plots
255+
x1_ = original_x1_;
249256
invalidateCachedData();
250257
update();
251258
}

0 commit comments

Comments
 (0)