Skip to content

Commit 7055d96

Browse files
joa-quimclaude
andcommitted
Fit 3-D subplot panels, and their heading, inside the figure (#4450)
A perspective panel is drawn as a projected box that is larger than the flat map rectangle subplot lays its panels out from, and nothing ever fitted that box to the panel. In the reported example both cubes burst out of the -Ff20c/10c figure area and the heading was laid out against the flat rectangle, so it ended up among them. gmt_map_setup could not have fitted the box earlier: it is only known once gmtmap_init_three_D has run, at the very end of the setup. So the fit now happens there, for a panel in 3-D: the footprint is measured from the eight corners of the x/y/z region, padded for the ticks and annotations that the frame draws outside it (a quarter of that above the box, where nothing is annotated), and if it does not fit the panel the projection is scaled down and the box centered in the panel via z_project.x_off/y_off. The scaling includes proj.zmax/zmin, the z-axis length in plot units that the PostScript perspective matrix works from; without those the numbers changed but the picture did not. The heading is now placed at subplot end rather than at subplot begin, since only then is it known how far a 3-D panel reaches: begin records the position it would have used in gmt.subplotheading.<fig> together with the canvas corner (as PSL_SUBPLOT_ox/oy in the PostScript, so no assumption about -X -Y is needed), gmt_plotinit records the highest projected y of any 3-D panel in gmt.subplottop.<fig>, and end returns to that corner, raises the heading by any excess and puts the origin back for whatever follows. The panel settings are cleared around that call, or the heading is scaled into the last panel, and restored afterwards since the -R history is built from them. 2-D figures write no gmt.subplottop file and are unaffected; test/subplot is unchanged apart from the new test. test/subplot/subplot_3d_fit.sh plots the example from the issue and requires the drawn height to stay near the 10 cm of the figure; the unfitted cubes reach some 19 cm. It measures the plot instead of comparing it, so it needs no baseline PostScript. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 45da506 commit 7055d96

4 files changed

Lines changed: 226 additions & 31 deletions

File tree

src/gmt_map.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6647,6 +6647,56 @@ GMT_LOCAL int gmtmap_init_three_D (struct GMT_CTRL *GMT) {
66476647
GMT->current.proj.z_project.ymin += GMT->current.proj.z_project.y_off;
66486648
GMT->current.proj.z_project.ymax += GMT->current.proj.z_project.y_off;
66496649

6650+
if (GMT->current.proj.three_D && GMT->current.plot.panel.active && GMT->current.plot.panel.no_scaling == 0) {
6651+
/* We are in a subplot panel. gmtmap_setxy could only fit the nominal 2-D map rectangle into the panel,
6652+
* since the perspective box is not known until here, and that box is the larger of the two - so the
6653+
* panels stuck out of the figure [issue #4450]. Measure the box the frame will actually occupy from
6654+
* the eight corners of the region, shrink the projection until it fits the panel, and center it there.
6655+
* All projected coordinates are linear in the plot scales, so the measured box scales with them. */
6656+
struct GMT_SUBPLOT *P = &(GMT->current.plot.panel);
6657+
double bx[2] = {DBL_MAX, -DBL_MAX}, by[2] = {DBL_MAX, -DBL_MAX}, bw, bh, f, xc, yc, pad;
6658+
unsigned int ix, iy, iz;
6659+
for (ix = 0; ix < 2; ix++) for (iy = 0; iy < 2; iy++) for (iz = 0; iz < 2; iz++) {
6660+
gmt_xyz_to_xy (GMT, gmt_x_to_xx (GMT, GMT->common.R.wesn[XLO+ix]), gmt_y_to_yy (GMT, GMT->common.R.wesn[YLO+iy]),
6661+
gmt_z_to_zz (GMT, GMT->common.R.wesn[ZLO+iz]), &xc, &yc);
6662+
bx[0] = MIN (bx[0], xc); bx[1] = MAX (bx[1], xc);
6663+
by[0] = MIN (by[0], yc); by[1] = MAX (by[1], yc);
6664+
}
6665+
/* The frame is annotated outside that box, so leave room for a tick, the annotation offset and the
6666+
* annotation itself on every side, or the annotations hang outside the panel */
6667+
pad = GMT->current.setting.map_tick_length[GMT_PRIMARY] + GMT->current.setting.map_annot_offset[GMT_PRIMARY] +
6668+
2.0 * GMT->current.setting.font_annot[GMT_PRIMARY].size * GMT->session.u2u[GMT_PT][GMT_INCH];
6669+
bx[0] -= pad; bx[1] += pad; by[0] -= pad; by[1] += 0.25 * pad; /* Nothing is annotated above the box */
6670+
bw = bx[1] - bx[0]; bh = by[1] - by[0];
6671+
f = (bw > 0.0 && bh > 0.0) ? MIN (P->w / bw, P->h / bh) : 1.0;
6672+
if (f < 1.0) { /* Too big for the panel, so scale the projection down */
6673+
GMT->current.proj.scale[GMT_X] *= f; GMT->current.proj.scale[GMT_Y] *= f; GMT->current.proj.scale[GMT_Z] *= f;
6674+
GMT->current.proj.i_scale[GMT_X] /= f; GMT->current.proj.i_scale[GMT_Y] /= f; GMT->current.proj.i_scale[GMT_Z] /= f;
6675+
GMT->current.proj.w_r *= f;
6676+
GMT->current.proj.rect[XHI] *= f; GMT->current.proj.rect[YHI] *= f;
6677+
GMT->current.proj.origin[GMT_X] *= f; GMT->current.proj.origin[GMT_Y] *= f; GMT->current.proj.origin[GMT_Z] *= f;
6678+
GMT->current.proj.zmax *= f; GMT->current.proj.zmin *= f; /* The z axis length is in plot units too */
6679+
GMT->current.proj.z_project.x_off *= f; GMT->current.proj.z_project.y_off *= f;
6680+
GMT->current.proj.z_project.xmin *= f; GMT->current.proj.z_project.xmax *= f;
6681+
GMT->current.proj.z_project.ymin *= f; GMT->current.proj.z_project.ymax *= f;
6682+
GMT->current.map.width *= f; GMT->current.map.height *= f;
6683+
GMT->current.map.half_width = 0.5 * GMT->current.map.width;
6684+
GMT->current.map.half_height = 0.5 * GMT->current.map.height;
6685+
bx[0] *= f; bx[1] *= f; by[0] *= f; by[1] *= f;
6686+
bw *= f; bh *= f;
6687+
}
6688+
/* Shift the projection so the box sits centered inside the panel */
6689+
GMT->current.proj.z_project.x_off += 0.5 * (P->w - bw) - bx[0];
6690+
GMT->current.proj.z_project.y_off += 0.5 * (P->h - bh) - by[0];
6691+
GMT->current.proj.z_project.xmin += 0.5 * (P->w - bw) - bx[0];
6692+
GMT->current.proj.z_project.xmax += 0.5 * (P->w - bw) - bx[0];
6693+
GMT->current.proj.z_project.ymin += 0.5 * (P->h - bh) - by[0];
6694+
GMT->current.proj.z_project.ymax += 0.5 * (P->h - bh) - by[0];
6695+
P->dx = P->dy = 0.0; /* The centering is in the projection now */
6696+
GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Perspective panel scaled by %g to a %g x %g footprint inside its %g x %g panel\n",
6697+
f, bw, bh, P->w, P->h);
6698+
}
6699+
66506700
return (GMT_NOERROR);
66516701
}
66526702

