@@ -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 */
0 commit comments