1 /* GNUPLOT - boundary.c */
2
3 /*[
4 * Copyright 1986 - 1993, 1998, 2004 Thomas Williams, Colin Kelley
5 *
6 * Permission to use, copy, and distribute this software and its
7 * documentation for any purpose with or without fee is hereby granted,
8 * provided that the above copyright notice appear in all copies and
9 * that both that copyright notice and this permission notice appear
10 * in supporting documentation.
11 *
12 * Permission to modify the software is granted, but not the right to
13 * distribute the complete modified source code. Modifications are to
14 * be distributed as patches to the released version. Permission to
15 * distribute binaries produced by compiling modified sources is granted,
16 * provided you
17 * 1. distribute the corresponding source modifications from the
18 * released version in the form of a patch file along with the binaries,
19 * 2. add special version identification to distinguish your version
20 * in addition to the base release version number,
21 * 3. provide your name and address as the primary contact for the
22 * support of your modified version, and
23 * 4. retain our contact information in regard to use of the base
24 * software.
25 * Permission to distribute the released version of the source code along
26 * with corresponding source modifications in the form of a patch file is
27 * granted with same provisions 2 through 4 for binary distributions.
28 *
29 * This software is provided "as is" without express or implied warranty
30 * to the extent permitted by applicable law.
31 ]*/
32
33 #include "graphics.h"
34 #include "boundary.h"
35 #include "alloc.h"
36 #include "axis.h"
37 #include "misc.h"
38 #include "pm3d.h" /* for is_plot_with_palette */
39
40 #define ERRORBARTIC GPMAX((t->h_tic/2),1)
41
42 /*{{{ local variables */
43 static int xlablin, x2lablin, ylablin, y2lablin, titlelin, xticlin, x2ticlin;
44
45 /*{{{ local and global variables */
46 static int key_sample_width; /* width of line sample */
47 static int key_sample_height; /* sample itself; does not scale with "set key spacing" */
48 static int key_sample_left; /* offset from x for left of line sample */
49 static int key_sample_right; /* offset from x for right of line sample */
50 static int key_text_left; /* offset from x for left-justified text */
51 static int key_text_right; /* offset from x for right-justified text */
52 static int key_size_left; /* size of left bit of key (text or sample, depends on key->reverse) */
53 static int key_size_right; /* size of right part of key (including padding) */
54 static int key_xleft; /* Amount of space on the left required by the key */
55 static int max_ptitl_len = 0; /* max length of plot-titles (keys) */
56 static int ptitl_cnt; /* count keys with len > 0 */
57
58 static int key_width; /* calculate once, then everyone uses it */
59 static int key_height; /* ditto */
60 static int key_title_height; /* nominal number of lines * character height */
61 static int key_title_extra; /* allow room for subscript/superscript */
62 static int time_y, time_x;
63
64 int title_x, title_y; /* Used by boundary and by 2D graphics */
65
66 /*
67 * These quantities are needed in do_plot() e.g. for histogtram title layout
68 */
69 int key_entry_height; /* bigger of t->v_char, t->v_tic */
70 int key_point_offset; /* offset from x for point sample */
71 int ylabel_x, y2label_x, xlabel_y, x2label_y;
72 int x2label_yoffset;
73 int ylabel_y, y2label_y, xtic_y, x2tic_y, ytic_x, y2tic_x;
74 int key_rows;
75 int key_cols;
76 int key_count;
77
78 static int key_col_wth, yl_ref;
79 static int xl, yl;
80
81 /*{{{ boundary() */
82 /* borders of plotting area
83 * computed once on every call to do_plot
84 *
85 * The order in which things are done has become critical:
86 * plot_bounds.ytop depends on title, x2label
87 * plot_bounds.ybot depends on key, if "under"
88 * once we have these, we can setup the y1 and y2 tics and the
89 * only then can we calculate plot_bounds.xleft and plot_bounds.xright
90 * plot_bounds.xright depends also on key RIGHT
91 * then we can do x and x2 tics
92 *
93 * For set size ratio ..., everything depends on everything else...
94 * not really a lot we can do about that, so we lose if the plot has to
95 * be reduced vertically. But the chances are the
96 * change will not be very big, so the number of tics will not
97 * change dramatically.
98 *
99 * Margin computation redone by Dick Crawford (rccrawford@lanl.gov) 4/98
100 */
101
102 void
boundary(struct curve_points * plots,int count)103 boundary(struct curve_points *plots, int count)
104 {
105 int yticlin = 0, y2ticlin = 0;
106 legend_key *key = &keyT;
107
108 struct termentry *t = term;
109 int can_rotate = (*t->text_angle) (TEXT_VERTICAL);
110
111 int xtic_textheight=0; /* height of xtic labels */
112 int x2tic_textheight=0; /* height of x2tic labels */
113 int title_textheight=0; /* height of title */
114 int xlabel_textheight=0; /* height of xlabel */
115 int x2label_textheight=0; /* height of x2label */
116 int ylabel_textwidth=0; /* width of (rotated) ylabel */
117 int y2label_textwidth=0; /* width of (rotated) y2label */
118 int timelabel_textwidth=0; /* width of timestamp */
119 int timelabel_textheight=0; /* height of timestamp */
120 int ytic_textwidth=0; /* width of ytic labels */
121 int y2tic_textwidth=0; /* width of y2tic labels */
122 int x2tic_height=0; /* 0 for tic_in or no x2tics, ticscale*v_tic otherwise */
123 int xtic_textwidth=0; /* amount by which the xtic label protrude to the right */
124 int xtic_height=0;
125 int ytic_width=0;
126 int y2tic_width=0;
127 int ttic_textheight=0; /* vertical clearance for ttics */
128
129 /* figure out which rotatable items are to be rotated
130 * (ylabel and y2label are rotated if possible) */
131 int vertical_timelabel = can_rotate ? timelabel.rotate : 0;
132 int vertical_xtics = can_rotate ? axis_array[FIRST_X_AXIS].tic_rotate : 0;
133 int vertical_x2tics = can_rotate ? axis_array[SECOND_X_AXIS].tic_rotate : 0;
134 int vertical_ytics = can_rotate ? axis_array[FIRST_Y_AXIS].tic_rotate : 0;
135 int vertical_y2tics = can_rotate ? axis_array[SECOND_Y_AXIS].tic_rotate : 0;
136
137 TBOOLEAN shift_labels_to_border = FALSE;
138
139 xticlin = ylablin = y2lablin = xlablin = x2lablin = titlelin = 0;
140
141 /*{{{ count lines in labels and tics */
142 if (title.text)
143 label_width(title.text, &titlelin);
144 if (axis_array[FIRST_X_AXIS].label.text)
145 label_width(axis_array[FIRST_X_AXIS].label.text, &xlablin);
146
147 /* This should go *inside* label_width(), but it messes up the key title */
148 /* Imperfect check for subscripts or superscripts */
149 if ((term->flags & TERM_ENHANCED_TEXT) && axis_array[FIRST_X_AXIS].label.text
150 && strpbrk(axis_array[FIRST_X_AXIS].label.text, "_^"))
151 xlablin++;
152
153 if (axis_array[SECOND_X_AXIS].label.text)
154 label_width(axis_array[SECOND_X_AXIS].label.text, &x2lablin);
155 if (axis_array[FIRST_Y_AXIS].label.text)
156 label_width(axis_array[FIRST_Y_AXIS].label.text, &ylablin);
157 if (axis_array[SECOND_Y_AXIS].label.text)
158 label_width(axis_array[SECOND_Y_AXIS].label.text, &y2lablin);
159
160 if (axis_array[FIRST_X_AXIS].ticmode) {
161 label_width(axis_array[FIRST_X_AXIS].formatstring, &xticlin);
162 /* Reserve room for user tic labels even if format of autoticks is "" */
163 if (xticlin == 0 && axis_array[FIRST_X_AXIS].ticdef.def.user)
164 xticlin = 1;
165 }
166
167 if (axis_array[SECOND_X_AXIS].ticmode)
168 label_width(axis_array[SECOND_X_AXIS].formatstring, &x2ticlin);
169 if (axis_array[FIRST_Y_AXIS].ticmode)
170 label_width(axis_array[FIRST_Y_AXIS].formatstring, &yticlin);
171 if (axis_array[SECOND_Y_AXIS].ticmode)
172 label_width(axis_array[SECOND_Y_AXIS].formatstring, &y2ticlin);
173 /*}}} */
174
175 /*{{{ preliminary plot_bounds.ytop calculation */
176
177 /* first compute heights of things to be written in the margin */
178
179
180 /* Title placement has been reworked for 5.4
181 * NOTE: title_textheight is _not_ the height of the title!
182 * It is the amount of space reserved for the title above the plot.
183 * A negative offset greater than the number of title lines means
184 * that the title will appear inside the boundary and no extra space
185 * needs to be reserved for it above the plot.
186 */
187 title_textheight = 0;
188 if (titlelin) {
189 title_textheight = t->v_char; /* Gap of one normal line height */
190 if (title.font)
191 t->set_font(title.font);
192 title_y = titlelin * t->v_char;
193 if ((titlelin + title.offset.y) > 0)
194 title_textheight += titlelin * t->v_char;
195 if (title.font)
196 t->set_font("");
197 title_y += 0.5 * t->v_char; /* Approximate same placement as version 5.2 */
198 }
199
200 /* Extra space at the top for spiderplot axis label */
201 if (spiderplot)
202 title_textheight += 1.5 * t->v_char;
203
204 /* x2label */
205 if (x2lablin) {
206 double tmpx, tmpy;
207 map_position_r(&(axis_array[SECOND_X_AXIS].label.offset),
208 &tmpx, &tmpy, "x2label");
209 if (axis_array[SECOND_X_AXIS].label.font)
210 t->set_font(axis_array[SECOND_X_AXIS].label.font);
211 x2label_textheight = (int) (x2lablin * t->v_char);
212 x2label_yoffset = tmpy;
213 if (axis_array[SECOND_X_AXIS].label.font)
214 t->set_font("");
215 } else
216 x2label_textheight = 0;
217
218 /* tic labels */
219 if (axis_array[SECOND_X_AXIS].ticmode & TICS_ON_BORDER) {
220 /* ought to consider tics on axes if axis near border */
221 x2tic_textheight = (int) (x2ticlin * t->v_char);
222 } else
223 x2tic_textheight = 0;
224
225 /* tics */
226 if (axis_array[SECOND_X_AXIS].ticmode & TICS_ON_BORDER) {
227 x2tic_height = t->v_tic * axis_array[SECOND_X_AXIS].ticscale;
228 if (axis_array[SECOND_X_AXIS].tic_in)
229 x2tic_height = -x2tic_height;
230 } else
231 x2tic_height = 0;
232
233 /* Polar (theta) tic labels need space at top and bottom of plot */
234 if (THETA_AXIS.ticmode) {
235 /* FIXME: Really 5% of polar grid radius, but we don't know that yet */
236 ttic_textheight = 2. * t->v_char;
237 }
238
239 /* timestamp */
240 if (timelabel.text) {
241 int timelin;
242 timelabel_textwidth = label_width(timelabel.text, &timelin);
243 if (vertical_timelabel) {
244 timelabel_textheight = timelabel_textwidth * t->v_char;
245 timelabel_textwidth = (timelin + 1.5) * t->h_char;
246 timelabel.place.y = 0;
247 } else {
248 timelabel_textheight = timelin * t->v_char;
249 timelabel_textwidth = timelabel_textwidth * t->h_char;
250 /* save textheight for use in do_key_bounds() */
251 timelabel.place.y = timelabel_textheight;
252 }
253 }
254
255 /* ylabel placement */
256 if (axis_array[FIRST_Y_AXIS].label.text) {
257 if (can_rotate && axis_array[FIRST_Y_AXIS].label.rotate != 0) {
258 ylabel_textwidth = ylablin * t->v_char;
259 } else {
260 /* Trying to estimate this length caused more problems than it solved.
261 * For one thing it comes out wrong for text passed to TeX terminals.
262 * Assume the common case is roughly 3 character widths and let the
263 * user adjust lmargin and offset for longer non-rotated ylabels.
264 */
265 ylabel_textwidth = 3 * t->h_char;
266 }
267 }
268
269 /* y2label placement */
270 if (axis_array[SECOND_Y_AXIS].label.text) {
271 if (can_rotate && axis_array[SECOND_Y_AXIS].label.rotate != 0) {
272 y2label_textwidth = y2lablin * t->v_char;
273 if (!axis_array[SECOND_Y_AXIS].ticmode)
274 y2label_textwidth += 0.5 * t->v_char;
275 } else {
276 /* See above. Estimating true text length causes problems */
277 y2label_textwidth = 3 * t->h_char;
278 }
279 }
280
281 /* compute plot_bounds.ytop from the various components
282 * unless tmargin is explicitly specified
283 */
284
285 plot_bounds.ytop = (int) (0.5 + (ysize + yoffset) * (t->ymax-1));
286
287 /* Sanity check top and bottom margins, in case the user got confused */
288 if (bmargin.scalex == screen && tmargin.scalex == screen)
289 if (bmargin.x > tmargin.x) {
290 double tmp = bmargin.x;
291 bmargin.x = tmargin.x;
292 tmargin.x = tmp;
293 }
294
295 if (tmargin.scalex == screen) {
296 /* Specified as absolute position on the canvas */
297 plot_bounds.ytop = (tmargin.x) * (float)(t->ymax-1);
298 } else if (tmargin.x >=0) {
299 /* Specified in terms of character height */
300 plot_bounds.ytop -= (int)(tmargin.x * (float)t->v_char + 0.5);
301 } else {
302 /* Auto-calculation of space required */
303 int top_margin = title_textheight;
304 if (x2label_textheight + x2label_yoffset > 0)
305 top_margin += x2label_textheight;
306
307 if (timelabel_textheight > top_margin && !timelabel_bottom && !vertical_timelabel)
308 top_margin = timelabel_textheight;
309
310 top_margin += x2tic_textheight;
311 top_margin += t->v_char;
312 if (x2tic_height > 0)
313 top_margin += x2tic_height;
314 top_margin += ttic_textheight;
315
316 plot_bounds.ytop -= top_margin;
317 if (plot_bounds.ytop == (int)(0.5 + (ysize + yoffset) * (t->ymax-1))) {
318 /* make room for the end of rotated ytics or y2tics */
319 plot_bounds.ytop -= (int) (t->h_char * 2);
320 }
321 }
322
323 /* end of preliminary plot_bounds.ytop calculation }}} */
324
325
326 /*{{{ preliminary plot_bounds.xleft, needed for "under" */
327 if (lmargin.scalex == screen)
328 plot_bounds.xleft = lmargin.x * (float)t->xmax;
329 else
330 plot_bounds.xleft = xoffset * t->xmax
331 + t->h_char * (lmargin.x >= 0 ? lmargin.x : 1);
332 /*}}} */
333
334
335 /*{{{ tentative plot_bounds.xright, needed for "under" */
336 if (rmargin.scalex == screen)
337 plot_bounds.xright = rmargin.x * (float)(t->xmax - 1);
338 else
339 plot_bounds.xright = (xsize + xoffset) * (t->xmax - 1)
340 - t->h_char * (rmargin.x >= 0 ? rmargin.x : 2);
341 /*}}} */
342
343
344 /*{{{ preliminary plot_bounds.ybot calculation
345 * first compute heights of labels and tics */
346
347 /* tic labels */
348 shift_labels_to_border = FALSE;
349 if (axis_array[FIRST_X_AXIS].ticmode & TICS_ON_AXIS) {
350 /* FIXME: This test for how close the axis is to the border does not match */
351 /* the tests in axis_output_tics(), and assumes FIRST_Y_AXIS. */
352 if (!inrange(0.0, axis_array[FIRST_Y_AXIS].min, axis_array[FIRST_Y_AXIS].max))
353 shift_labels_to_border = TRUE;
354 if (0.05 > fabs( axis_array[FIRST_Y_AXIS].min
355 / (axis_array[FIRST_Y_AXIS].max - axis_array[FIRST_Y_AXIS].min)))
356 shift_labels_to_border = TRUE;
357 }
358 if ((axis_array[FIRST_X_AXIS].ticmode & TICS_ON_BORDER)
359 || shift_labels_to_border) {
360 xtic_textheight = (int) (t->v_char * (xticlin + 1));
361 } else
362 xtic_textheight = 0;
363
364 /* tics */
365 if (!axis_array[FIRST_X_AXIS].tic_in
366 && ((axis_array[FIRST_X_AXIS].ticmode & TICS_ON_BORDER)
367 || ((axis_array[SECOND_X_AXIS].ticmode & TICS_MIRROR)
368 && (axis_array[SECOND_X_AXIS].ticmode & TICS_ON_BORDER))))
369 xtic_height = (int) (t->v_tic * axis_array[FIRST_X_AXIS].ticscale);
370 else
371 xtic_height = 0;
372
373 /* xlabel */
374 if (xlablin) {
375 double tmpx, tmpy;
376 map_position_r(&(axis_array[FIRST_X_AXIS].label.offset),
377 &tmpx, &tmpy, "boundary");
378 /* offset is subtracted because if > 0, the margin is smaller */
379 /* textheight is inflated by 0.2 to allow descenders to clear bottom of canvas */
380 xlabel_textheight = (((float)xlablin + 0.2) * t->v_char - tmpy);
381 if (!axis_array[FIRST_X_AXIS].ticmode)
382 xlabel_textheight += 0.5 * t->v_char;
383 } else
384 xlabel_textheight = 0;
385
386 /* compute plot_bounds.ybot from the various components
387 * unless bmargin is explicitly specified */
388
389 plot_bounds.ybot = yoffset * (float)t->ymax;
390
391 if (bmargin.scalex == screen) {
392 /* Absolute position for bottom of plot */
393 plot_bounds.ybot = bmargin.x * (float)t->ymax;
394 } else if (bmargin.x >= 0) {
395 /* Position based on specified character height */
396 plot_bounds.ybot += bmargin.x * (float)t->v_char + 0.5;
397 } else {
398 plot_bounds.ybot += xtic_height + xtic_textheight;
399 if (xlabel_textheight > 0)
400 plot_bounds.ybot += xlabel_textheight;
401 if (!vertical_timelabel && timelabel_bottom && timelabel_textheight > 0)
402 plot_bounds.ybot += timelabel_textheight;
403 if (plot_bounds.ybot == (int) (t->ymax * yoffset)) {
404 /* make room for the end of rotated ytics or y2tics */
405 plot_bounds.ybot += (int) (t->h_char * 2);
406 }
407 if (spiderplot) /* Extra space at the bottom for spiderplot axis label */
408 plot_bounds.ybot += 2 * t->h_char;
409 /* Last chance for better estimate of space required for ttic labels */
410 /* It is too late to go back and adjust positions relative to ytop */
411 if (ttic_textheight > 0) {
412 ttic_textheight = 0.05 * (plot_bounds.ytop - plot_bounds.ybot);
413 plot_bounds.ybot += ttic_textheight;
414 }
415 }
416
417 /* end of preliminary plot_bounds.ybot calculation }}} */
418
419 /* Determine the size and position of the key box */
420 if (key->visible) {
421 /* Count max_len key and number keys with len > 0 */
422 max_ptitl_len = find_maxl_keys(plots, count, &ptitl_cnt);
423 do_key_layout(key);
424 }
425
426 /* Adjust range of dependent axes y and y2 */
427 if (nonlinear(&axis_array[FIRST_Y_AXIS]))
428 extend_primary_ticrange(&axis_array[FIRST_Y_AXIS]);
429 if (nonlinear(&axis_array[SECOND_Y_AXIS]))
430 extend_primary_ticrange(&axis_array[SECOND_Y_AXIS]);
431 setup_tics(&axis_array[FIRST_Y_AXIS], 20);
432 setup_tics(&axis_array[SECOND_Y_AXIS], 20);
433
434 /* Adjust color axis limits if necessary. */
435 if (is_plot_with_palette()) {
436 axis_checked_extend_empty_range(COLOR_AXIS, "All points of color axis undefined.");
437 if (color_box.where != SMCOLOR_BOX_NO)
438 setup_tics(&axis_array[COLOR_AXIS], 20);
439 }
440
441 /*{{{ recompute plot_bounds.xleft based on widths of ytics, ylabel etc
442 unless it has been explicitly set by lmargin */
443
444 /* tic labels */
445 shift_labels_to_border = FALSE;
446 if (axis_array[FIRST_Y_AXIS].ticmode & TICS_ON_AXIS) {
447 /* FIXME: This test for how close the axis is to the border does not match */
448 /* the tests in axis_output_tics(), and assumes FIRST_X_AXIS. */
449 if (!inrange(0.0, axis_array[FIRST_X_AXIS].min, axis_array[FIRST_X_AXIS].max))
450 shift_labels_to_border = TRUE;
451 if (0.1 > fabs( axis_array[FIRST_X_AXIS].min
452 / (axis_array[FIRST_X_AXIS].max - axis_array[FIRST_X_AXIS].min)))
453 shift_labels_to_border = TRUE;
454 }
455
456 if ((axis_array[FIRST_Y_AXIS].ticmode & TICS_ON_BORDER)
457 || shift_labels_to_border) {
458 if (vertical_ytics)
459 /* HBB: we will later add some white space as part of this, so
460 * reserve two more rows (one above, one below the text ...).
461 * Same will be done to similar calc.'s elsewhere */
462 ytic_textwidth = (int) (t->v_char * (yticlin + 2));
463 else {
464 widest_tic_strlen = 0; /* reset the global variable ... */
465 /* get gen_tics to call widest_tic_callback with all labels
466 * the latter sets widest_tic_strlen to the length of the widest
467 * one ought to consider tics on axis if axis near border...
468 */
469 gen_tics(&axis_array[FIRST_Y_AXIS], widest_tic_callback);
470
471 ytic_textwidth = (int) (t->h_char * (widest_tic_strlen + 2));
472 }
473 } else if (axis_array[FIRST_Y_AXIS].label.text) {
474 /* substitutes for extra space added to left of ytix labels */
475 ytic_textwidth = 2 * (t->h_char);
476 } else {
477 ytic_textwidth = 0;
478 }
479
480 /* tics */
481 if (!axis_array[FIRST_Y_AXIS].tic_in
482 && ((axis_array[FIRST_Y_AXIS].ticmode & TICS_ON_BORDER)
483 || ((axis_array[SECOND_Y_AXIS].ticmode & TICS_MIRROR)
484 && (axis_array[SECOND_Y_AXIS].ticmode & TICS_ON_BORDER))))
485 ytic_width = (int) (t->h_tic * axis_array[FIRST_Y_AXIS].ticscale);
486 else
487 ytic_width = 0;
488
489 if (lmargin.x < 0) {
490 /* Auto-calculation */
491 int space_to_left = key_xleft;
492
493 if (space_to_left < timelabel_textwidth && vertical_timelabel)
494 space_to_left = timelabel_textwidth;
495 if (space_to_left < ylabel_textwidth)
496 space_to_left = ylabel_textwidth;
497 plot_bounds.xleft = xoffset * t->xmax;
498 plot_bounds.xleft += space_to_left;
499 plot_bounds.xleft += ytic_width + ytic_textwidth;
500
501 if (plot_bounds.xleft - ytic_width - ytic_textwidth < 0)
502 plot_bounds.xleft = ytic_width + ytic_textwidth;
503 if (plot_bounds.xleft == t->xmax * xoffset)
504 plot_bounds.xleft += t->h_char * 2;
505 /* DBT 12-3-98 extra margin just in case */
506 plot_bounds.xleft += 0.5 * t->h_char;
507 }
508 /* Note: we took care of explicit 'set lmargin foo' at line 492 */
509
510 /* end of plot_bounds.xleft calculation }}} */
511
512 /*{{{ recompute plot_bounds.xright based on widest y2tic. y2labels, key "outside"
513 unless it has been explicitly set by rmargin */
514
515 /* tic labels */
516 if (axis_array[SECOND_Y_AXIS].ticmode & TICS_ON_BORDER) {
517 if (vertical_y2tics)
518 y2tic_textwidth = (int) (t->v_char * (y2ticlin + 2));
519 else {
520 widest_tic_strlen = 0; /* reset the global variable ... */
521 /* get gen_tics to call widest_tic_callback with all labels
522 * the latter sets widest_tic_strlen to the length of the widest
523 * one ought to consider tics on axis if axis near border...
524 */
525 gen_tics(&axis_array[SECOND_Y_AXIS], widest_tic_callback);
526
527 y2tic_textwidth = (int) (t->h_char * (widest_tic_strlen + 2));
528 }
529 } else {
530 y2tic_textwidth = 0;
531 }
532
533 /* EAM May 2009
534 * Check to see if any xtic labels are so long that they extend beyond
535 * the right boundary of the plot. If so, allow extra room in the margin.
536 * If the labels are too long to fit even with a big margin, too bad.
537 */
538 if (axis_array[FIRST_X_AXIS].ticdef.def.user) {
539 struct ticmark *tic = axis_array[FIRST_X_AXIS].ticdef.def.user;
540 int maxrightlabel = plot_bounds.xright;
541
542 /* We don't really know the plot layout yet, but try for an estimate */
543 axis_set_scale_and_range(&axis_array[FIRST_X_AXIS], plot_bounds.xleft, plot_bounds.xright);
544
545 while (tic) {
546 if (tic->label) {
547 double xx;
548 int length = estimate_strlen(tic->label)
549 * cos(DEG2RAD * (double)(axis_array[FIRST_X_AXIS].tic_rotate))
550 * term->h_char;
551
552 if (inrange(tic->position,
553 axis_array[FIRST_X_AXIS].set_min,
554 axis_array[FIRST_X_AXIS].set_max)) {
555 xx = axis_log_value_checked(FIRST_X_AXIS, tic->position, "xtic");
556 xx = map_x(xx);
557 xx += (axis_array[FIRST_X_AXIS].tic_rotate) ? length : length /2;
558 if (maxrightlabel < xx)
559 maxrightlabel = xx;
560 }
561 }
562 tic = tic->next;
563 }
564 xtic_textwidth = maxrightlabel - plot_bounds.xright;
565 if (xtic_textwidth > term->xmax/4) {
566 xtic_textwidth = term->xmax/4;
567 int_warn(NO_CARET, "difficulty making room for xtic labels");
568 }
569 }
570
571 /* tics */
572 if (!axis_array[SECOND_Y_AXIS].tic_in
573 && ((axis_array[SECOND_Y_AXIS].ticmode & TICS_ON_BORDER)
574 || ((axis_array[FIRST_Y_AXIS].ticmode & TICS_MIRROR)
575 && (axis_array[FIRST_Y_AXIS].ticmode & TICS_ON_BORDER))))
576 y2tic_width = (int) (t->h_tic * axis_array[SECOND_Y_AXIS].ticscale);
577 else
578 y2tic_width = 0;
579
580 /* Make room for the color box if needed. */
581 if (rmargin.scalex != screen) {
582 if (is_plot_with_colorbox()) {
583 #define COLORBOX_SCALE 0.100
584 #define WIDEST_COLORBOX_TICTEXT 3
585 if ((color_box.where != SMCOLOR_BOX_NO) && (color_box.where != SMCOLOR_BOX_USER)) {
586 plot_bounds.xright -= (int) (plot_bounds.xright-plot_bounds.xleft)*COLORBOX_SCALE;
587 plot_bounds.xright -= (int) ((t->h_char) * WIDEST_COLORBOX_TICTEXT);
588 }
589 color_box.xoffset = 0;
590 }
591
592 if (rmargin.x < 0) {
593 color_box.xoffset = plot_bounds.xright;
594 plot_bounds.xright -= y2tic_width + y2tic_textwidth;
595 if (y2label_textwidth > 0)
596 plot_bounds.xright -= y2label_textwidth;
597
598 if (plot_bounds.xright > (xsize+xoffset)*(t->xmax-1) - (t->h_char * 2))
599 plot_bounds.xright = (xsize+xoffset)*(t->xmax-1) - (t->h_char * 2);
600
601 color_box.xoffset -= plot_bounds.xright;
602 /* EAM 2009 - protruding xtic labels */
603 if (term->xmax - plot_bounds.xright < xtic_textwidth)
604 plot_bounds.xright = term->xmax - xtic_textwidth;
605 /* DBT 12-3-98 extra margin just in case */
606 plot_bounds.xright -= 1.0 * t->h_char;
607 }
608 /* Note: we took care of explicit 'set rmargin foo' at line 502 */
609 }
610
611 /* end of plot_bounds.xright calculation }}} */
612
613
614 /* Set up x and x2 tics */
615 /* we should base the guide on the width of the xtics, but we cannot
616 * use widest_tics until tics are set up. Bit of a downer - let us
617 * assume tics are 5 characters wide
618 */
619 setup_tics(&axis_array[FIRST_X_AXIS], 20);
620 setup_tics(&axis_array[SECOND_X_AXIS], 20);
621
622 /* Make sure that if polar grid is shown on a cartesian axis plot */
623 /* the rtics match up with the primary x tics. */
624 if (R_AXIS.ticmode && (polar || raxis)) {
625 if (bad_axis_range(&R_AXIS) || (!polar && R_AXIS.min != 0)) {
626 set_explicit_range(&R_AXIS, 0.0, X_AXIS.max);
627 R_AXIS.min = 0;
628 R_AXIS.max = axis_array[FIRST_X_AXIS].max;
629 int_warn(NO_CARET, "resetting rrange");
630 }
631 setup_tics(&axis_array[POLAR_AXIS], 10);
632 }
633
634
635 /* Modify the bounding box to fit the aspect ratio, if any was given */
636 if (aspect_ratio != 0.0) {
637 double current_aspect_ratio;
638 double current, required;
639
640 if (aspect_ratio < 0 && (X_AXIS.max - X_AXIS.min) != 0.0) {
641 current_aspect_ratio = -aspect_ratio
642 * fabs((Y_AXIS.max - Y_AXIS.min) / (X_AXIS.max - X_AXIS.min));
643 } else
644 current_aspect_ratio = aspect_ratio;
645
646 if (current_aspect_ratio < 0.005 || current_aspect_ratio > 2000.0)
647 int_warn(NO_CARET, "extreme aspect ratio");
648
649 current = ((double) (plot_bounds.ytop - plot_bounds.ybot))
650 / ((double) (plot_bounds.xright - plot_bounds.xleft));
651 required = (current_aspect_ratio * t->v_tic) / t->h_tic;
652
653 /* Fixed borders take precedence over centering */
654 if (current > required) {
655 /* too tall */
656 int old_height = plot_bounds.ytop - plot_bounds.ybot;
657 int new_height = required * (plot_bounds.xright - plot_bounds.xleft);
658 if (bmargin.scalex == screen)
659 plot_bounds.ytop = plot_bounds.ybot + new_height;
660 else if (tmargin.scalex == screen)
661 plot_bounds.ybot = plot_bounds.ytop - new_height;
662 else {
663 plot_bounds.ybot += (old_height - new_height) / 2;
664 plot_bounds.ytop -= (old_height - new_height) / 2;
665 }
666
667 } else {
668 /* too wide */
669 int old_width = plot_bounds.xright - plot_bounds.xleft;
670 int new_width = (plot_bounds.ytop - plot_bounds.ybot) / required;
671 if (lmargin.scalex == screen)
672 plot_bounds.xright = plot_bounds.xleft + new_width;
673 else if (rmargin.scalex == screen)
674 plot_bounds.xleft = plot_bounds.xright - new_width;
675 else {
676 plot_bounds.xleft += (old_width - new_width) / 2;
677 plot_bounds.xright -= (old_width - new_width) / 2;
678 }
679 }
680 }
681
682 /* Calculate space needed for tic label rotation.
683 * If [tb]margin is auto, move the plot boundary.
684 * Otherwise use textheight to adjust placement of various titles.
685 */
686
687 if (axis_array[SECOND_X_AXIS].ticmode & TICS_ON_BORDER && vertical_x2tics) {
688 /* Assuming left justified tic labels. Correction below if they aren't */
689 double projection = sin((double)axis_array[SECOND_X_AXIS].tic_rotate*DEG2RAD);
690 if (axis_array[SECOND_X_AXIS].tic_pos == RIGHT)
691 projection *= -1;
692 else if (axis_array[SECOND_X_AXIS].tic_pos == CENTRE)
693 projection = 0.5*fabs(projection);
694 widest_tic_strlen = 0; /* reset the global variable ... */
695 gen_tics(&axis_array[SECOND_X_AXIS], widest_tic_callback);
696 if (tmargin.x < 0) /* Undo original estimate */
697 plot_bounds.ytop += x2tic_textheight;
698 /* Adjust spacing for rotation */
699 if (projection > 0.0)
700 x2tic_textheight += (int) (t->h_char * (widest_tic_strlen)) * projection;
701 if (tmargin.x < 0)
702 plot_bounds.ytop -= x2tic_textheight;
703 }
704 if (axis_array[FIRST_X_AXIS].ticmode & TICS_ON_BORDER && vertical_xtics) {
705 double projection;
706 /* This adjustment will happen again in axis_output_tics but we need it now */
707 if (axis_array[FIRST_X_AXIS].tic_rotate == TEXT_VERTICAL
708 && !axis_array[FIRST_X_AXIS].manual_justify)
709 axis_array[FIRST_X_AXIS].tic_pos = RIGHT;
710 if (axis_array[FIRST_X_AXIS].tic_rotate == 90)
711 projection = -1.0;
712 else if (axis_array[FIRST_X_AXIS].tic_rotate == TEXT_VERTICAL)
713 projection = -1.0;
714 else
715 projection = -sin((double)axis_array[FIRST_X_AXIS].tic_rotate*DEG2RAD);
716 if (axis_array[FIRST_X_AXIS].tic_pos == RIGHT)
717 projection *= -1;
718 widest_tic_strlen = 0; /* reset the global variable ... */
719 gen_tics(&axis_array[FIRST_X_AXIS], widest_tic_callback);
720
721 if (bmargin.x < 0)
722 plot_bounds.ybot -= xtic_textheight;
723 if (projection > 0.0)
724 xtic_textheight = (int) (t->h_char * widest_tic_strlen) * projection
725 + t->v_char;
726 if (bmargin.x < 0)
727 plot_bounds.ybot += xtic_textheight;
728 }
729
730 /*
731 * Notwithstanding all these fancy calculations,
732 * plot_bounds.ytop must always be above plot_bounds.ybot
733 */
734 if (plot_bounds.ytop < plot_bounds.ybot) {
735 int i = plot_bounds.ytop;
736
737 plot_bounds.ytop = plot_bounds.ybot;
738 plot_bounds.ybot = i;
739 FPRINTF((stderr,"boundary: Big problems! plot_bounds.ybot > plot_bounds.ytop\n"));
740 }
741
742 /* compute coordinates for axis labels, title etc */
743
744 x2label_y = plot_bounds.ytop + x2label_textheight;
745 x2label_y += 0.5 * t->v_char;
746 if (x2label_textheight + x2label_yoffset >= 0) {
747 x2label_y += 1.5 * x2tic_textheight;
748 /* Adjust for the tics themselves */
749 if (x2tic_height > 0)
750 x2label_y += x2tic_height;
751 }
752
753 title_x = (plot_bounds.xleft + plot_bounds.xright) / 2;
754
755 /* title_y was previously set to the actual title height.
756 * Further corrections to this placement only if it is above the plot
757 */
758 title_y += plot_bounds.ytop;
759 if (titlelin + title.offset.y > 0) {
760 title_y += x2tic_textheight;
761 title_y += ttic_textheight;
762 if (x2label_y + x2label_yoffset > plot_bounds.ytop)
763 title_y += x2label_textheight;
764 if (x2tic_height > 0)
765 title_y += x2tic_height;
766 }
767
768 /* Shift upward by 0.2 line to allow for descenders in xlabel text */
769 xlabel_y = plot_bounds.ybot - xtic_height - xtic_textheight - xlabel_textheight
770 + ((float)xlablin+0.2) * t->v_char;
771 xlabel_y -= ttic_textheight;
772 ylabel_x = plot_bounds.xleft - ytic_width - ytic_textwidth;
773 ylabel_x -= ylabel_textwidth/2;
774
775 y2label_x = plot_bounds.xright + y2tic_width + y2tic_textwidth;
776 y2label_x += y2label_textwidth/2;
777
778 /* Nov 2016 - simplify placement of timestamp
779 * Stamp the same place on the page regardless of plot margins
780 */
781 if (vertical_timelabel) {
782 time_x = 1.5 * term->h_char;
783 if (timelabel_bottom)
784 time_y = term->v_char;
785 else
786 time_y = term->ymax - term->v_char;
787 } else {
788 time_x = 1.0 * term->h_char;
789 if (timelabel_bottom)
790 time_y = timelabel_textheight - 0.5 * term->v_char;
791 else
792 time_y = term->ymax;
793 }
794
795 xtic_y = plot_bounds.ybot - xtic_height
796 - (int) (vertical_xtics ? t->h_char : t->v_char);
797
798 x2tic_y = plot_bounds.ytop + (x2tic_height > 0 ? x2tic_height : 0)
799 + (vertical_x2tics ? (int) t->h_char : t->v_char);
800
801 ytic_x = plot_bounds.xleft - ytic_width
802 - (vertical_ytics
803 ? (ytic_textwidth - (int) t->v_char)
804 : (int) t->h_char);
805
806 y2tic_x = plot_bounds.xright + y2tic_width
807 + (int) (vertical_y2tics ? t->v_char : t->h_char);
808
809 /* restore text to horizontal [we tested rotation above] */
810 (void) (*t->text_angle) (0);
811
812 /* needed for map_position() below */
813 axis_set_scale_and_range(&axis_array[FIRST_X_AXIS], plot_bounds.xleft, plot_bounds.xright);
814 axis_set_scale_and_range(&axis_array[SECOND_X_AXIS], plot_bounds.xleft, plot_bounds.xright);
815 axis_set_scale_and_range(&axis_array[FIRST_Y_AXIS], plot_bounds.ybot, plot_bounds.ytop);
816 axis_set_scale_and_range(&axis_array[SECOND_Y_AXIS], plot_bounds.ybot, plot_bounds.ytop);
817
818 /* Calculate limiting bounds of the key */
819 do_key_bounds(key);
820
821
822 /* Set default clipping to the plot boundary */
823 clip_area = &plot_bounds;
824
825 /* Sanity checks */
826 if (plot_bounds.xright < plot_bounds.xleft
827 || plot_bounds.ytop < plot_bounds.ybot)
828 int_warn(NO_CARET, "Terminal canvas area too small to hold plot."
829 "\n\t Check plot boundary and font sizes.");
830
831 }
832
833 /*}}} */
834
835 void
do_key_bounds(legend_key * key)836 do_key_bounds(legend_key *key)
837 {
838 struct termentry *t = term;
839
840 key_height = key_title_height + key_title_extra
841 + key_rows * key_entry_height + key->height_fix * key_entry_height;
842 key_width = key_col_wth * key_cols;
843
844 /* Key inside plot boundaries */
845 if (key->region == GPKEY_AUTO_INTERIOR_LRTBC
846 || (key->region == GPKEY_AUTO_EXTERIOR_LRTBC && key->vpos == JUST_CENTRE && key->hpos == CENTRE)) {
847 if (key->vpos == JUST_TOP) {
848 key->bounds.ytop = plot_bounds.ytop - t->v_tic;
849 key->bounds.ybot = key->bounds.ytop - key_height;
850 } else if (key->vpos == JUST_BOT) {
851 key->bounds.ybot = plot_bounds.ybot + t->v_tic;
852 key->bounds.ytop = key->bounds.ybot + key_height;
853 } else /* (key->vpos == JUST_CENTRE) */ {
854 key->bounds.ybot = ((plot_bounds.ybot + plot_bounds.ytop) - key_height) / 2;
855 key->bounds.ytop = ((plot_bounds.ybot + plot_bounds.ytop) + key_height) / 2;
856 }
857 if (key->hpos == LEFT) {
858 key->bounds.xleft = plot_bounds.xleft + t->h_char;
859 key->bounds.xright = key->bounds.xleft + key_width;
860 } else if (key->hpos == RIGHT) {
861 key->bounds.xright = plot_bounds.xright - t->h_char;
862 key->bounds.xleft = key->bounds.xright - key_width;
863 } else /* (key->hpos == CENTER) */ {
864 key->bounds.xleft = ((plot_bounds.xright + plot_bounds.xleft) - key_width) / 2;
865 key->bounds.xright = ((plot_bounds.xright + plot_bounds.xleft) + key_width) / 2;
866 }
867
868 /* Key outside plot boundaries */
869 } else if (key->region == GPKEY_AUTO_EXTERIOR_LRTBC || key->region == GPKEY_AUTO_EXTERIOR_MARGIN) {
870
871 /* Vertical alignment */
872 if (key->margin == GPKEY_TMARGIN) {
873 /* align top first since tmargin may be manual */
874 key->bounds.ytop = (ysize + yoffset) * t->ymax - t->v_tic;
875 key->bounds.ybot = key->bounds.ytop - key_height;
876 } else if (key->margin == GPKEY_BMARGIN) {
877 /* align bottom first since bmargin may be manual */
878 key->bounds.ybot = yoffset * t->ymax + t->v_tic;
879 if (timelabel.rotate == 0 && timelabel_bottom && timelabel.place.y > 0)
880 key->bounds.ybot += (int)(timelabel.place.y);
881 key->bounds.ytop = key->bounds.ybot + key_height;
882 } else {
883 if (key->vpos == JUST_TOP) {
884 /* align top first since tmargin may be manual */
885 key->bounds.ytop = plot_bounds.ytop;
886 key->bounds.ybot = key->bounds.ytop - key_height;
887 } else if (key->vpos == JUST_CENTRE) {
888 key->bounds.ybot = ((plot_bounds.ybot + plot_bounds.ytop) - key_height) / 2;
889 key->bounds.ytop = ((plot_bounds.ybot + plot_bounds.ytop) + key_height) / 2;
890 } else {
891 /* align bottom first since bmargin may be manual */
892 key->bounds.ybot = plot_bounds.ybot;
893 key->bounds.ytop = key->bounds.ybot + key_height;
894 }
895 }
896
897 /* Horizontal alignment */
898 if (key->margin == GPKEY_LMARGIN) {
899 /* align left first since lmargin may be manual */
900 key->bounds.xleft = xoffset * t->xmax + t->h_char;
901 key->bounds.xright = key->bounds.xleft + key_width;
902 } else if (key->margin == GPKEY_RMARGIN) {
903 /* align right first since rmargin may be manual */
904 key->bounds.xright = (xsize + xoffset) * (t->xmax-1) - t->h_char;
905 key->bounds.xleft = key->bounds.xright - key_width;
906 } else {
907 if (key->hpos == LEFT) {
908 /* align left first since lmargin may be manual */
909 key->bounds.xleft = plot_bounds.xleft;
910 key->bounds.xright = key->bounds.xleft + key_width;
911 } else if (key->hpos == CENTRE) {
912 key->bounds.xleft = ((plot_bounds.xright + plot_bounds.xleft) - key_width) / 2;
913 key->bounds.xright = ((plot_bounds.xright + plot_bounds.xleft) + key_width) / 2;
914 } else {
915 /* align right first since rmargin may be manual */
916 key->bounds.xright = plot_bounds.xright;
917 key->bounds.xleft = key->bounds.xright - key_width;
918 }
919 }
920
921 /* Key at explicit position specified by user */
922 } else {
923 int x, y;
924
925 /* FIXME!!!
926 * pm 22.1.2002: if key->user_pos.scalex or scaley == first_axes or second_axes,
927 * then the graph scaling is not yet known and the box is positioned incorrectly;
928 * you must do "replot" to avoid the wrong plot ... bad luck if output does not
929 * go to screen
930 */
931 map_position(&key->user_pos, &x, &y, "key");
932
933 /* Here top, bottom, left, right refer to the alignment with respect to point. */
934 key->bounds.xleft = x;
935 if (key->hpos == CENTRE)
936 key->bounds.xleft -= key_width/2;
937 else if (key->hpos == RIGHT)
938 key->bounds.xleft -= key_width;
939 key->bounds.xright = key->bounds.xleft + key_width;
940 key->bounds.ytop = y;
941 if (key->vpos == JUST_CENTRE)
942 key->bounds.ytop += key_height/2;
943 else if (key->vpos == JUST_BOT)
944 key->bounds.ytop += key_height;
945 key->bounds.ybot = key->bounds.ytop - key_height;
946 }
947 }
948
949 /* Calculate positioning of components that make up the key box */
950 void
do_key_layout(legend_key * key)951 do_key_layout(legend_key *key)
952 {
953 struct termentry *t = term;
954 TBOOLEAN key_panic = FALSE;
955
956 /* If there is a separate font for the key, use it for space calculations. */
957 if (key->font)
958 t->set_font(key->font);
959
960 /* Is it OK to initialize these here rather than in do_plot? */
961 key_count = 0;
962 key_xleft = 0;
963 xl = yl = 0;
964
965 if (key->swidth >= 0) {
966 key_sample_width = key->swidth * t->h_char + t->h_tic;
967 } else {
968 key_sample_width = 0;
969 }
970
971 key_sample_height = GPMAX( 1.25 * t->v_tic, t->v_char );
972 key_entry_height = key_sample_height * key->vert_factor;
973 /* HBB 20020122: safeguard to prevent division by zero later */
974 if (key_entry_height == 0)
975 key_entry_height = 1;
976
977 /* Key title length and height */
978 key_title_height = 0;
979 key_title_extra = 0;
980 if (key->title.text) {
981 int ytheight;
982 (void) label_width(key->title.text, &ytheight);
983 if (key->title.font)
984 t->set_font(key->title.font);
985 key_title_height = ytheight * t->v_char;
986 if (key->title.font)
987 t->set_font("");
988 if ((*key->title.text) && (t->flags & TERM_ENHANCED_TEXT)
989 && (strchr(key->title.text,'^') || strchr(key->title.text,'_')))
990 key_title_extra = t->v_char;
991 }
992
993 if (key->reverse) {
994 key_sample_left = -key_sample_width;
995 key_sample_right = 0;
996 /* if key width is being used, adjust right-justified text */
997 key_text_left = t->h_char;
998 key_text_right = t->h_char * (max_ptitl_len + 1 + key->width_fix);
999 key_size_left = t->h_char - key_sample_left; /* sample left is -ve */
1000 key_size_right = key_text_right;
1001 } else {
1002 key_sample_left = 0;
1003 key_sample_right = key_sample_width;
1004 /* if key width is being used, adjust left-justified text */
1005 key_text_left = -(int) (t->h_char
1006 * (max_ptitl_len + 1 + key->width_fix));
1007 key_text_right = -(int) t->h_char;
1008 key_size_left = -key_text_left;
1009 key_size_right = key_sample_right + t->h_char;
1010 }
1011 key_point_offset = (key_sample_left + key_sample_right) / 2;
1012
1013 /* advance width for cols */
1014 key_col_wth = key_size_left + key_size_right;
1015
1016 key_rows = ptitl_cnt;
1017 key_cols = 1;
1018
1019 /* calculate rows and cols for key */
1020
1021 if (key->stack_dir == GPKEY_HORIZONTAL) {
1022 /* maximise no cols, limited by label-length */
1023 key_cols = (int) (plot_bounds.xright - plot_bounds.xleft) / key_col_wth;
1024 if (key->maxcols > 0 && key_cols > key->maxcols)
1025 key_cols = key->maxcols;
1026 /* EAM Dec 2004 - Rather than turn off the key, try to squeeze */
1027 if (key_cols == 0) {
1028 key_cols = 1;
1029 key_panic = TRUE;
1030 key_col_wth = (plot_bounds.xright - plot_bounds.xleft);
1031 }
1032 key_rows = (ptitl_cnt + key_cols - 1) / key_cols;
1033 /* now calculate actual no cols depending on no rows */
1034 key_cols = (key_rows == 0) ? 1 : (ptitl_cnt + key_rows - 1) / key_rows;
1035 if (key_cols == 0) {
1036 key_cols = 1;
1037 }
1038 } else {
1039 /* maximise no rows, limited by plot_bounds.ytop-plot_bounds.ybot */
1040 int i = (plot_bounds.ytop - plot_bounds.ybot - key->height_fix * key_entry_height
1041 - key_title_height - key_title_extra)
1042 / key_entry_height;
1043 if (key->maxrows > 0 && i > key->maxrows)
1044 i = key->maxrows;
1045
1046 if (i == 0) {
1047 i = 1;
1048 key_panic = TRUE;
1049 }
1050 if (ptitl_cnt > i) {
1051 key_cols = (ptitl_cnt + i - 1) / i;
1052 /* now calculate actual no rows depending on no cols */
1053 if (key_cols == 0) {
1054 key_cols = 1;
1055 key_panic = TRUE;
1056 }
1057 key_rows = (ptitl_cnt + key_cols - 1) / key_cols;
1058 #if (0)
1059 } else {
1060 /* This was a work-around for a spiderplot bug. No longer needed? */
1061 if (key_rows == 0)
1062 key_rows = i;
1063 #endif
1064 }
1065 }
1066
1067 /* If the key title is wider than the contents, try to make room for it */
1068 if (key->title.text) {
1069 int ytlen = label_width(key->title.text, NULL) - key->swidth + 2;
1070 if (key->title.font)
1071 t->set_font(key->title.font);
1072 ytlen *= t->h_char;
1073 if (ytlen > key_cols * key_col_wth)
1074 key_col_wth = ytlen / key_cols;
1075 if (key->title.font)
1076 t->set_font("");
1077 }
1078
1079 /* Adjust for outside key, leave manually set margins alone */
1080 if ((key->region == GPKEY_AUTO_EXTERIOR_LRTBC && (key->vpos != JUST_CENTRE || key->hpos != CENTRE))
1081 || key->region == GPKEY_AUTO_EXTERIOR_MARGIN) {
1082 int more = 0;
1083 if (key->margin == GPKEY_BMARGIN && bmargin.x < 0) {
1084 more = key_rows * key_entry_height + key_title_height + key_title_extra
1085 + key->height_fix * key_entry_height;
1086 if (plot_bounds.ybot + more > plot_bounds.ytop)
1087 key_panic = TRUE;
1088 else
1089 plot_bounds.ybot += more;
1090 } else if (key->margin == GPKEY_TMARGIN && tmargin.x < 0) {
1091 more = key_rows * key_entry_height + key_title_height + key_title_extra
1092 + key->height_fix * key_entry_height;
1093 if (plot_bounds.ytop - more < plot_bounds.ybot)
1094 key_panic = TRUE;
1095 else
1096 plot_bounds.ytop -= more;
1097 } else if (key->margin == GPKEY_LMARGIN && lmargin.x < 0) {
1098 more = key_col_wth * key_cols;
1099 if (plot_bounds.xleft + more > plot_bounds.xright)
1100 key_panic = TRUE;
1101 else
1102 key_xleft = more;
1103 plot_bounds.xleft += key_xleft;
1104 } else if (key->margin == GPKEY_RMARGIN && rmargin.x < 0) {
1105 more = key_col_wth * key_cols;
1106 if (plot_bounds.xright - more < plot_bounds.xleft)
1107 key_panic = TRUE;
1108 else
1109 plot_bounds.xright -= more;
1110 }
1111 }
1112
1113 /* Restore default font */
1114 if (key->font)
1115 t->set_font("");
1116
1117 /* warn if we had to punt on key size calculations */
1118 if (key_panic)
1119 int_warn(NO_CARET, "Warning - difficulty fitting plot titles into key");
1120 }
1121
1122 int
find_maxl_keys(struct curve_points * plots,int count,int * kcnt)1123 find_maxl_keys(struct curve_points *plots, int count, int *kcnt)
1124 {
1125 int mlen, len, curve, cnt;
1126 int previous_plot_style = 0;
1127 struct curve_points *this_plot;
1128
1129 mlen = cnt = 0;
1130 this_plot = plots;
1131 for (curve = 0; curve < count; this_plot = this_plot->next, curve++) {
1132
1133 if (this_plot->plot_style == PARALLELPLOT)
1134 continue;
1135
1136 if (this_plot->title && !this_plot->title_is_suppressed
1137 && !this_plot->title_position) {
1138 if (this_plot->plot_style == SPIDERPLOT && this_plot->plot_type != KEYENTRY)
1139 ; /* Nothing */
1140 else {
1141 ignore_enhanced(this_plot->title_no_enhanced);
1142 len = estimate_strlen(this_plot->title);
1143 if (len != 0) {
1144 cnt++;
1145 if (len > mlen)
1146 mlen = len;
1147 }
1148 ignore_enhanced(FALSE);
1149 }
1150 }
1151
1152 /* Check for new histogram here and save space for divider */
1153 if (this_plot->plot_style == HISTOGRAMS
1154 && previous_plot_style == HISTOGRAMS
1155 && this_plot->histogram_sequence == 0 && cnt > 1)
1156 cnt++;
1157
1158 /* Check for column-stacked histogram with key entries.
1159 * Same thing for spiderplots.
1160 * This is needed for 'plot ... using col:key(1)'
1161 */
1162 if (this_plot->labels &&
1163 (this_plot->plot_style == HISTOGRAMS || this_plot->plot_style == SPIDERPLOT)) {
1164 text_label *key_entry = this_plot->labels->next;
1165 for (; key_entry; key_entry=key_entry->next) {
1166 cnt++;
1167 len = key_entry->text ? estimate_strlen(key_entry->text) : 0;
1168 if (len > mlen)
1169 mlen = len;
1170 }
1171 }
1172 previous_plot_style = this_plot->plot_style;
1173 }
1174
1175 if (kcnt != NULL)
1176 *kcnt = cnt;
1177 return (mlen);
1178 }
1179
1180 /*
1181 * Make the key sample code a subroutine so that it can eventually be
1182 * shared by the 3d code also. As of now the two code sections are not
1183 * very parallel. EAM Nov 2003
1184 */
1185
1186 void
do_key_sample(struct curve_points * this_plot,legend_key * key,char * title,coordval var_color)1187 do_key_sample(
1188 struct curve_points *this_plot,
1189 legend_key *key,
1190 char *title,
1191 coordval var_color)
1192 {
1193 struct termentry *t = term;
1194 int xl_save = xl;
1195 int yl_save = yl;
1196
1197 /* Clip key box against canvas */
1198 BoundingBox *clip_save = clip_area;
1199 if (term->flags & TERM_CAN_CLIP)
1200 clip_area = NULL;
1201 else
1202 clip_area = &canvas;
1203
1204 /* If the plot this title belongs to specified a non-standard place */
1205 /* for the key sample to appear, use that to override xl, yl. */
1206 if (this_plot->title_position && this_plot->title_position->scalex != character) {
1207 map_position(this_plot->title_position, &xl, &yl, "key sample");
1208 xl -= (key->just == GPKEY_LEFT) ? key_text_left : key_text_right;
1209 }
1210
1211 (*t->layer)(TERM_LAYER_BEGIN_KEYSAMPLE);
1212
1213 if (key->textcolor.type == TC_VARIABLE)
1214 /* Draw key text in same color as plot */
1215 ;
1216 else if (key->textcolor.type != TC_DEFAULT)
1217 /* Draw key text in same color as key title */
1218 apply_pm3dcolor(&key->textcolor);
1219 else
1220 /* Draw key text in black */
1221 (*t->linetype)(LT_BLACK);
1222
1223 if (this_plot->title_is_automated && (t->flags & TERM_IS_LATEX)) {
1224 title = texify_title(title, this_plot->plot_type);
1225 }
1226
1227 if (key->just == GPKEY_LEFT) {
1228 write_multiline(xl + key_text_left, yl, title, LEFT, JUST_CENTRE, 0, key->font);
1229 } else {
1230 if ((*t->justify_text) (RIGHT)) {
1231 write_multiline(xl + key_text_right, yl, title, RIGHT, JUST_CENTRE, 0, key->font);
1232 } else {
1233 int x = xl + key_text_right - t->h_char * estimate_strlen(title);
1234 if (key->region == GPKEY_AUTO_EXTERIOR_LRTBC || /* HBB 990327 */
1235 key->region == GPKEY_AUTO_EXTERIOR_MARGIN ||
1236 inrange((x), (plot_bounds.xleft), (plot_bounds.xright)))
1237 write_multiline(x, yl, title, LEFT, JUST_CENTRE, 0, key->font);
1238 }
1239 }
1240
1241 /* Draw sample in same style and color as the corresponding plot */
1242 /* The variable color case uses the color of the first data point */
1243 if (!check_for_variable_color(this_plot, &var_color))
1244 term_apply_lp_properties(&this_plot->lp_properties);
1245
1246 /* draw sample depending on bits set in plot_style */
1247 if (this_plot->plot_style & PLOT_STYLE_HAS_FILL && t->fillbox) {
1248 struct fill_style_type *fs = &this_plot->fill_properties;
1249 int style = style_from_fill(fs);
1250 int x = xl + key_sample_left;
1251 int y = yl - key_sample_height/4;
1252 int w = key_sample_right - key_sample_left;
1253 int h = key_sample_height/2;
1254
1255 if (this_plot->plot_style == CIRCLES && w > 0) {
1256 do_arc(xl + key_point_offset, yl, key_sample_height/4, 0., 360., style, FALSE);
1257 /* Retrace the border if the style requests it */
1258 if (need_fill_border(fs)) {
1259 do_arc(xl + key_point_offset, yl, key_sample_height/4, 0., 360., 0, FALSE);
1260 }
1261
1262 } else if (this_plot->plot_style == ELLIPSES && w > 0) {
1263 t_ellipse *key_ellipse = (t_ellipse *) gp_alloc(sizeof(t_ellipse),
1264 "cute little ellipse for the key sample");
1265 key_ellipse->center.x = xl + key_point_offset;
1266 key_ellipse->center.y = yl;
1267 key_ellipse->extent.x = w * 2/3;
1268 key_ellipse->extent.y = h;
1269 key_ellipse->orientation = 0.0;
1270 /* already in term coords, no need to map */
1271 do_ellipse(2, key_ellipse, style, FALSE);
1272 /* Retrace the border if the style requests it */
1273 if (need_fill_border(fs)) {
1274 do_ellipse(2, key_ellipse, 0, FALSE);
1275 }
1276 free(key_ellipse);
1277
1278 } else if (w > 0) { /* All other plot types with fill */
1279 if (style != FS_EMPTY)
1280 (*t->fillbox)(style,x,y,w,h);
1281
1282 /* need_fill_border will set the border linetype, but candlesticks don't want it */
1283 if ((this_plot->plot_style == CANDLESTICKS && fs->border_color.type == TC_LT
1284 && fs->border_color.lt == LT_NODRAW)
1285 || style == FS_EMPTY
1286 || need_fill_border(fs)) {
1287 newpath();
1288 draw_clip_line( xl + key_sample_left, yl - key_sample_height/4,
1289 xl + key_sample_right, yl - key_sample_height/4);
1290 draw_clip_line( xl + key_sample_right, yl - key_sample_height/4,
1291 xl + key_sample_right, yl + key_sample_height/4);
1292 draw_clip_line( xl + key_sample_right, yl + key_sample_height/4,
1293 xl + key_sample_left, yl + key_sample_height/4);
1294 draw_clip_line( xl + key_sample_left, yl + key_sample_height/4,
1295 xl + key_sample_left, yl - key_sample_height/4);
1296 closepath();
1297 }
1298 if (fs->fillstyle != FS_EMPTY && fs->fillstyle != FS_DEFAULT
1299 && !(fs->border_color.type == TC_LT && fs->border_color.lt == LT_NODRAW)) {
1300 /* need_fill_border() might have changed our original linetype */
1301 term_apply_lp_properties(&this_plot->lp_properties);
1302 }
1303 }
1304
1305 } else if ((this_plot->plot_style & PLOT_STYLE_HAS_VECTOR) && t->arrow) {
1306 double x1 = xl + key_sample_left;
1307 double y1 = yl;
1308 double x2 = xl + key_sample_right;
1309 double y2 = yl;
1310 apply_head_properties(&(this_plot->arrow_properties));
1311 draw_clip_arrow(x1, y1, x2, y2,
1312 this_plot->arrow_properties.head);
1313
1314 } else if (this_plot->lp_properties.l_type == LT_NODRAW) {
1315 ;
1316
1317 } else if ((this_plot->plot_style & PLOT_STYLE_HAS_ERRORBAR) && this_plot->plot_type != FUNC) {
1318 /* errors for data plots only */
1319 if ((bar_lp.flags & LP_ERRORBAR_SET) != 0)
1320 term_apply_lp_properties(&bar_lp);
1321 draw_clip_line(xl + key_sample_left, yl, xl + key_sample_right, yl);
1322 /* Even if error bars are dotted, the end lines are always solid */
1323 if ((bar_lp.flags & LP_ERRORBAR_SET) != 0)
1324 term->dashtype(DASHTYPE_SOLID,NULL);
1325
1326 } else if ((this_plot->plot_style & PLOT_STYLE_HAS_LINE)) {
1327 draw_clip_line(xl + key_sample_left, yl, xl + key_sample_right, yl);
1328 }
1329
1330 if ((this_plot->plot_type == DATA || this_plot->plot_type == KEYENTRY)
1331 && (this_plot->plot_style & PLOT_STYLE_HAS_ERRORBAR)
1332 && (this_plot->plot_style != CANDLESTICKS)
1333 && (bar_size > 0.0)) {
1334 draw_clip_line( xl + key_sample_left, yl + ERRORBARTIC,
1335 xl + key_sample_left, yl - ERRORBARTIC);
1336 draw_clip_line( xl + key_sample_right, yl + ERRORBARTIC,
1337 xl + key_sample_right, yl - ERRORBARTIC);
1338 }
1339
1340 /* oops - doing the point sample now would break the postscript
1341 * terminal for example, which changes current line style
1342 * when drawing a point, but does not restore it. We must wait to
1343 * draw the point sample at the end of do_plot (comment KEY SAMPLES).
1344 */
1345
1346 (*t->layer)(TERM_LAYER_END_KEYSAMPLE);
1347
1348 /* Restore original linetype for the main plot if we changed it */
1349 if (this_plot->plot_type != FUNC
1350 && (this_plot->plot_style & PLOT_STYLE_HAS_ERRORBAR)
1351 && (bar_lp.flags & LP_ERRORBAR_SET) != 0) {
1352 term_apply_lp_properties(&this_plot->lp_properties);
1353 }
1354
1355 /* Restore previous clipping area */
1356 clip_area = clip_save;
1357 xl = xl_save;
1358 yl = yl_save;
1359 }
1360
1361 void
do_key_sample_point(struct curve_points * this_plot,legend_key * key)1362 do_key_sample_point(
1363 struct curve_points *this_plot,
1364 legend_key *key)
1365 {
1366 struct termentry *t = term;
1367 int xl_save = xl;
1368 int yl_save = yl;
1369
1370 /* If the plot this title belongs to specified a non-standard place
1371 * for the key sample to appear, use that to override xl, yl.
1372 * For "at end|beg" do nothing at all.
1373 */
1374 if (this_plot->title_position) {
1375 if (this_plot->title_position->scalex == character)
1376 return;
1377 map_position(this_plot->title_position, &xl, &yl, "key sample");
1378 xl -= (key->just == GPKEY_LEFT) ? key_text_left : key_text_right;
1379 }
1380
1381 (t->layer)(TERM_LAYER_BEGIN_KEYSAMPLE);
1382
1383 if (this_plot->plot_style == LINESPOINTS
1384 && this_plot->lp_properties.p_interval < 0) {
1385 t_colorspec background_fill = BACKGROUND_COLORSPEC;
1386 (*t->set_color)(&background_fill);
1387 (*t->pointsize)(pointsize * pointintervalbox);
1388 (*t->point)(xl + key_point_offset, yl, 6);
1389 term_apply_lp_properties(&this_plot->lp_properties);
1390 }
1391
1392 if (this_plot->plot_style == BOXPLOT) {
1393 ; /* Don't draw a sample point in the key */
1394
1395 } else if (this_plot->plot_style == DOTS) {
1396 if (on_page(xl + key_point_offset, yl))
1397 (*t->point) (xl + key_point_offset, yl, -1);
1398
1399 } else if (this_plot->plot_style & PLOT_STYLE_HAS_POINT) {
1400 if (this_plot->lp_properties.p_size == PTSZ_VARIABLE)
1401 (*t->pointsize)(pointsize);
1402 if (on_page(xl + key_point_offset, yl)) {
1403 if (this_plot->lp_properties.p_type == PT_CHARACTER) {
1404 if (this_plot->labels->textcolor.type != TC_DEFAULT)
1405 apply_pm3dcolor(&(this_plot->labels->textcolor));
1406 (*t->put_text) (xl + key_point_offset, yl,
1407 this_plot->lp_properties.p_char);
1408 apply_pm3dcolor(&(this_plot->lp_properties.pm3d_color));
1409 } else {
1410 (*t->point) (xl + key_point_offset, yl,
1411 this_plot->lp_properties.p_type);
1412 }
1413 }
1414
1415 } else if (this_plot->plot_style == LABELPOINTS) {
1416 struct text_label *label = this_plot->labels;
1417 if (label->lp_properties.flags & LP_SHOW_POINTS) {
1418 term_apply_lp_properties(&label->lp_properties);
1419 (*t->point) (xl + key_point_offset, yl, label->lp_properties.p_type);
1420 }
1421 }
1422
1423 xl = xl_save;
1424 yl = yl_save;
1425 (t->layer)(TERM_LAYER_END_KEYSAMPLE);
1426 }
1427
1428 /* Graph legend is now optionally done in two passes. The first pass calculates */
1429 /* and reserves the necessary space. Next the individual plots in the graph */
1430 /* are drawn. Then the reserved space for the legend is blanked out, and */
1431 /* finally the second pass through this code draws the legend. */
1432 void
draw_key(legend_key * key,TBOOLEAN key_pass)1433 draw_key(legend_key *key, TBOOLEAN key_pass)
1434 {
1435 struct termentry *t = term;
1436
1437 (t->layer)(TERM_LAYER_KEYBOX);
1438
1439 /* In two-pass mode (set key opaque) we blank out the key box after */
1440 /* the graph is drawn and then redo the key in the blank area. */
1441 if (key_pass && t->fillbox && !(t->flags & TERM_NULL_SET_COLOR)) {
1442 t_colorspec background_fill = BACKGROUND_COLORSPEC;
1443 (*t->set_color)(&background_fill);
1444 (*t->fillbox)(FS_OPAQUE, key->bounds.xleft, key->bounds.ybot,
1445 key_width, key_height);
1446 }
1447
1448 if (key->title.text) {
1449 int title_anchor;
1450 if (key->title.pos == CENTRE)
1451 title_anchor = (key->bounds.xleft + key->bounds.xright) / 2;
1452 else if (key->title.pos == RIGHT)
1453 title_anchor = key->bounds.xright - term->h_char;
1454 else
1455 title_anchor = key->bounds.xleft + term->h_char;
1456
1457 /* Only draw the title once */
1458 if (key_pass || !key->front) {
1459 write_label(title_anchor,
1460 key->bounds.ytop - (key_title_extra + key_entry_height)/2,
1461 &key->title);
1462 (*t->linetype)(LT_BLACK);
1463 }
1464 }
1465
1466 if (key->box.l_type > LT_NODRAW) {
1467 BoundingBox *clip_save = clip_area;
1468 if (term->flags & TERM_CAN_CLIP)
1469 clip_area = NULL;
1470 else
1471 clip_area = &canvas;
1472 term_apply_lp_properties(&key->box);
1473 newpath();
1474 draw_clip_line(key->bounds.xleft, key->bounds.ybot, key->bounds.xleft, key->bounds.ytop);
1475 draw_clip_line(key->bounds.xleft, key->bounds.ytop, key->bounds.xright, key->bounds.ytop);
1476 draw_clip_line(key->bounds.xright, key->bounds.ytop, key->bounds.xright, key->bounds.ybot);
1477 draw_clip_line(key->bounds.xright, key->bounds.ybot, key->bounds.xleft, key->bounds.ybot);
1478 closepath();
1479 /* draw a horizontal line between key title and first entry */
1480 if (key->title.text)
1481 draw_clip_line( key->bounds.xleft,
1482 key->bounds.ytop - (key_title_height + key_title_extra),
1483 key->bounds.xright,
1484 key->bounds.ytop - (key_title_height + key_title_extra));
1485 clip_area = clip_save;
1486 }
1487
1488 yl_ref = key->bounds.ytop - (key_title_height + key_title_extra);
1489 yl_ref -= ((key->height_fix + 1) * key_entry_height) / 2;
1490 xl = key->bounds.xleft + key_size_left;
1491 yl = yl_ref;
1492 }
1493
1494 /*
1495 * This routine draws the plot title, the axis labels, and an optional time stamp.
1496 */
1497 void
draw_titles()1498 draw_titles()
1499 {
1500 struct termentry *t = term;
1501
1502 /* YLABEL */
1503 if (axis_array[FIRST_Y_AXIS].label.text) {
1504 int x = ylabel_x;
1505 int y = (plot_bounds.ytop + plot_bounds.ybot) / 2;
1506 /* There has been much argument about the optimal ylabel position */
1507 x += t->h_char / 4.;
1508 write_label(x, y, &(axis_array[FIRST_Y_AXIS].label));
1509 reset_textcolor(&(axis_array[FIRST_Y_AXIS].label.textcolor));
1510 }
1511
1512 /* Y2LABEL */
1513 if (axis_array[SECOND_Y_AXIS].label.text) {
1514 int x = y2label_x;
1515 int y = (plot_bounds.ytop + plot_bounds.ybot) / 2;
1516 write_label(x, y, &(axis_array[SECOND_Y_AXIS].label));
1517 reset_textcolor(&(axis_array[SECOND_Y_AXIS].label.textcolor));
1518 }
1519
1520 /* XLABEL */
1521 if (axis_array[FIRST_X_AXIS].label.text) {
1522 struct text_label *label = &axis_array[FIRST_X_AXIS].label;
1523 double tmpx, tmpy;
1524 int x, y;
1525 map_position_r(&(label->offset), &tmpx, &tmpy, "xlabel");
1526
1527 x = (plot_bounds.xright + plot_bounds.xleft) / 2;
1528 y = xlabel_y - t->v_char / 2;
1529 y -= tmpy; /* xlabel_y already contained tmpy */
1530
1531 write_label(x, y, label);
1532 reset_textcolor(&(label->textcolor));
1533 }
1534
1535 /* X2LABEL */
1536 if (axis_array[SECOND_X_AXIS].label.text) {
1537 int x, y;
1538 /* we worked out y-coordinate in boundary() */
1539 x = (plot_bounds.xright + plot_bounds.xleft) / 2;
1540 y = x2label_y - t->v_char / 2;
1541 write_label(x, y, &(axis_array[SECOND_X_AXIS].label));
1542 reset_textcolor(&(axis_array[SECOND_X_AXIS].label.textcolor));
1543 }
1544
1545 /* RLABEL */
1546 if (axis_array[POLAR_AXIS].label.text) {
1547 int x, y;
1548
1549 /* This assumes we always have a horizontal R axis */
1550 x = map_x(polar_radius(R_AXIS.max) / 2.0);
1551 y = map_y(0.0) + t->v_char;
1552 write_label(x, y, &(axis_array[POLAR_AXIS].label));
1553 reset_textcolor(&(axis_array[POLAR_AXIS].label.textcolor));
1554 }
1555
1556 /* PLACE TIMELABEL */
1557 if (timelabel.text)
1558 do_timelabel(time_x, time_y);
1559 }
1560
1561 /* advance current position in the key in preparation for next key entry */
1562 void
advance_key(TBOOLEAN only_invert)1563 advance_key(TBOOLEAN only_invert)
1564 {
1565 legend_key *key = &keyT;
1566
1567 if (key->invert)
1568 yl = key->bounds.ybot + yl_ref + key_entry_height/2 - yl;
1569 if (only_invert)
1570 return;
1571 if (key_count >= key_rows) {
1572 yl = yl_ref;
1573 xl += key_col_wth;
1574 key_count = 0;
1575 } else
1576 yl = yl - key_entry_height;
1577 }
1578
1579 /* stupid test used in only one place but it refers to our local variables */
1580 TBOOLEAN
at_left_of_key()1581 at_left_of_key()
1582 {
1583 return (yl == yl_ref);
1584 }
1585