src/gmt_plot.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9277,6 +9277,25 @@ struct PSL_CTRL *gmt_plotinit (struct GMT_CTRL *GMT, struct GMT_OPTION *options)
92779277
/* Consider offsets required to center the plot on the subplot panel [0/0] */
92789278
GMT->current.setting.map_origin[GMT_X] += (P->dx + P->gap[XLO]);
92799279
GMT->current.setting.map_origin[GMT_Y] += (P->dy + P->gap[YLO]);
9280+
if (GMT->current.proj.three_D) {
9281+
/* A subplot heading is laid out in figure coordinates, while a perspective panel may extend above its
9282+
* nominal 2-D rectangle. Record the highest projected y reached by any 3-D panel so that subplot end
9283+
* can place the figure heading above the real perspective footprint (issue #4450). */
9284+
int fig = gmt_get_current_figure (GMT->parent);
9285+
char file[PATH_MAX] = {""};
9286+
double old_top = -DBL_MAX;
9287+
double top = GMT->current.setting.map_origin[GMT_Y] + GMT->current.proj.z_project.ymax;
9288+
FILE *fp_top = NULL;
9289+
snprintf (file, PATH_MAX, "%s/gmt.subplottop.%d", GMT->parent->gwf_dir, fig);
9290+
if ((fp_top = fopen (file, "r")) != NULL) {
9291+
if (fscanf (fp_top, "%lf", &old_top) != 1) old_top = -DBL_MAX;
9292+
fclose (fp_top);
9293+
}
9294+
if (top > old_top && (fp_top = fopen (file, "w")) != NULL) {
9295+
fprintf (fp_top, "%.16g\n", top);
9296+
fclose (fp_top);
9297+
}
9298+
}
92809299
if (P->first && O_active) /* Run completion script, if any */
92819300
PSL_setexec (PSL, 1);
92829301
}

