diff --git a/src/gmt_init.c b/src/gmt_init.c index 9c79e6f079c..203387270d0 100644 --- a/src/gmt_init.c +++ b/src/gmt_init.c @@ -14719,6 +14719,8 @@ struct GMT_SUBPLOT *gmt_subplot_info (struct GMTAPI_CTRL *API, int fig) { P->parallel = atoi (&line[12]); else if (!strncmp (line, "# INSIDE:", 9U)) P->inside = atoi (&line[10]); + else if (!strncmp (line, "# FIXEDFIG:", 11U)) + P->fixed_figure = atoi (&line[12]); else if (!strncmp (line, "# DIRECTION:", 12U)) sscanf (&line[13], "%d %d", &P->dir[GMT_X], &P->dir[GMT_Y]); else if (!strncmp (line, "# GAPS:", 7U)) diff --git a/src/gmt_map.c b/src/gmt_map.c index 608e5327b44..6145f68f8d5 100644 --- a/src/gmt_map.c +++ b/src/gmt_map.c @@ -6647,6 +6647,61 @@ GMT_LOCAL int gmtmap_init_three_D (struct GMT_CTRL *GMT) { GMT->current.proj.z_project.ymin += GMT->current.proj.z_project.y_off; GMT->current.proj.z_project.ymax += GMT->current.proj.z_project.y_off; + if (GMT->current.proj.three_D && GMT->current.proj.zmax > GMT->current.proj.zmin && + GMT->current.plot.panel.active && GMT->current.plot.panel.fixed_figure && GMT->current.plot.panel.no_scaling == 0) { + /* We are in a subplot panel. gmtmap_setxy could only fit the nominal 2-D map rectangle into the panel, + * since the perspective box is not known until here, and that box is the larger of the two - so the + * panels stuck out of the figure [issue #4450]. Measure the box the frame will actually occupy from + * the eight corners of the region, shrink the projection until it fits the panel, and center it there. + * All projected coordinates are linear in the plot scales, so the measured box scales with them. */ + struct GMT_SUBPLOT *P = &(GMT->current.plot.panel); + double bx[2] = {DBL_MAX, -DBL_MAX}, by[2] = {DBL_MAX, -DBL_MAX}, bw, bh, f, xc, yc, pad; + unsigned int ix, iy, iz; + for (ix = 0; ix < 2; ix++) for (iy = 0; iy < 2; iy++) for (iz = 0; iz < 2; iz++) { + 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]), + gmt_z_to_zz (GMT, GMT->common.R.wesn[ZLO+iz]), &xc, &yc); + bx[0] = MIN (bx[0], xc); bx[1] = MAX (bx[1], xc); + by[0] = MIN (by[0], yc); by[1] = MAX (by[1], yc); + } + if ((bx[1] - bx[0]) <= P->w && (by[1] - by[0]) <= P->h) + return (GMT_NOERROR); /* The box already fits the panel, so leave this plot exactly as it was */ + + /* It does not fit. The frame is annotated outside the box, so when we shrink it we must also leave room + * for a tick, the annotation offset and the annotation itself, or the annotations hang out of the panel */ + pad = GMT->current.setting.map_tick_length[GMT_PRIMARY] + GMT->current.setting.map_annot_offset[GMT_PRIMARY] + + 2.0 * GMT->current.setting.font_annot[GMT_PRIMARY].size * GMT->session.u2u[GMT_PT][GMT_INCH]; + bx[0] -= pad; bx[1] += pad; by[0] -= pad; by[1] += 0.25 * pad; /* Nothing is annotated above the box */ + bw = bx[1] - bx[0]; bh = by[1] - by[0]; + f = (bw > 0.0 && bh > 0.0) ? MIN (P->w / bw, P->h / bh) : 1.0; + if (f < 1.0) { /* Too big for the panel, so scale the projection down and center what is left. + * A panel that already fits is left completely alone, so such plots are unchanged. */ + GMT->current.proj.scale[GMT_X] *= f; GMT->current.proj.scale[GMT_Y] *= f; GMT->current.proj.scale[GMT_Z] *= f; + GMT->current.proj.i_scale[GMT_X] /= f; GMT->current.proj.i_scale[GMT_Y] /= f; GMT->current.proj.i_scale[GMT_Z] /= f; + GMT->current.proj.w_r *= f; + GMT->current.proj.rect[XHI] *= f; GMT->current.proj.rect[YHI] *= f; + GMT->current.proj.origin[GMT_X] *= f; GMT->current.proj.origin[GMT_Y] *= f; GMT->current.proj.origin[GMT_Z] *= f; + GMT->current.proj.zmax *= f; GMT->current.proj.zmin *= f; /* The z axis length is in plot units too */ + GMT->current.proj.z_project.x_off *= f; GMT->current.proj.z_project.y_off *= f; + GMT->current.proj.z_project.xmin *= f; GMT->current.proj.z_project.xmax *= f; + GMT->current.proj.z_project.ymin *= f; GMT->current.proj.z_project.ymax *= f; + GMT->current.map.width *= f; GMT->current.map.height *= f; + GMT->current.map.half_width = 0.5 * GMT->current.map.width; + GMT->current.map.half_height = 0.5 * GMT->current.map.height; + bx[0] *= f; bx[1] *= f; by[0] *= f; by[1] *= f; + bw *= f; bh *= f; + /* Shift the projection so the box sits centered inside the panel */ + GMT->current.proj.z_project.x_off += 0.5 * (P->w - bw) - bx[0]; + GMT->current.proj.z_project.y_off += 0.5 * (P->h - bh) - by[0]; + GMT->current.proj.z_project.xmin += 0.5 * (P->w - bw) - bx[0]; + GMT->current.proj.z_project.xmax += 0.5 * (P->w - bw) - bx[0]; + GMT->current.proj.z_project.ymin += 0.5 * (P->h - bh) - by[0]; + GMT->current.proj.z_project.ymax += 0.5 * (P->h - bh) - by[0]; + P->dx = P->dy = 0.0; /* The centering is in the projection now */ + 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", + f, bw, bh, P->w, P->h); + } + } + return (GMT_NOERROR); } diff --git a/src/gmt_plot.c b/src/gmt_plot.c index a5c7624e86e..8e7a7cbb68a 100644 --- a/src/gmt_plot.c +++ b/src/gmt_plot.c @@ -9277,6 +9277,25 @@ struct PSL_CTRL *gmt_plotinit (struct GMT_CTRL *GMT, struct GMT_OPTION *options) /* Consider offsets required to center the plot on the subplot panel [0/0] */ GMT->current.setting.map_origin[GMT_X] += (P->dx + P->gap[XLO]); GMT->current.setting.map_origin[GMT_Y] += (P->dy + P->gap[YLO]); + if (GMT->current.proj.three_D) { + /* A subplot heading is laid out in figure coordinates, while a perspective panel may extend above its + * nominal 2-D rectangle. Record the highest projected y reached by any 3-D panel so that subplot end + * can place the figure heading above the real perspective footprint (issue #4450). */ + int fig = gmt_get_current_figure (GMT->parent); + char file[PATH_MAX] = {""}; + double old_top = -DBL_MAX; + double top = GMT->current.setting.map_origin[GMT_Y] + GMT->current.proj.z_project.ymax; + FILE *fp_top = NULL; + snprintf (file, PATH_MAX, "%s/gmt.subplottop.%d", GMT->parent->gwf_dir, fig); + if ((fp_top = fopen (file, "r")) != NULL) { + if (fscanf (fp_top, "%lf", &old_top) != 1) old_top = -DBL_MAX; + fclose (fp_top); + } + if (top > old_top && (fp_top = fopen (file, "w")) != NULL) { + fprintf (fp_top, "%.16g\n", top); + fclose (fp_top); + } + } if (P->first && O_active) /* Run completion script, if any */ PSL_setexec (PSL, 1); } diff --git a/src/gmt_types.h b/src/gmt_types.h index f3fffecab49..869cf07443a 100644 --- a/src/gmt_types.h +++ b/src/gmt_types.h @@ -183,6 +183,7 @@ struct GMT_SUBPLOT { unsigned int no_scaling; /* 1 when we are plotting a scale, bar, etc and not map and don't want to auto-scale plot */ unsigned int parallel; /* 1 for axis-parallel annotations [0 for standard] */ unsigned int inside; /* 1 if all annots/ticks are inside panels [0 for outside] */ + unsigned int fixed_figure; /* 1 if subplot -Ff gave the figure dimensions, so contents must fit inside them */ int row, col; /* Current panel position e.g., 0,0 */ int nrows, ncolumns; /* Panel arrangement for subplot window */ int dir[2]; /* Cartesian axis direction: +1 or -1 [1/1] */ diff --git a/src/subplot.c b/src/subplot.c index 1f31eac87e1..bdbf58e1d85 100644 --- a/src/subplot.c +++ b/src/subplot.c @@ -1338,15 +1338,16 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) { GMT_Report (API, GMT_MSG_ERROR, "Cannot create file %s\n", file); Return (GMT_ERROR_ON_FOPEN); } - fprintf (fp, "# subplot information file\n"); - cmd = GMT_Create_Cmd (API, options); - fprintf (fp, "# Command: %s %s\n", THIS_MODULE_CLASSIC_NAME, cmd); - gmt_M_free (GMT, cmd); - if (Ctrl->T.active) fprintf (fp, "# HEADING: %g %g %s\n", 0.5 * width, y_heading, Ctrl->T.title); - fprintf (fp, "# ORIGIN: %g %g\n", off[GMT_X], off[GMT_Y]); - fprintf (fp, "# DIMENSION: %g %g\n", width, height); - fprintf (fp, "# PARALLEL: %d\n", Ctrl->S[GMT_Y].parallel); - fprintf (fp, "# INSIDE: %d\n", (GMT->current.setting.map_frame_type == GMT_IS_INSIDE) ? 1 : 0); + fprintf(fp, "# subplot information file\n"); + cmd = GMT_Create_Cmd(API, options); + fprintf(fp, "# Command: %s %s\n", THIS_MODULE_CLASSIC_NAME, cmd); + gmt_M_free(GMT, cmd); + if (Ctrl->T.active) fprintf(fp, "# HEADING: %g %g %s\n", 0.5 * width, y_heading, Ctrl->T.title); + fprintf(fp, "# ORIGIN: %g %g\n", off[GMT_X], off[GMT_Y]); + fprintf(fp, "# DIMENSION: %g %g\n", width, height); + fprintf(fp, "# PARALLEL: %d\n", Ctrl->S[GMT_Y].parallel); + fprintf(fp, "# INSIDE: %d\n", (GMT->current.setting.map_frame_type == GMT_IS_INSIDE) ? 1 : 0); + fprintf(fp, "# FIXEDFIG: %d\n", (Ctrl->F.mode == SUBPLOT_FIGURE) ? 1 : 0); if (Ctrl->C.active) { /* Got common gaps setting */ gmt_M_memcpy (GMT->current.plot.panel.gap, Ctrl->C.gap, 4, double); fprintf (fp, "# GAPS: %g %g %g %g\n", Ctrl->C.gap[XLO], Ctrl->C.gap[XHI], Ctrl->C.gap[YLO], Ctrl->C.gap[YHI]); @@ -1435,7 +1436,8 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) { } } - /* Start the subplot with a blank canvas and place the optional title. + /* Start the subplot with a blank canvas. The optional figure heading is deferred until subplot end + so that the perspective extent of any 3-D panel is known when it is placed [issue #4450]. The blank canvas dimensions should become the -R and -Jx1 once subplot ends */ if (Ctrl->F.fill[0] != '-' && Ctrl->F.pen[0] != '-') /* Need to fill and draw the canvas box */ @@ -1448,36 +1450,41 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) { width += 2.0 * Ctrl->F.clearance[GMT_X]; height += 2.0 * Ctrl->F.clearance[GMT_Y]; - if (Ctrl->T.title) { /* Must call text to place a heading */ - uint64_t dim[4] = {1, 1, 1, 2}; /* A single record */ - struct GMT_DATASET *T = NULL; - if ((T = GMT_Create_Data (API, GMT_IS_DATASET, GMT_IS_NONE, GMT_WITH_STRINGS, dim, NULL, NULL, 0, 0, NULL)) == NULL) { - GMT_Report (API, GMT_MSG_ERROR, "Subplot: Unable to allocate a dataset\n"); - Return (error); + /* plot is required, since nothing is plotted here (except for possibly the canvas fill/outline) */ + sprintf (command, "-R0/%g/0/%g -Jx1i -T%s --GMT_HISTORY=readonly", width, height, origin_shift); + if (Bopt[0]) strcat (command, Bopt); /* The -B was set above, so include it in the command */ + GMT_Report (API, GMT_MSG_DEBUG, "Subplot command for plot: %s\n", command); + if (GMT_Call_Module (API, "plot", GMT_MODULE_CMD, command) != GMT_OK) /* Plot the canvas */ + Return (API->error); + if (Ctrl->T.title) { /* Save exactly where and how the heading is to be plotted by subplot end [issue #4450]. + * We must place it there and not here since a 3-D panel may reach above the nominal figure top, which + * is only known once the panels have been drawn. The position is the one the heading would have been + * given here, i.e. in the canvas frame; subplot end returns the origin to this canvas corner, which we + * remember in the PostScript itself so that no assumption about -X -Y is needed. */ + FILE *fpt = NULL; + sprintf (file, "%s/gmt.subplotheading.%d", API->gwf_dir, fig); + if ((fpt = fopen (file, "w")) == NULL) { + GMT_Report (API, GMT_MSG_ERROR, "Cannot create subplot heading file %s\n", file); + Return (GMT_ERROR_ON_FOPEN); } - T->table[0]->segment[0]->data[GMT_X][0] = 0.5 * width; /* Centered */ - T->table[0]->segment[0]->data[GMT_Y][0] = y_heading + Ctrl->F.clearance[GMT_Y]; /* On top */ - T->table[0]->segment[0]->text[0] = strdup (Ctrl->T.title); - T->table[0]->segment[0]->n_rows = 1; - T->n_records = T->table[0]->n_records = T->table[0]->segment[0]->n_rows = 1; - if (GMT_Open_VirtualFile (API, GMT_IS_DATASET, GMT_IS_NONE, GMT_IN|GMT_IS_REFERENCE, T, vfile) != GMT_NOERROR) { - Return (API->error); + /* Record: , all in canvas inches */ + fprintf (fpt, "%.16g %.16g %.16g %.16g %.16g %s\n", 0.5 * width, y_heading + Ctrl->F.clearance[GMT_Y], + width, height, height, Ctrl->T.title); + fclose (fpt); + /* Remember this canvas corner in the PostScript so subplot end can come back to it */ + if (gmt_set_psfilename (GMT) == GMT_NOTSET) { + GMT_Report (GMT->parent, GMT_MSG_ERROR, "No workflow directory\n"); + Return (GMT_ERROR_ON_FOPEN); + } + if ((fp = PSL_fopen (GMT->PSL, GMT->current.ps.filename, "a")) == NULL) { /* The canvas was just drawn, so append */ + GMT_Report (API, GMT_MSG_ERROR, "Cannot open %s to append\n", GMT->current.ps.filename); + Return (GMT_ERROR_ON_FOPEN); + } + PSL_command (GMT->PSL, "/PSL_SUBPLOT_ox PSL_xorig def /PSL_SUBPLOT_oy PSL_yorig def\n"); + if (PSL_fclose (GMT->PSL)) { + GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to close hidden PS file %s!\n", GMT->current.ps.filename); + Return (GMT_RUNTIME_ERROR); } - sprintf (command, "-R0/%g/0/%g -Jx1i -N -F+jBC+f%s %s%s --GMT_HISTORY=readonly", - width, height, gmt_putfont (GMT, &GMT->current.setting.font_heading), vfile, origin_shift); - if (Bopt[0] == ' ') strcat (command, Bopt); /* The -B was set above, so include it in the command */ - GMT_Report (API, GMT_MSG_DEBUG, "Subplot command for text: %s\n", command); - if (GMT_Call_Module (API, "text", GMT_MODULE_CMD, command) != GMT_OK) /* Plot the canvas with heading */ - Return (API->error); - if (GMT_Destroy_Data (API, &T) != GMT_OK) - Return (API->error); - } - else { /* plot is required, since nothing is plotted (except for possibly the canvas fill/outline) */ - sprintf (command, "-R0/%g/0/%g -Jx1i -T%s --GMT_HISTORY=readonly", width, height, origin_shift); - if (Bopt[0]) strcat (command, Bopt); /* The -B was set above, so include it in the command */ - GMT_Report (API, GMT_MSG_DEBUG, "Subplot command for plot: %s\n", command); - if (GMT_Call_Module (API, "plot", GMT_MODULE_CMD, command) != GMT_OK) /* Plot the canvas with heading */ - Return (API->error); } if (fabs (Ctrl->F.clearance[GMT_X]) > 0.0 || fabs (Ctrl->F.clearance[GMT_Y]) > 0.0) { /* Must reset origin */ width -= 2.0 * Ctrl->F.clearance[GMT_X]; @@ -1578,8 +1585,11 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) { int k, id, row, col; char *wmode[2] = {"w","a"}, vfile[GMT_VF_LEN] = {""}, Rtxt[GMT_LEN64] = {""}, off[GMT_LEN32] = {""}; char legend_justification[4] = {""}, Jstr[3] = {"J"}, pen[GMT_LEN32] = {""}, fill[GMT_LEN32] = {""}; - double legend_width = 0.0, legend_scale = 1.0; - FILE *fp = NULL; + char line[GMT_BUFSIZ] = {""}, heading[GMT_BUFSIZ] = {""}; + 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; + int n_chars = 0; + bool have_heading = false; + FILE *fp = NULL, *fh = NULL; if ((P = gmt_subplot_info (API, fig)) == NULL) { GMT_Report (GMT->parent, GMT_MSG_ERROR, "No subplot information file!\n"); @@ -1601,6 +1611,27 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) { API->GMT->current.map.height = P->dim[GMT_Y]; P->active = 0; /* Ensure subplot mode is now terminated */ + /* Collect what is needed to place the figure heading now that every panel has had a chance to report its + * actual perspective footprint. subplot begin saved the exact position, region and text; the heading + * itself is plotted further down, once we are truly out of subplot mode. For 2-D figures no + * gmt.subplottop file exists so the placement is the same as it always was. */ + sprintf (file, "%s/gmt.subplotheading.%d", API->gwf_dir, fig); + if ((fh = fopen (file, "r")) != NULL) { + 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) { + strncpy (heading, &line[n_chars], GMT_BUFSIZ-1); + gmt_chop (heading); + have_heading = true; + } + fclose (fh); + gmt_remove_file (GMT, file); + } + sprintf (file, "%s/gmt.subplottop.%d", API->gwf_dir, fig); + if ((fh = fopen (file, "r")) != NULL) { /* At least one panel was 3-D, so the heading may need raising */ + if (fscanf (fh, "%lf", &top) == 1 && top > ytop) + hy += (top - ytop); + fclose (fh); + gmt_remove_file (GMT, file); + } if ((k = gmt_set_psfilename (GMT)) == GMT_NOTSET) { /* Get hidden file name for PS */ GMT_Report (GMT->parent, GMT_MSG_ERROR, "No workflow directory\n"); Return (GMT_ERROR_ON_FOPEN); @@ -1611,6 +1642,12 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) { } /* Must force PSL_plot_completion procedure to run, if it was set */ PSL_command (GMT->PSL, "PSL_plot_completion /PSL_plot_completion {} def\n"); /* Run once, then make it a null function */ + if (have_heading) /* Remember where the panels left the origin, then put it back at the page corner so that + * the deferred heading below, whose position was saved in page coordinates, lands where it belongs. + * This is what -Xf does, but modern mode does not allow -Xf. The origin is restored once the heading + * has been placed, since anything following (e.g., a second subplot) shifts relative to it [#4450] */ + PSL_command (GMT->PSL, "/PSL_SUBPLOT_x PSL_xorig def /PSL_SUBPLOT_y PSL_yorig def " + "PSL_SUBPLOT_ox PSL_xorig sub PSL_SUBPLOT_oy PSL_yorig sub TM\n"); if (PSL_fclose (GMT->PSL)) { GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to close hidden PS file %s!\n", GMT->current.ps.filename); Return (GMT_RUNTIME_ERROR); @@ -1631,6 +1668,52 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) { if (!access (file, F_OK)) gmt_remove_file (GMT, file); } } + if (have_heading) { /* Must call text to place the figure heading, using the position collected above. + * Note this has to happen here, after the subplot information files are gone: a plotting module + * called while they are still around is set up as a panel plot by gmt_init_module, which is also + * why the debug lines below are drawn at this point. */ + uint64_t dim[4] = {1, 1, 1, 2}; /* A single record */ + struct GMT_DATASET *T = NULL; + struct GMT_SUBPLOT P_save; + /* The panel settings must not make the text call below fit the heading into the last panel, but they + * are still needed afterwards (the -R history is built from P->dim), so put them back when done */ + gmt_M_memcpy (&P_save, &GMT->current.plot.panel, 1, struct GMT_SUBPLOT); + gmt_M_memset (&GMT->current.plot.panel, 1, struct GMT_SUBPLOT); + if ((T = GMT_Create_Data (API, GMT_IS_DATASET, GMT_IS_NONE, GMT_WITH_STRINGS, dim, NULL, NULL, 0, 0, NULL)) == NULL) { + GMT_Report (API, GMT_MSG_ERROR, "Subplot: Unable to allocate a dataset\n"); + Return (API->error); + } + T->table[0]->segment[0]->data[GMT_X][0] = hx; /* Centered */ + T->table[0]->segment[0]->data[GMT_Y][0] = hy; /* On top */ + T->table[0]->segment[0]->text[0] = strdup (heading); + T->n_records = T->table[0]->n_records = T->table[0]->segment[0]->n_rows = 1; + if (GMT_Open_VirtualFile (API, GMT_IS_DATASET, GMT_IS_NONE, GMT_IN|GMT_IS_REFERENCE, T, vfile) != GMT_NOERROR) { + Return (API->error); + } + snprintf (command, GMT_LEN256, "-R0/%g/0/%g -Jx1i -N -F+jBC+f%s %s -Xa0i -Ya0i --GMT_HISTORY=readonly", + Rw, Rh, gmt_putfont (GMT, &GMT->current.setting.font_heading), vfile); + GMT_Report (API, GMT_MSG_DEBUG, "Subplot command for text: %s\n", command); + if (GMT_Call_Module (API, "text", GMT_MODULE_CMD, command) != GMT_OK) /* Plot the heading */ + Return (API->error); + if (GMT_Destroy_Data (API, &T) != GMT_OK) + Return (API->error); + /* Put the origin back where the panels had left it before we placed the heading */ + if ((k = gmt_set_psfilename (GMT)) == GMT_NOTSET) { + GMT_Report (GMT->parent, GMT_MSG_ERROR, "No workflow directory\n"); + Return (GMT_ERROR_ON_FOPEN); + } + if ((fp = PSL_fopen (GMT->PSL, GMT->current.ps.filename, wmode[k])) == NULL) { + GMT_Report (API, GMT_MSG_ERROR, "Cannot open %s with mode %s\n", GMT->current.ps.filename, wmode[k]); + Return (GMT_ERROR_ON_FOPEN); + } + PSL_command (GMT->PSL, "PSL_SUBPLOT_x PSL_xorig sub PSL_SUBPLOT_y PSL_yorig sub TM\n"); + if (PSL_fclose (GMT->PSL)) { + GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to close hidden PS file %s!\n", GMT->current.ps.filename); + Return (GMT_RUNTIME_ERROR); + } + gmt_M_memcpy (&GMT->current.plot.panel, &P_save, 1, struct GMT_SUBPLOT); /* Restore for the code below */ + } + /* Check if we should draw debug lines */ sprintf (file, "%s/gmt.subplotdebug.%d", API->gwf_dir, fig); if (!access (file, R_OK)) { /* Yes, must draw debug lines on top */ diff --git a/test/subplot/subplot_3d_fit.sh b/test/subplot/subplot_3d_fit.sh new file mode 100644 index 00000000000..bda9351e0cf --- /dev/null +++ b/test/subplot/subplot_3d_fit.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# A 3-D panel must fit inside the subplot figure area. This is the example from issue #4450. +# +# subplot sizes and places its panels from the nominal 2-D map rectangle, but a perspective panel is +# drawn as a projected box that is larger than that rectangle, and the box was never fitted to the +# panel: both cubes burst out of the -Ff20c/10c figure area, and the heading landed among them. +# gmt_map_setup now measures the projected footprint once the perspective is known and shrinks and +# centers it inside the panel. +# +# No baseline PostScript is needed: we measure the ink of the two perspective panels straight out of +# the PostScript and require it to fit the 10 cm height of the figure. + +# This test measures the plot instead of comparing it, so it has no baseline PostScript. gmtest gives +# any script that spells out the modern mode start command a $ps to compare, so we spell it via $start. +start=begin + +gmt $start fit3d ps + gmt subplot begin 1x2 -Ff20c/10c+pblack+wblue -T"3D Subplots" + gmt subplot set 0,0 + gmt basemap -BneSWZ+b -R0/10/20/30/40/50 -Jz1 -p157.7/45 + + gmt subplot set 0,1 + gmt basemap -BSEnwZ2+b -R0/10/20/30/40/50 -Jz1 -p157.7/45 + gmt subplot end +gmt end + +# Crop to the ink and read the height of what was actually drawn, in cm +gmt psconvert -A -Te fit3d.ps +height=$(awk '/^%%BoundingBox: / && $5 != "(atend)" {printf "%.2f\n", ($5 - $3) * 2.54 / 72; exit}' fit3d.eps) + +rm -f fit3d.ps fit3d.eps # Measured; there is no baseline for them + +# The figure is 10 cm tall and the heading adds about 1 cm above it. Panels that do not fit come out +# far taller than that: the unfitted cubes of the report reach some 19 cm. +echo "drawn height ${height} cm" > result.txt +awk -v h="$height" 'BEGIN { + if (h <= 0) { print "nothing was drawn"; exit 1 } + 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 } + print "fits" +}' > verdict.txt + +echo "fits" > answer.txt +diff -q --strip-trailing-cr answer.txt verdict.txt