diff --git a/doc/rst/source/text.rst b/doc/rst/source/text.rst index df2b4798af4..a8b14b4007a 100644 --- a/doc/rst/source/text.rst +++ b/doc/rst/source/text.rst @@ -138,7 +138,14 @@ Optional Arguments columns, one offset pair per text record, in the same order as the input data. Values are in the current length unit [:term:`PROJ_LENGTH_UNIT`]. - **+v** - Draw a line from the original point to the shifted point; - append a *pen* to change the attributes for this line. **Note**: + append a *pen* to change the attributes for this line. To place an arrow + head at the original point, add the pen modifier **+vb**\ *vspecs* (e.g., + **-D**\ 1c+v1p,red+vb0.3c+gred); a head at the text end is not supported. + Add the pen modifier **+o**\ *offset*, given *before* **+vb**\ (e.g., **-D**\ 1c+v1p,red+o0.1c+vb0.3c+gred), + to stop the line, and its head, that distance short of the point, e.g., to clear a symbol drawn there; + the offset is as for :ref:`line attributes ` + in :doc:`plot`. Only the data-point end is affected. + See :ref:`Technical reference - Drawing of Vectors`. **Note**: The **-Dj**\|\ **J** selection cannot be used with paragraph mode (|-M|). .. _-F: diff --git a/src/gmt_plot.c b/src/gmt_plot.c index 57894002083..74fe578faf0 100644 --- a/src/gmt_plot.c +++ b/src/gmt_plot.c @@ -58,6 +58,7 @@ * gmt_map_text * gmt_map_title * gmt_plane_perspective + * gmt_plot_end_vectors * gmt_plot_geo_ellipse * gmt_plot_grid_graticules * gmt_plot_line @@ -6186,6 +6187,52 @@ GMT_LOCAL bool gmtplot_these_are_duplicates (double x0, double y0, double x1, do return true; } +void gmt_plot_end_vectors (struct GMT_CTRL *GMT, double *x, double *y, uint64_t n, struct GMT_PEN *P) { + /* Maybe add vector heads. Here, x,y are in inches on the plot */ + unsigned int k, current[2] = {0,0}, next[2] = {1,0}; + double dim[PSL_MAX_DIMS], angle, s, c, L; + char *end[2] = {"start", "end"}; + if (n < 2) return; /* No line of mine */ + if (P->end[0].V == NULL && P->end[1].V == NULL) return; /* No arrow heads requested */ + current[1] = (unsigned int)n-1; next[1] = (unsigned int)n-2; + PSL_command (GMT->PSL, "V\n"); + GMT->PSL->current.linewidth = -1.0; /* Will be changed by next PSL_setlinewidth */ + gmt_M_memset (dim, PSL_MAX_DIMS, double); + for (k = 0; k < 2; k++) { + if (P->end[k].V == NULL) continue; + P->end[k].V->v.status |= PSL_VEC_LINE; /* Flag so head is not skipped due to "short" vector */ + /* Add vector heads to this end */ + PSL_comment (GMT->PSL, "Add vector head to %s of line\n", end[k]); + angle = d_atan2d (y[current[k]] - y[next[k]], x[current[k]] - x[next[k]]); + sincosd (angle, &s, &c); + L = (P->end[k].V->v.v_kind[1] == PSL_VEC_TERMINAL) ? 1e-3 : P->end[k].length; + P->end[k].V->v.v_width = (float)(P->end[k].V->v.pen.width * GMT->session.u2u[GMT_PT][GMT_INCH]); /* Set symbol pen width */ + dim[PSL_VEC_XTIP] = x[current[k]] + c * L; + dim[PSL_VEC_YTIP] = y[current[k]] + s * L; + dim[PSL_VEC_TAIL_WIDTH] = P->end[k].V->v.v_width; + dim[PSL_VEC_HEAD_LENGTH] = P->end[k].V->v.h_length; + dim[PSL_VEC_HEAD_WIDTH] = P->end[k].V->v.h_width; + dim[PSL_VEC_HEAD_SHAPE] = P->end[k].V->v.v_shape; + dim[PSL_VEC_STATUS] = (double)P->end[k].V->v.status; + dim[PSL_VEC_HEAD_TYPE_BEGIN] = (double)P->end[k].V->v.v_kind[0]; + dim[PSL_VEC_HEAD_TYPE_END] = (double)P->end[k].V->v.v_kind[1]; + dim[PSL_VEC_TRIM_BEGIN] = (double)P->end[k].V->v.v_trim[0]; + dim[PSL_VEC_TRIM_END] = (double)P->end[k].V->v.v_trim[1]; + if (P->end[k].V->v.status & PSL_VEC_OUTLINE2) { /* Gave specific head outline pen */ + PSL_defpen (GMT->PSL, "PSL_vecheadpen", P->end[k].V->v.pen.width, P->end[k].V->v.pen.style, P->end[k].V->v.pen.offset, P->end[k].V->v.pen.rgb); + dim[PSL_VEC_HEAD_PENWIDTH] = P->end[k].V->v.pen.width; + } + else { /* Set default based on line pen */ + PSL_defpen (GMT->PSL, "PSL_vecheadpen", 0.5 * P->width, P->style, P->offset, P->rgb); + dim[PSL_VEC_HEAD_PENWIDTH] = 0.5 * P->width; + } + gmt_setfill (GMT, &P->end[k].V->v.fill, (P->end[k].V->v.status & PSL_VEC_OUTLINE2) == PSL_VEC_OUTLINE2); + PSL_plotsymbol (GMT->PSL, x[current[k]], y[current[k]], dim, PSL_VECTOR); + } + GMT->PSL->current.linewidth = -1.0; /* Will be changed by next PSL_setlinewidth */ + PSL_command (GMT->PSL, "U\n"); +} + void gmt_plot_line (struct GMT_CTRL *GMT, double *x, double *y, unsigned int *pen, uint64_t n, unsigned int mode) { /* Mode = PSL_LINEAR [0] or PSL_BEZIER [1] */ /* Drawing lines of finite thickness that cross map boundaries is tricky because the exit or entry point diff --git a/src/gmt_prototypes.h b/src/gmt_prototypes.h index e8139237fe2..d485f218fb4 100644 --- a/src/gmt_prototypes.h +++ b/src/gmt_prototypes.h @@ -305,6 +305,7 @@ EXTERN_MSC void gmt_set_basemap_orders (struct GMT_CTRL *GMT, unsigned int frame EXTERN_MSC void gmt_map_clip_off (struct GMT_CTRL *GMT); EXTERN_MSC void gmt_map_clip_on (struct GMT_CTRL *GMT, double rgb[], unsigned int flag); EXTERN_MSC void gmt_BB_clip_on (struct GMT_CTRL *GMT, double rgb[], unsigned int flag); +EXTERN_MSC void gmt_plot_end_vectors (struct GMT_CTRL *GMT, double *x, double *y, uint64_t n, struct GMT_PEN *P); EXTERN_MSC void gmt_plot_line (struct GMT_CTRL *GMT, double *x, double *y, unsigned int *pen, uint64_t n, unsigned int mode); EXTERN_MSC void gmt_setpen (struct GMT_CTRL *GMT, struct GMT_PEN *pen); EXTERN_MSC void gmt_setfill (struct GMT_CTRL *GMT, struct GMT_FILL *fill, int outline); diff --git a/src/pstext.c b/src/pstext.c index df595215956..9a0efc2ef54 100644 --- a/src/pstext.c +++ b/src/pstext.c @@ -145,6 +145,7 @@ static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new /* Initialize values whose defaults are not 0/false/NULL */ C->D.pen = C->W.pen = GMT->current.setting.map_default_pen; + gmt_M_memset (C->D.pen.end, 2, struct GMT_LINE_END); /* Leader ignores the default pen trims and heads (and must not free its heads) */ C->C.dx = C->C.dy = GMT_TEXT_CLEARANCE; /* 15% of font size is default clearance */ C->C.percent = true; C->C.mode = 'o'; /* Rectangular box shape */ @@ -163,10 +164,51 @@ static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new static void Free_Ctrl (struct GMT_CTRL *GMT, struct PSTEXT_CTRL *C) { /* Deallocate control structure */ if (!C) return; gmt_M_str_free (C->D.file); + gmt_freepen (GMT, &C->D.pen); gmt_M_str_free (C->F.text); gmt_M_free (GMT, C); } +#define PSTEXT_LEADER_NP 101 /* Number of vertices used to trim the leader */ + +GMT_LOCAL bool pstext_trim_leader (struct GMT_CTRL *GMT, double *x, double *y, struct GMT_PEN *pen) { + /* Trim the data-point end of the leader (x[0],y[0]) -> (x[1],y[1]), in inches, with the pen's +o and +v via gmt_trim_line as plot does. + * The text end is never trimmed since the box clipping is relative to it. Returns true if nothing is left to draw. */ + uint64_t n = PSTEXT_LEADER_NP; + double *xx = gmt_M_memory (GMT, NULL, n, double), *yy = gmt_M_memory (GMT, NULL, n, double), xe, ye; + struct GMT_PEN P = *pen; /* Shallow copy so we can skip the text end */ + + P.end[1].offset = 0.0; + for (uint64_t k = 0; k < n; k++) { /* gmt_trim_line needs vertices to trim along, and treats reaching the last one as trimmed away */ + double f = (double)k / (double)(n - 1); + xx[k] = x[0] + f * (x[1] - x[0]); yy[k] = y[0] + f * (y[1] - y[0]); + } + for (uint64_t k = 0; k < n; k++) { /* gmt_trim_line works in input coordinates */ + gmt_xy_to_geo (GMT, &xx[k], &yy[k], xx[k], yy[k]); + if (gmt_M_is_dnan (xx[k]) || gmt_M_is_dnan (yy[k])) { /* Not invertible (e.g., corner outside the map domain): draw untrimmed */ + gmt_M_free (GMT, xx); gmt_M_free (GMT, yy); + return false; + } + } + if (gmt_trim_line (GMT, &xx, &yy, &n, &P)) { /* Trimmed away completely */ + gmt_M_free (GMT, xx); gmt_M_free (GMT, yy); + return true; + } + gmt_geo_to_xy (GMT, xx[0], yy[0], &xe, &ye); /* New start of the leader */ + x[0] = xe; y[0] = ye; + gmt_M_free (GMT, xx); gmt_M_free (GMT, yy); + return false; +} + +GMT_LOCAL void pstext_draw_leader (struct GMT_CTRL *GMT, struct PSTEXT_INFO *T, double *xx, double *yy, char *text, double offset[]) { + /* Draw leader line from the data point (xx[0],yy[0]) to the text at (xx[1],yy[1]), with optional +o trim and vector head at the data point */ + double x[2] = {xx[0], xx[1]}, y[2] = {yy[0], yy[1]}; + if (gmt_trim_requested (GMT, &T->vecpen) && pstext_trim_leader (GMT, x, y, &T->vecpen)) return; + gmt_setpen (GMT, &T->vecpen); + PSL_plotline_clipped_by_textbox (GMT->PSL, x[0], y[0], x[1], y[1], T->font.size, text, T->paragraph_angle, T->block_justify, offset); + gmt_plot_end_vectors (GMT, x, y, 2, &T->vecpen); +} + GMT_LOCAL void pstext_output_words (struct GMT_CTRL *GMT, struct PSL_CTRL *PSL, double x, double y, char *text, struct PSTEXT_INFO *T, struct PSTEXT_CTRL *Ctrl) { double offset[2]; @@ -186,8 +228,8 @@ GMT_LOCAL void pstext_output_words (struct GMT_CTRL *GMT, struct PSL_CTRL *PSL, PSL_setfont (PSL, T->font.id); if (T->boxflag & 32) { /* Need to draw a vector from (x,y) to the offset text */ - gmt_setpen (GMT, &(T->vecpen)); - PSL_plotsegment (PSL, x, y, x + T->x_offset, y + T->y_offset); + double xx[2] = {x, x + T->x_offset}, yy[2] = {y, y + T->y_offset}; + pstext_draw_leader (GMT, T, xx, yy, NULL, offset); } if (Ctrl->D.justify) /* Smart offset according to justification (from Dave Huang) */ gmt_smart_justify (GMT, T->block_justify, T->paragraph_angle, T->x_offset, T->y_offset, &x, &y, Ctrl->D.justify); @@ -207,7 +249,7 @@ GMT_LOCAL void pstext_output_words (struct GMT_CTRL *GMT, struct PSL_CTRL *PSL, } if (T->boxflag & 1) gmt_setpen (GMT, &(T->boxpen)); /* Change current pen */ if (T->boxflag & 2) fill = &(T->boxfill); /* Determine if fill or not */ - if (T->boxflag & 3) gmt_setfill (GMT, fill, T->boxflag & 1); /* Change current fill and/or outline */ + gmt_setfill (GMT, fill, T->boxflag & 1); /* Change current fill and/or outline (none if only a leader line) */ /* Compute text box, draw/fill it, and in the process store the text in the PS file for next command */ PSL_plotparagraphbox (PSL, x, y, T->font.size, text, T->paragraph_angle, T->block_justify, offset, mode); /* Passing NULL means we typeset using the last stored paragraph info */ @@ -357,7 +399,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) { "Use -Dj to move text origin away from point (direction determined by text's justification). " "Upper case -DJ will shorten diagonal shifts at corners by sqrt(2). Cannot be used with -M. Optional modifiers:"); GMT_Usage (API, 3, "+f: Read per-record offsets (dx dy) from (two columns in current distance units); overrides fixed /."); - GMT_Usage (API, 3, "+v: Draw line from text to original point; optionally append a [%s].", gmt_putpen (API->GMT, &API->GMT->current.setting.map_default_pen)); + GMT_Usage (API, 3, "+v: Draw line from text to original point; optionally append a [%s]. Add pen modifier +vb for a vector head at the point, and, before +vb, +o (as for plot -W) to stop the line short of it.", gmt_putpen (API->GMT, &API->GMT->current.setting.map_default_pen)); GMT_Usage (API, 1, "\n-F[+a[]][+c[]][+f[]][+h|l|r[]|+t|+z[]][+j[]]"); GMT_Usage (API, -2, "Specify values for text attributes that apply to all text records:"); GMT_Usage (API, 3, "+a Specify baseline for all text [0]."); @@ -482,6 +524,7 @@ static int parse (struct GMT_CTRL *GMT, struct PSTEXT_CTRL *Ctrl, struct GMT_OPT if (opt->arg[j] == 'v') { /* Want to draw a line from point to offset point */ Ctrl->D.line = true; n_errors += gmt_M_check_condition(GMT, opt->arg[j+1] && gmt_getpen (GMT, &opt->arg[j+1], &Ctrl->D.pen), "Option -D: Give pen after +v\n"); + n_errors += gmt_M_check_condition(GMT, Ctrl->D.pen.end[1].V != NULL, "Option -D: A vector head can only be placed at the data point end of the line; use pen modifier +vb\n"); if (opt->arg[j-1] == '+') /* New-style syntax */ opt->arg[j-1] = 0; else @@ -1006,10 +1049,8 @@ EXTERN_MSC int GMT_pstext (void *V_API, int mode, void *args) { offset[0] = T.x_space; offset[1] = T.y_space; } - if (T.boxflag & 32) { /* Draw line from original point to shifted location, clipped at textbox edge */ - gmt_setpen(GMT, &T.vecpen); - PSL_plotline_clipped_by_textbox(PSL, xx[0], yy[0], xx[1], yy[1], T.font.size, use_text, T.paragraph_angle, T.block_justify, offset); - } + if (T.boxflag & 32) /* Draw line from original point to shifted location, clipped at textbox edge */ + pstext_draw_leader (GMT, &T, xx, yy, use_text, offset); if (!Ctrl->G.mode && T.boxflag & 3) { /* Plot the box beneath the text */ if (Ctrl->S.active) { /* Lay down shaded box first */ PSL_setfill (PSL, Ctrl->S.fill.rgb, 0); /* shade color */ @@ -1485,10 +1526,8 @@ EXTERN_MSC int GMT_pstext (void *V_API, int mode, void *args) { offset[0] = T.x_space; offset[1] = T.y_space; } - if (T.boxflag & 32) { /* Draw line from original point to shifted location, clipped at textbox edge */ - gmt_setpen (GMT, &T.vecpen); - PSL_plotline_clipped_by_textbox(PSL, xx[0], yy[0], xx[1], yy[1], T.font.size, use_text, T.paragraph_angle, T.block_justify, offset); - } + if (T.boxflag & 32) /* Draw line from original point to shifted location, clipped at textbox edge */ + pstext_draw_leader (GMT, &T, xx, yy, use_text, offset); if (!Ctrl->G.mode && T.boxflag & 3) { /* Plot the box beneath the text */ if (Ctrl->S.active) { /* Lay down shaded box first */ PSL_setfill (PSL, Ctrl->S.fill.rgb, 0); /* shade color */ diff --git a/src/psxy.c b/src/psxy.c index 9da764bda2e..058b30eed1c 100644 --- a/src/psxy.c +++ b/src/psxy.c @@ -470,52 +470,6 @@ GMT_LOCAL int psxy_plot_decorations (struct GMT_CTRL *GMT, struct GMT_DATASET *D return GMT_NOERROR; } -GMT_LOCAL void psxy_plot_end_vectors (struct GMT_CTRL *GMT, double *x, double *y, uint64_t n, struct GMT_PEN *P) { - /* Maybe add vector heads. Here, x,y are in inches on the plot */ - unsigned int k, current[2] = {0,0}, next[2] = {1,0}; - double dim[PSL_MAX_DIMS], angle, s, c, L; - char *end[2] = {"start", "end"}; - if (n < 2) return; /* No line of mine */ - if (P->end[0].V == NULL && P->end[1].V == NULL) return; /* No arrow heads requested */ - current[1] = (unsigned int)n-1; next[1] = (unsigned int)n-2; - PSL_command (GMT->PSL, "V\n"); - GMT->PSL->current.linewidth = -1.0; /* Will be changed by next PSL_setlinewidth */ - gmt_M_memset (dim, PSL_MAX_DIMS, double); - for (k = 0; k < 2; k++) { - if (P->end[k].V == NULL) continue; - P->end[k].V->v.status |= PSL_VEC_LINE; /* Flag so head is not skipped due to "short" vector */ - /* Add vector heads to this end */ - PSL_comment (GMT->PSL, "Add vector head to %s of line\n", end[k]); - angle = d_atan2d (y[current[k]] - y[next[k]], x[current[k]] - x[next[k]]); - sincosd (angle, &s, &c); - L = (P->end[k].V->v.v_kind[1] == PSL_VEC_TERMINAL) ? 1e-3 : P->end[k].length; - P->end[k].V->v.v_width = (float)(P->end[k].V->v.pen.width * GMT->session.u2u[GMT_PT][GMT_INCH]); /* Set symbol pen width */ - dim[PSL_VEC_XTIP] = x[current[k]] + c * L; - dim[PSL_VEC_YTIP] = y[current[k]] + s * L; - dim[PSL_VEC_TAIL_WIDTH] = P->end[k].V->v.v_width; - dim[PSL_VEC_HEAD_LENGTH] = P->end[k].V->v.h_length; - dim[PSL_VEC_HEAD_WIDTH] = P->end[k].V->v.h_width; - dim[PSL_VEC_HEAD_SHAPE] = P->end[k].V->v.v_shape; - dim[PSL_VEC_STATUS] = (double)P->end[k].V->v.status; - dim[PSL_VEC_HEAD_TYPE_BEGIN] = (double)P->end[k].V->v.v_kind[0]; - dim[PSL_VEC_HEAD_TYPE_END] = (double)P->end[k].V->v.v_kind[1]; - dim[PSL_VEC_TRIM_BEGIN] = (double)P->end[k].V->v.v_trim[0]; - dim[PSL_VEC_TRIM_END] = (double)P->end[k].V->v.v_trim[1]; - if (P->end[k].V->v.status & PSL_VEC_OUTLINE2) { /* Gave specific head outline pen */ - PSL_defpen (GMT->PSL, "PSL_vecheadpen", P->end[k].V->v.pen.width, P->end[k].V->v.pen.style, P->end[k].V->v.pen.offset, P->end[k].V->v.pen.rgb); - dim[PSL_VEC_HEAD_PENWIDTH] = P->end[k].V->v.pen.width; - } - else { /* Set default based on line pen */ - PSL_defpen (GMT->PSL, "PSL_vecheadpen", 0.5 * P->width, P->style, P->offset, P->rgb); - dim[PSL_VEC_HEAD_PENWIDTH] = 0.5 * P->width; - } - gmt_setfill (GMT, &P->end[k].V->v.fill, (P->end[k].V->v.status & PSL_VEC_OUTLINE2) == PSL_VEC_OUTLINE2); - PSL_plotsymbol (GMT->PSL, x[current[k]], y[current[k]], dim, PSL_VECTOR); - } - GMT->PSL->current.linewidth = -1.0; /* Will be changed by next PSL_setlinewidth */ - PSL_command (GMT->PSL, "U\n"); -} - GMT_LOCAL bool psxy_is_stroke_symbol (int symbol) { /* Return true if cross, x, y, - symbols */ if (symbol == PSL_CROSS) return true; @@ -3260,7 +3214,7 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { else if (draw_line) { if ((GMT->current.plot.n = gmt_geo_to_xy_line (GMT, L->data[GMT_X], L->data[GMT_Y], L->n_rows)) == 0) continue; if (S.symbol != GMT_SYMBOL_FRONT || !S.f.invisible) gmt_plot_line (GMT, GMT->current.plot.x, GMT->current.plot.y, GMT->current.plot.pen, GMT->current.plot.n, current_pen.mode); - psxy_plot_end_vectors (GMT, GMT->current.plot.x, GMT->current.plot.y, GMT->current.plot.n, ¤t_pen); /* Maybe add vector heads */ + gmt_plot_end_vectors (GMT, GMT->current.plot.x, GMT->current.plot.y, GMT->current.plot.n, ¤t_pen); /* Maybe add vector heads */ } } if (S.symbol == GMT_SYMBOL_FRONT) { /* Must also draw fault crossbars */