@@ -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
284289QRect 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
303317double 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+
11531211void 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
17731855void 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;
0 commit comments