src/subplot.c

Lines changed: 113 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,8 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) {
14351435
}
14361436
}
14371437

1438-
/* Start the subplot with a blank canvas and place the optional title.
1438+
/* Start the subplot with a blank canvas. The optional figure heading is deferred until subplot end
1439+
so that the perspective extent of any 3-D panel is known when it is placed [issue #4450].
14391440
The blank canvas dimensions should become the -R and -Jx1 once subplot ends */
14401441

14411442
if (Ctrl->F.fill[0] != '-' && Ctrl->F.pen[0] != '-') /* Need to fill and draw the canvas box */
@@ -1448,36 +1449,41 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) {
14481449
width += 2.0 * Ctrl->F.clearance[GMT_X];
14491450
height += 2.0 * Ctrl->F.clearance[GMT_Y];
14501451

1451-
if (Ctrl->T.title) { /* Must call text to place a heading */
1452-
uint64_t dim[4] = {1, 1, 1, 2}; /* A single record */
1453-
struct GMT_DATASET *T = NULL;
1454-
if ((T = GMT_Create_Data (API, GMT_IS_DATASET, GMT_IS_NONE, GMT_WITH_STRINGS, dim, NULL, NULL, 0, 0, NULL)) == NULL) {
1455-
GMT_Report (API, GMT_MSG_ERROR, "Subplot: Unable to allocate a dataset\n");
1456-
Return (error);
1452+
/* plot is required, since nothing is plotted here (except for possibly the canvas fill/outline) */
1453+
sprintf (command, "-R0/%g/0/%g -Jx1i -T%s --GMT_HISTORY=readonly", width, height, origin_shift);
1454+
if (Bopt[0]) strcat (command, Bopt); /* The -B was set above, so include it in the command */
1455+
GMT_Report (API, GMT_MSG_DEBUG, "Subplot command for plot: %s\n", command);
1456+
if (GMT_Call_Module (API, "plot", GMT_MODULE_CMD, command) != GMT_OK) /* Plot the canvas */
1457+
Return (API->error);
1458+
if (Ctrl->T.title) { /* Save exactly where and how the heading is to be plotted by subplot end [issue #4450].
1459+
* We must place it there and not here since a 3-D panel may reach above the nominal figure top, which
1460+
* is only known once the panels have been drawn. The position is the one the heading would have been
1461+
* given here, i.e. in the canvas frame; subplot end returns the origin to this canvas corner, which we
1462+
* remember in the PostScript itself so that no assumption about -X -Y is needed. */
1463+
FILE *fpt = NULL;
1464+
sprintf (file, "%s/gmt.subplotheading.%d", API->gwf_dir, fig);
1465+
if ((fpt = fopen (file, "w")) == NULL) {
1466+
GMT_Report (API, GMT_MSG_ERROR, "Cannot create subplot heading file %s\n", file);
1467+
Return (GMT_ERROR_ON_FOPEN);
14571468
}
1458-
T->table[0]->segment[0]->data[GMT_X][0] = 0.5 * width; /* Centered */
1459-
T->table[0]->segment[0]->data[GMT_Y][0] = y_heading + Ctrl->F.clearance[GMT_Y]; /* On top */
1460-
T->table[0]->segment[0]->text[0] = strdup (Ctrl->T.title);
1461-
T->table[0]->segment[0]->n_rows = 1;
1462-
T->n_records = T->table[0]->n_records = T->table[0]->segment[0]->n_rows = 1;
1463-
if (GMT_Open_VirtualFile (API, GMT_IS_DATASET, GMT_IS_NONE, GMT_IN|GMT_IS_REFERENCE, T, vfile) != GMT_NOERROR) {
1464-
Return (API->error);
1469+
/* Record: <x> <y> <width of region> <height of region> <top of figure> <title>, all in canvas inches */
1470+
fprintf (fpt, "%.16g %.16g %.16g %.16g %.16g %s\n", 0.5 * width, y_heading + Ctrl->F.clearance[GMT_Y],
1471+
width, height, height, Ctrl->T.title);
1472+
fclose (fpt);
1473+
/* Remember this canvas corner in the PostScript so subplot end can come back to it */
1474+
if (gmt_set_psfilename (GMT) == GMT_NOTSET) {
1475+
GMT_Report (GMT->parent, GMT_MSG_ERROR, "No workflow directory\n");
1476+
Return (GMT_ERROR_ON_FOPEN);
1477+
}
1478+
if ((fp = PSL_fopen (GMT->PSL, GMT->current.ps.filename, "a")) == NULL) { /* The canvas was just drawn, so append */
1479+
GMT_Report (API, GMT_MSG_ERROR, "Cannot open %s to append\n", GMT->current.ps.filename);
1480+
Return (GMT_ERROR_ON_FOPEN);
1481+
}
1482+
PSL_command (GMT->PSL, "/PSL_SUBPLOT_ox PSL_xorig def /PSL_SUBPLOT_oy PSL_yorig def\n");
1483+
if (PSL_fclose (GMT->PSL)) {
1484+
GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to close hidden PS file %s!\n", GMT->current.ps.filename);
1485+
Return (GMT_RUNTIME_ERROR);
14651486
}
1466-
sprintf (command, "-R0/%g/0/%g -Jx1i -N -F+jBC+f%s %s%s --GMT_HISTORY=readonly",
1467-
width, height, gmt_putfont (GMT, &GMT->current.setting.font_heading), vfile, origin_shift);
1468-
if (Bopt[0] == ' ') strcat (command, Bopt); /* The -B was set above, so include it in the command */
1469-
GMT_Report (API, GMT_MSG_DEBUG, "Subplot command for text: %s\n", command);
1470-
if (GMT_Call_Module (API, "text", GMT_MODULE_CMD, command) != GMT_OK) /* Plot the canvas with heading */
1471-
Return (API->error);
1472-
if (GMT_Destroy_Data (API, &T) != GMT_OK)
1473-
Return (API->error);
1474-
}
1475-
else { /* plot is required, since nothing is plotted (except for possibly the canvas fill/outline) */
1476-
sprintf (command, "-R0/%g/0/%g -Jx1i -T%s --GMT_HISTORY=readonly", width, height, origin_shift);
1477-
if (Bopt[0]) strcat (command, Bopt); /* The -B was set above, so include it in the command */
1478-
GMT_Report (API, GMT_MSG_DEBUG, "Subplot command for plot: %s\n", command);
1479-
if (GMT_Call_Module (API, "plot", GMT_MODULE_CMD, command) != GMT_OK) /* Plot the canvas with heading */
1480-
Return (API->error);
14811487
}
14821488
if (fabs (Ctrl->F.clearance[GMT_X]) > 0.0 || fabs (Ctrl->F.clearance[GMT_Y]) > 0.0) { /* Must reset origin */
14831489
width -= 2.0 * Ctrl->F.clearance[GMT_X];
@@ -1578,8 +1584,11 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) {
15781584
int k, id, row, col;
15791585
char *wmode[2] = {"w","a"}, vfile[GMT_VF_LEN] = {""}, Rtxt[GMT_LEN64] = {""}, off[GMT_LEN32] = {""};
15801586
char legend_justification[4] = {""}, Jstr[3] = {"J"}, pen[GMT_LEN32] = {""}, fill[GMT_LEN32] = {""};
1581-
double legend_width = 0.0, legend_scale = 1.0;
1582-
FILE *fp = NULL;
1587+
char line[GMT_BUFSIZ] = {""}, heading[GMT_BUFSIZ] = {""};
1588+
double legend_width = 0.0, legend_scale = 1.0, hx = 0.0, hy = 0.0, top = -DBL_MAX, ytop = DBL_MAX, Rw = 0.0, Rh = 0.0;
1589+
int n_chars = 0;
1590+
bool have_heading = false;
1591+
FILE *fp = NULL, *fh = NULL;
15831592

15841593
if ((P = gmt_subplot_info (API, fig)) == NULL) {
15851594
GMT_Report (GMT->parent, GMT_MSG_ERROR, "No subplot information file!\n");
@@ -1601,6 +1610,27 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) {
16011610
API->GMT->current.map.height = P->dim[GMT_Y];
16021611
P->active = 0; /* Ensure subplot mode is now terminated */
16031612

1613+
/* Collect what is needed to place the figure heading now that every panel has had a chance to report its
1614+
* actual perspective footprint. subplot begin saved the exact position, region and text; the heading
1615+
* itself is plotted further down, once we are truly out of subplot mode. For 2-D figures no
1616+
* gmt.subplottop file exists so the placement is the same as it always was. */
1617+
sprintf (file, "%s/gmt.subplotheading.%d", API->gwf_dir, fig);
1618+
if ((fh = fopen (file, "r")) != NULL) {
1619+
if (fgets (line, GMT_BUFSIZ, fh) && sscanf (line, "%lf %lf %lf %lf %lf %n", &hx, &hy, &Rw, &Rh, &ytop, &n_chars) == 5 && n_chars > 0) {
1620+
strncpy (heading, &line[n_chars], GMT_BUFSIZ-1);
1621+
gmt_chop (heading);
1622+
have_heading = true;
1623+
}
1624+
fclose (fh);
1625+
gmt_remove_file (GMT, file);
1626+
}
1627+
sprintf (file, "%s/gmt.subplottop.%d", API->gwf_dir, fig);
1628+
if ((fh = fopen (file, "r")) != NULL) { /* At least one panel was 3-D, so the heading may need raising */
1629+
if (fscanf (fh, "%lf", &top) == 1 && top > ytop)
1630+
hy += (top - ytop);
1631+
fclose (fh);
1632+
gmt_remove_file (GMT, file);
1633+
}
16041634
if ((k = gmt_set_psfilename (GMT)) == GMT_NOTSET) { /* Get hidden file name for PS */
16051635
GMT_Report (GMT->parent, GMT_MSG_ERROR, "No workflow directory\n");
16061636
Return (GMT_ERROR_ON_FOPEN);
@@ -1611,6 +1641,12 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) {
16111641
}
16121642
/* Must force PSL_plot_completion procedure to run, if it was set */
16131643
PSL_command (GMT->PSL, "PSL_plot_completion /PSL_plot_completion {} def\n"); /* Run once, then make it a null function */
1644+
if (have_heading) /* Remember where the panels left the origin, then put it back at the page corner so that
1645+
* the deferred heading below, whose position was saved in page coordinates, lands where it belongs.
1646+
* This is what -Xf does, but modern mode does not allow -Xf. The origin is restored once the heading
1647+
* has been placed, since anything following (e.g., a second subplot) shifts relative to it [#4450] */
1648+
PSL_command (GMT->PSL, "/PSL_SUBPLOT_x PSL_xorig def /PSL_SUBPLOT_y PSL_yorig def "
1649+
"PSL_SUBPLOT_ox PSL_xorig sub PSL_SUBPLOT_oy PSL_yorig sub TM\n");
16141650
if (PSL_fclose (GMT->PSL)) {
16151651
GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to close hidden PS file %s!\n", GMT->current.ps.filename);
16161652
Return (GMT_RUNTIME_ERROR);
@@ -1631,6 +1667,52 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) {
16311667
if (!access (file, F_OK)) gmt_remove_file (GMT, file);
16321668
}
16331669
}
1670+
if (have_heading) { /* Must call text to place the figure heading, using the position collected above.
1671+
* Note this has to happen here, after the subplot information files are gone: a plotting module
1672+
* called while they are still around is set up as a panel plot by gmt_init_module, which is also
1673+
* why the debug lines below are drawn at this point. */
1674+
uint64_t dim[4] = {1, 1, 1, 2}; /* A single record */
1675+
struct GMT_DATASET *T = NULL;
1676+
struct GMT_SUBPLOT P_save;
1677+
/* The panel settings must not make the text call below fit the heading into the last panel, but they
1678+
* are still needed afterwards (the -R history is built from P->dim), so put them back when done */
1679+
gmt_M_memcpy (&P_save, &GMT->current.plot.panel, 1, struct GMT_SUBPLOT);
1680+
gmt_M_memset (&GMT->current.plot.panel, 1, struct GMT_SUBPLOT);
1681+
if ((T = GMT_Create_Data (API, GMT_IS_DATASET, GMT_IS_NONE, GMT_WITH_STRINGS, dim, NULL, NULL, 0, 0, NULL)) == NULL) {
1682+
GMT_Report (API, GMT_MSG_ERROR, "Subplot: Unable to allocate a dataset\n");
1683+
Return (API->error);
1684+
}
1685+
T->table[0]->segment[0]->data[GMT_X][0] = hx; /* Centered */
1686+
T->table[0]->segment[0]->data[GMT_Y][0] = hy; /* On top */
1687+
T->table[0]->segment[0]->text[0] = strdup (heading);
1688+
T->n_records = T->table[0]->n_records = T->table[0]->segment[0]->n_rows = 1;
1689+
if (GMT_Open_VirtualFile (API, GMT_IS_DATASET, GMT_IS_NONE, GMT_IN|GMT_IS_REFERENCE, T, vfile) != GMT_NOERROR) {
1690+
Return (API->error);
1691+
}
1692+
snprintf (command, GMT_LEN256, "-R0/%g/0/%g -Jx1i -N -F+jBC+f%s %s -Xa0i -Ya0i --GMT_HISTORY=readonly",
1693+
Rw, Rh, gmt_putfont (GMT, &GMT->current.setting.font_heading), vfile);
1694+
GMT_Report (API, GMT_MSG_DEBUG, "Subplot command for text: %s\n", command);
1695+
if (GMT_Call_Module (API, "text", GMT_MODULE_CMD, command) != GMT_OK) /* Plot the heading */
1696+
Return (API->error);
1697+
if (GMT_Destroy_Data (API, &T) != GMT_OK)
1698+
Return (API->error);
1699+
/* Put the origin back where the panels had left it before we placed the heading */
1700+
if ((k = gmt_set_psfilename (GMT)) == GMT_NOTSET) {
1701+
GMT_Report (GMT->parent, GMT_MSG_ERROR, "No workflow directory\n");
1702+
Return (GMT_ERROR_ON_FOPEN);
1703+
}
1704+
if ((fp = PSL_fopen (GMT->PSL, GMT->current.ps.filename, wmode[k])) == NULL) {
1705+
GMT_Report (API, GMT_MSG_ERROR, "Cannot open %s with mode %s\n", GMT->current.ps.filename, wmode[k]);
1706+
Return (GMT_ERROR_ON_FOPEN);
1707+
}
1708+
PSL_command (GMT->PSL, "PSL_SUBPLOT_x PSL_xorig sub PSL_SUBPLOT_y PSL_yorig sub TM\n");
1709+
if (PSL_fclose (GMT->PSL)) {
1710+
GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to close hidden PS file %s!\n", GMT->current.ps.filename);
1711+
Return (GMT_RUNTIME_ERROR);
1712+
}
1713+
gmt_M_memcpy (&GMT->current.plot.panel, &P_save, 1, struct GMT_SUBPLOT); /* Restore for the code below */
1714+
}
1715+
16341716
/* Check if we should draw debug lines */
16351717
sprintf (file, "%s/gmt.subplotdebug.%d", API->gwf_dir, fig);
16361718
if (!access (file, R_OK)) { /* Yes, must draw debug lines on top */

test/subplot/subplot_3d_fit.sh

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env bash
2+
#
3+
# A 3-D panel must fit inside the subplot figure area. This is the example from issue #4450.
4+
#
5+
# subplot sizes and places its panels from the nominal 2-D map rectangle, but a perspective panel is
6+
# drawn as a projected box that is larger than that rectangle, and the box was never fitted to the
7+
# panel: both cubes burst out of the -Ff20c/10c figure area, and the heading landed among them.
8+
# gmt_map_setup now measures the projected footprint once the perspective is known and shrinks and
9+
# centers it inside the panel.
10+
#
11+
# No baseline PostScript is needed: we measure the ink of the two perspective panels straight out of
12+
# the PostScript and require it to fit the 10 cm height of the figure.
13+
14+
# This test measures the plot instead of comparing it, so it has no baseline PostScript. gmtest gives
15+
# any script that spells out the modern mode start command a $ps to compare, so we spell it via $start.
16+
start=begin
17+
18+
gmt $start fit3d ps
19+
gmt subplot begin 1x2 -Ff20c/10c+pblack+wblue -T"3D Subplots"
20+
gmt subplot set 0,0
21+
gmt basemap -BneSWZ+b -R0/10/20/30/40/50 -Jz1 -p157.7/45
22+
23+
gmt subplot set 0,1
24+
gmt basemap -BSEnwZ2+b -R0/10/20/30/40/50 -Jz1 -p157.7/45
25+
gmt subplot end
26+
gmt end
27+
28+
# Crop to the ink and read the height of what was actually drawn, in cm
29+
gmt psconvert -A -Te fit3d.ps
30+
height=$(awk '/^%%BoundingBox: / && $5 != "(atend)" {printf "%.2f\n", ($5 - $3) * 2.54 / 72; exit}' fit3d.eps)
31+
32+
rm -f fit3d.ps fit3d.eps # Measured; there is no baseline for them
33+
34+
# The figure is 10 cm tall and the heading adds about 1 cm above it. Panels that do not fit come out
35+
# far taller than that: the unfitted cubes of the report reach some 19 cm.
36+
echo "drawn height ${height} cm" > result.txt
37+
awk -v h="$height" 'BEGIN {
38+
if (h <= 0) { print "nothing was drawn"; exit 1 }
39+
if (h > 13.0) { printf "the figure is %.2f cm tall, so the 3-D panels do not fit its 10 cm height\n", h; exit 1 }
40+
print "fits"
41+
}' > verdict.txt
42+
43+
echo "fits" > answer.txt
44+
diff -q --strip-trailing-cr answer.txt verdict.txt

0 commit comments

Comments
 (0)