Skip to content

Commit 65f2d39

Browse files
joa-quimseisman
andauthored
Add the so wished option to plot spheres in psxyz. (#8844)
* Add the so wished option to plot spheres in psxyz. They aren't really spheres but circles in 3D plots. This is mostly a Claude.ai creation but a one that needed a lot of baby-sitting development. * Change -SQ to -SP for sphere plots. Add a sphere.sh test * Add the baseline image sphere.ps to dvc * Update src/psxyz.c Co-authored-by: Dongdong Tian <seisman.info@gmail.com> * Fixes and updates to this test. * Make the -SP+e & +a work better. Oddly the PS seem right but conversion to raster flips spheres upside-down. --------- Co-authored-by: Dongdong Tian <seisman.info@gmail.com>
1 parent 749297b commit 65f2d39

File tree

11 files changed

+181
-6
lines changed

11 files changed

+181
-6
lines changed

doc/rst/source/explain_3D_symbols.rst_

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,17 @@
3131
**q** if *size* is a quantity in x-units [Default is plot-distance units].
3232
The facet colors will be modified to simulate shading.
3333
Use **-SU** to disable 3-D illumination.
34+
35+
**-SP**\ *size*\ [**c**\|\ **i**\|\ **p**][**+a**\ *azimuth*][**+e**\ *elevation*][**+f**][**+n**]
36+
Sphere (3-D) with diameter *size* [Default unit is **c** (cm)].
37+
The sphere is rendered with a radial gradient from white at the light source
38+
to the fill color (**-G**) at the opposite side, creating a 3-D appearance.
39+
Modifiers:
40+
41+
- **+a**\ *azimuth* - Set light source azimuth [default: 0, from the right].
42+
- **+e**\ *elevation* - Set light source elevation [default: 90, perpendicular to viewing plane].
43+
- **+f** - Use flat/constant fill color (no gradient shading).
44+
- **+n** - Draw outline only (no fill).
45+
46+
The outline pen is controlled by **-W**. If no fill color is specified via **-G**,
47+
the sphere defaults to black.

src/PSL_prologue.ps

1.27 KB
Binary file not shown.

src/PSL_strings.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,35 @@ static char *PSL_prologue_str =
10381038
"/Sp {N 3 -1 roll 0 360 arc fs N}!\n"
10391039
"% Patch fill: x1 y1 ... xn yn n\n"
10401040
"/SP {M {D} repeat FO}!\n"
1041+
"% Sphere with 3D shading: radius xc yc\n"
1042+
"/SPhere {/yc_SP edef /xc_SP edef /radius_SP edef\n"
1043+
" /SP_flat where {pop} {/SP_flat false def} ifelse\n"
1044+
" /SP_no_fill where {pop} {/SP_no_fill false def} ifelse\n"
1045+
" gsave\n"
1046+
" xc_SP yc_SP matrix currentmatrix transform /yc_SP_user edef /xc_SP_user edef\n"
1047+
" radius_SP 0 matrix currentmatrix dtransform dup mul exch dup mul add sqrt /radius_SP_user edef\n"
1048+
" /SP_lx where {pop /SP_lx_orig SP_lx def /SP_ly_orig SP_ly def} {/SP_lx_orig 0.0 def /SP_ly_orig 0.0 def} ifelse\n"
1049+
" radius_SP_user SP_lx_orig mul /SP_lx_user edef radius_SP_user SP_ly_orig mul /SP_ly_user edef\n"
1050+
" matrix setmatrix\n"
1051+
" SP_no_fill {\n"
1052+
" % No fill (outline only, drawn separately in C code)\n"
1053+
" } {\n"
1054+
" fc\n"
1055+
" SP_flat {\n"
1056+
" % Flat/constant color (no gradient)\n"
1057+
" xc_SP_user yc_SP_user T N 0 0 radius_SP_user 0 360 arc fill\n"
1058+
" } {\n"
1059+
" % 3D gradient shading\n"
1060+
" currentrgbcolor /B_SP edef /G_SP edef /R_SP edef\n"
1061+
" xc_SP_user yc_SP_user T N 0 0 radius_SP_user 0 360 arc\n"
1062+
" /Pattern setcolorspace\n"
1063+
" << /PatternType 2 /Shading << /ShadingType 3 /ColorSpace /DeviceRGB\n"
1064+
" /Coords [SP_lx_user SP_ly_user 0 0 0 radius_SP_user] /Function <<\n"
1065+
" /FunctionType 2 /Domain [0 1] /C0 [1 1 1] /C1 [R_SP G_SP B_SP] /N 1 >> >>\n"
1066+
" >> matrix makepattern setcolor fill\n"
1067+
" } ifelse\n"
1068+
" } ifelse\n"
1069+
" grestore}!\n"
10411070
"% Rectangle: height width xc yc\n"
10421071
"/Sr {M dup -2 div 2 index -2 div G dup 0 D exch 0 exch D neg 0 D FO}!\n"
10431072
"% Rounded rectangle: height width radius xc yc\n"

src/gmt_init.c

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17118,7 +17118,7 @@ int gmt_parse_symbol_option (struct GMT_CTRL *GMT, char *text, struct GMT_SYMBOL
1711817118
unsigned int ju, col;
1711917119
char symbol_type, txt_a[GMT_LEN256] = {""}, txt_b[GMT_LEN256] = {""}, txt_c[GMT_LEN256] = {""}, txt_d[GMT_LEN256] = {""};
1712017120
char text_cp[GMT_LEN256] = {""}, diameter[GMT_LEN32] = {""}, *c = NULL;
17121-
static char *allowed_symbols[2] = {"~=-+AaBbCcDdEefGgHhIiJjMmNnpqRrSsTtVvWwxy", "=-+AabCcDdEefGgHhIiJjMmNnOopqRrSsTtUuVvWwxy"};
17121+
static char *allowed_symbols[2] = {"~=-+AaBbCcDdEefGgHhIiJjMmNnpQqRrSsTtVvWwxy", "=-+AabCcDdEefGgHhIiJjMmNnOopQqRrSsTtUuVvWwxy"};
1712217122
static char *bar_symbols[2] = {"Bb", "-BbOoUu"};
1712317123
if (cmd) {
1712417124
p->base = GMT->session.d_NaN;
@@ -17213,6 +17213,28 @@ int gmt_parse_symbol_option (struct GMT_CTRL *GMT, char *text, struct GMT_SYMBOL
1721317213
}
1721417214
}
1721517215
}
17216+
else if (text[0] == 'P') { /* Sphere symbol with optional modifiers for light position, flat color, or no fill */
17217+
char arg[GMT_LEN64] = {""};
17218+
n = sscanf (text, "%c%[^+]", &symbol_type, arg); /* arg should be symbol size with no +<modifiers> at the end */
17219+
if (n == 1) { /* No modifiers or no size given */
17220+
if (text[1] && text[1] != '+') {
17221+
/* Gave size without modifiers */
17222+
strncpy (arg, &text[1], GMT_LEN64-1);
17223+
}
17224+
}
17225+
if (arg[0] && arg[0] != '+') { /* Need to get size */
17226+
if (cmd) p->read_size_cmd = false;
17227+
p->size_x = p->given_size_x = gmt_M_to_inch (GMT, arg);
17228+
check = false;
17229+
}
17230+
else if (!text[1] || text[1] == '+') { /* No size given */
17231+
if (p->size_x == 0.0) p->size_x = p->given_size_x;
17232+
if (p->size_y == 0.0) p->size_y = p->given_size_y;
17233+
if (p->size_x == 0.0)
17234+
col_off++;
17235+
if (cmd) p->read_size_cmd = true;
17236+
}
17237+
}
1721617238
else if (strchr (GMT_VECTOR_CODES, text[0])) {
1721717239
/* Vectors gets separate treatment because of optional modifiers [+j<just>+b+e+s+l+r+a<angle>+n<norm>] */
1721817240
int one;
@@ -17823,14 +17845,67 @@ int gmt_parse_symbol_option (struct GMT_CTRL *GMT, char *text, struct GMT_SYMBOL
1782317845
GMT_Report (GMT->parent, GMT_MSG_ERROR, "Option -S: Symbol type %c is 3-D only\n", symbol_type);
1782417846
}
1782517847
break;
17826-
case 'P':
1782717848
case 'p':
1782817849
p->symbol = PSL_DOT;
1782917850
if (p->size_x == 0.0 && !p->read_size) { /* User forgot to set size */
1783017851
p->size_x = GMT_DOT_SIZE;
1783117852
check = false;
1783217853
}
1783317854
break;
17855+
case 'P': /* Sphere symbol: -SP<size>[+a<azim>][+e<elev>][+f][+n] */
17856+
p->symbol = PSL_SPHERE;
17857+
/* Set default light position: center (perpendicular to viewing plane) */
17858+
p->SP_lx = 0.0;
17859+
p->SP_ly = 0.0;
17860+
p->SP_light_set = false;
17861+
p->SP_flat = false;
17862+
p->SP_no_fill = false;
17863+
/* Process +a, +e, +f, and +n modifiers */
17864+
{
17865+
double azimuth = 0.0, elevation = 90.0; /* Default values: centered light */
17866+
bool got_azim = false, got_elev = false;
17867+
char mod[GMT_LEN64] = {""};
17868+
unsigned int pos = 0, error = 0;
17869+
if ((c = strchr(text, '+'))) { /* Got modifiers */
17870+
while (gmt_getmodopt(GMT, 'S', c, "aefn", &pos, mod, &error) && error == 0) {
17871+
switch (mod[0]) {
17872+
case 'a': /* Azimuth */
17873+
if (mod[1]) {
17874+
azimuth = atof(&mod[1]);
17875+
got_azim = true;
17876+
}
17877+
else {
17878+
GMT_Report(GMT->parent, GMT_MSG_ERROR, "Option -SP: +a modifier requires azimuth value\n");
17879+
decode_error++;
17880+
}
17881+
break;
17882+
case 'e': /* Elevation */
17883+
if (mod[1]) {
17884+
elevation = atof(&mod[1]);
17885+
got_elev = true;
17886+
}
17887+
else {
17888+
GMT_Report(GMT->parent, GMT_MSG_ERROR, "Option -SP: +e modifier requires elevation value\n");
17889+
decode_error++;
17890+
}
17891+
break;
17892+
case 'f': /* Flat/constant color (no gradient) */
17893+
p->SP_flat = true;
17894+
break;
17895+
case 'n': /* No fill (outline only) */
17896+
p->SP_no_fill = true;
17897+
break;
17898+
}
17899+
}
17900+
if (got_azim || got_elev) { /* Store light azimuth/elevation for later projection */
17901+
/* Store the light direction - will be projected to 2D at drawing time */
17902+
p->SP_light_az = azimuth;
17903+
p->SP_light_el = elevation;
17904+
p->SP_light_set = true;
17905+
}
17906+
}
17907+
}
17908+
break;
1783417909
case 'q': /* Quoted lines: -Sq[d|n|l|s|x]<info>[:<labelinfo>] */
1783517910
p->symbol = GMT_SYMBOL_QUOTED_LINE;
1783617911
check = false;

src/gmt_plot.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,15 @@ struct GMT_SYMBOL {
190190
struct GMT_FRONTLINE f; /* parameters needed for a front */
191191
struct GMT_CUSTOM_SYMBOL *custom; /* pointer to a custom symbol */
192192

193+
/* These apply to sphere symbols */
194+
double SP_lx; /* Light source x position for sphere symbol [0.0] */
195+
double SP_ly; /* Light source y position for sphere symbol [0.0] */
196+
double SP_light_az; /* Light source azimuth for sphere symbol [0.0] */
197+
double SP_light_el; /* Light source elevation for sphere symbol [0.0] */
198+
bool SP_light_set; /* true if +a or +e modifier was used to set light position */
199+
bool SP_flat; /* true if +f modifier was used to disable gradient (flat/solid color) */
200+
bool SP_no_fill; /* true if +n modifier was used to draw outline only (no fill) */
201+
193202
struct GMT_CONTOUR G; /* For quoted lines */
194203
struct GMT_DECORATE D; /* For decorated lines */
195204
};

src/postscriptlight.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3995,6 +3995,10 @@ int PSL_plotsymbol (struct PSL_CTRL *PSL, double x, double y, double size[], int
39953995
PSL_command (PSL, "%d %d %d S%c\n", psl_iz (PSL, 0.5 * size[0]), psl_ix (PSL, x), psl_iy (PSL, y), (char)symbol);
39963996
break;
39973997

3998+
case PSL_SPHERE: /* Sphere */
3999+
PSL_command (PSL, "%d %d %d SPhere\n", psl_iz (PSL, 0.5 * size[0]), psl_ix (PSL, x), psl_iy (PSL, y));
4000+
break;
4001+
39984002
/* Multi-parameter fillable symbols */
39994003

40004004
case PSL_WEDGE: /* A wedge or pie-slice. size[0] = radius, size[1..2] = azimuth range of arc */

src/postscriptlight.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ extern "C" {
6969
#define PSL_MARC ((int)'m')
7070
#define PSL_PENTAGON ((int)'n')
7171
#define PSL_DOT ((int)'p')
72+
#define PSL_SPHERE ((int)'P')
7273
#define PSL_RECT ((int)'r')
7374
#define PSL_RNDRECT ((int)'R')
7475
#define PSL_SQUARE ((int)'s')

src/psxy.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) {
616616
GMT_Usage (API, 2, "\n%s Basic geometric symbol. Append one:", GMT_LINE_BULLET);
617617
GMT_Usage (API, -3, "-(xdash), +(plus), st(a)r, (b|B)ar, (c)ircle, (d)iamond, (e)llipse, "
618618
"(f)ront, octa(g)on, (h)exagon, (i)nvtriangle, (j)rotated rectangle, "
619-
"(k)ustom, (l)etter, (m)athangle, pe(n)tagon, (p)oint, (q)uoted line, (r)ectangle, "
619+
"(k)ustom, (l)etter, (m)athangle, pe(n)tagon, (p)oint, s(Q)here, (q)uoted line, (r)ectangle, "
620620
"(R)ounded rectangle, (s)quare, (t)riangle, (v)ector, (w)edge, (x)cross, (y)dash, or "
621621
"=(geovector, i.e., great or small circle vectors) or ~(decorated line).");
622622
GMT_Usage (API, -3, "If no size is specified, then the 3rd column must have sizes. "

src/psxyz.c

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) {
238238
GMT_Usage (API, -3, "-(xdash), +(plus), st(a)r, (b|B)ar, (c)ircle, (d)iamond, (e)llipse, "
239239
"(f)ront, octa(g)on, (h)exagon (i)nvtriangle, (j)rotated rectangle, "
240240
"(k)ustom, (l)etter, (m)athangle, pe(n)tagon, c(o)lumn, (p)oint, "
241-
"(q)uoted line, (r)ectangle, (R)ounded rectangle, (s)quare, (t)riangle, "
241+
"s(P)here, (q)uoted line, (r)ectangle, (R)ounded rectangle, (s)quare, (t)riangle, "
242242
"c(u)be, (v)ector, (w)edge, (x)cross, (y)dash, (z)dash, or "
243243
"=(geovector, i.e., great or small circle vectors).");
244244

@@ -276,6 +276,13 @@ static int usage (struct GMTAPI_CTRL *API, int level) {
276276
GMT_Usage (API, 2, "\n%s 3-D Cube: Give <size> as the length of all sides; append q if <size> "
277277
"is a quantity in x-units.", GMT_LINE_BULLET);
278278

279+
GMT_Usage (API, 2, "\n%s 3-D Sphere: Give <size> as sphere diameter [Default unit is cm]. "
280+
"Sphere is rendered with radial gradient shading from white at light source to fill color. Modifiers:", GMT_LINE_BULLET);
281+
GMT_Usage (API, 3, "+a Set light source azimuth [0, from the right].");
282+
GMT_Usage (API, 3, "+e Set light source elevation [90, perpendicular to viewing plane].");
283+
GMT_Usage (API, 3, "+f Use flat/constant fill color (no gradient shading).");
284+
GMT_Usage (API, 3, "+n Draw outline only (no fill). Outline color from -W.");
285+
279286
GMT_Usage (API, 2, "\n%s Ellipse: If not given, we read direction, major, and minor axis from columns 4-6. "
280287
"If -SE rather than -Se is selected, %s will expect azimuth, and "
281288
"axes [in km], and convert azimuths based on map projection. "
@@ -1048,6 +1055,10 @@ EXTERN_MSC int GMT_psxyz (void *V_API, int mode, void *args) {
10481055
fill_active = Ctrl->G.active; /* Make copies because we will change the values */
10491056
outline_active = Ctrl->W.active;
10501057
if (not_line && !outline_active && S.symbol != PSL_WEDGE && !fill_active && !get_rgb && !QR_symbol) outline_active = true; /* If no fill nor outline for symbols then turn outline on */
1058+
if (S.symbol == PSL_SPHERE && !fill_active && !S.SP_no_fill) { /* Sphere needs a default fill for 3D shading */
1059+
current_fill.rgb[0] = current_fill.rgb[1] = current_fill.rgb[2] = 0.5; /* Black */
1060+
fill_active = true;
1061+
}
10511062

10521063
if (Ctrl->D.active) {
10531064
/* Shift the plot a bit. This is a bit frustrating, since the only way to do this
@@ -1892,6 +1903,31 @@ EXTERN_MSC int GMT_psxyz (void *V_API, int mode, void *args) {
18921903
gmt_plane_perspective (GMT, GMT_Z, data[i].z);
18931904
PSL_plotsymbol (PSL, xpos[item], data[i].y, data[i].dim, data[i].symbol);
18941905
break;
1906+
case PSL_SPHERE: /* Case created by Claude.ai */
1907+
gmt_plane_perspective(GMT, GMT_Z, data[i].z);
1908+
if (S.SP_light_set) { /* Calculate and output custom light position for sphere */
1909+
/* Simple model: azimuth controls horizontal, elevation controls vertical */
1910+
/* Relative to viewing direction */
1911+
double dazim = GMT->current.proj.z_project.view_azimuth - S.SP_light_az;
1912+
if (dazim > 90) dazim = 90.0; /* Do let illum from the hidden hemisphere */
1913+
if (dazim < -90) dazim = -90.0;
1914+
double SP_lx_proj = sind(dazim);
1915+
double SP_ly_proj = sind(S.SP_light_el - GMT->current.proj.z_project.view_elevation);
1916+
PSL_command(PSL, "/SP_lx %.12g def /SP_ly %.12g def\n", SP_lx_proj, SP_ly_proj);
1917+
}
1918+
if (S.SP_flat) /* Output flat/constant color flag for sphere */
1919+
PSL_command(PSL, "/SP_flat true def\n");
1920+
if (S.SP_no_fill) /* Output no-fill flag for sphere */
1921+
PSL_command(PSL, "/SP_no_fill true def\n");
1922+
PSL_plotsymbol(PSL, xpos[item], data[i].y, data[i].dim, data[i].symbol);
1923+
/* Draw outline circle in user space using pen color */
1924+
if (data[i].outline) {
1925+
/* The 1/4 factor was obtained by trial-and-error. Couldn't find the true logic. (JL) */
1926+
PSL_command(PSL, "V /DeviceRGB setcolorspace matrix setmatrix %s "
1927+
"xc_SP_user yc_SP_user T N 0 0 radius_SP_user 0 360 arc S U\n",
1928+
PSL_makepen(PSL, (1.0/4.0) * data[i].p.width, data[i].p.rgb, data[i].p.style, data[i].p.offset));
1929+
}
1930+
break;
18951931
case PSL_ELLIPSE:
18961932
gmt_plane_perspective (GMT, GMT_Z, data[i].z);
18971933
if (data[i].flag & 2)

test/baseline/psxyz.dvc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
outs:
2-
- md5: e8300f3b8e45690deb50c78d03d3e548.dir
3-
nfiles: 28
2+
- md5: d294c350ed35533a2b8baf4729953c9d.dir
3+
nfiles: 29
44
path: psxyz
55
hash: md5
6+
size: 1205108

0 commit comments

Comments
 (0)