/* This file is part of the GNU plotutils package. Copyright (C) 1989, 1990, 1991, 1995, 1996, 1997, 1998, 1999, 2000, 2005, Free Software Foundation, Inc. The GNU plotutils package is free software. You may redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software foundation; either version 2, or (at your option) any later version. The GNU plotutils package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the GNU plotutils package; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301, USA. */ /* This file contains the point plotter half of GNU graph. The point plotter could easily be linked with other software. It translates a sequence of points, regarded as defining a polyline or a sequence of polylines, to a sequence of libplot calls. There is support for multigraphing, i.e. producing a plot consisting of more than a single graph. Each graph may be drawn from more than one file, i.e., input stream, and each input stream may provide more than a single polyline. A `point' is a structure. Each point structure contains the following fields: x and y coordinates of the point a `have_x_errorbar' flag (true or false) a `have_y_errorbar' flag (true or false) xmin and xmax (meaningful only if have_x_errorbar is set) ymin and ymax (meaningful only if have_y_errorbar is set) a `pendown' flag a symbol type (a small integer, interpreted as a marker type) a symbol size (a fraction of the size of the plotting box) a symbol font name (relevant only for symbol types >= 32) a linemode (a small integer) a linewidth (a fraction of the size of the display device) a polyline fill-fraction (in the interval [0,1], <0 means no fill) a use_color flag (true or false) The point plotter constructs a polyline from each successive run of points that have the pendown flag set. It assumes that the final seven fields are assumed to be the same for each point in such a run, i.e., it takes their values from the first point of the run. At the location of each point on the polyline, the appropriate marker symbol, if any, will be plotted. Symbol types greater than or equal to 32 are interpreted as single characters to be plotted, rather than symbols. Points without the pendown flag set cause the polyline to be broken, and a new one to begin, before the symbol (if any) is plotted. The plotter supports five basic linemodes: 1 through 5. The interpretation of `linemode' depends on the polyline's use_color flag. linemode If monochrome If color 1 solid red 2 dotted green 3 dotdashed blue 4 shortdashed magenta 5 longdashed cyan In the monochrome case, the pattern simply repeats: 6,7,8,9,10 are equivalent to 1,2,3,4,5, etc. In the colored case, the sequence of colors also repeats. But linemodes 1,2,3,4,5 are drawn solid, while 6,7,8,9,10 are drawn dotted, 11,12,13,14,15 are drawn dotdashed, etc. So there are 25 distinct colored linemodes, and 5 distinct monochrome (black) ones. The color of a symbol will be the same as the color of the polyline on which it is plotted. linemodes -1, -2, etc. have a special interpretation. They are `disconnected' linemodes: no polyline will appear, but if color is being used, the color of the plotted symbols (if any) will be linemode-dependent. -1,-2,-3,-4,5 signify red,green,blue,magenta,cyan (the same sequence as for 1,2,3,4,5); thereafter the sequence repeats. linemode 0 is special (for backward compatibility). No line is drawn; symbol #1 (a point) will be used. So using linemode 0 is the same as using linemode -1, symbol 1. The point plotter is invoked by calling the following, in order. new_multigrapher() creates a new point plotter. begin_graph() set_graph_parameters() initializes global structures used by the draw_frame_of_graph() and plot_point() routines. These include the structures that specify the linear transformation from user coordinates to the coordinates used by libplot, and structures that specify the style of the plot frame. draw_frame_of_graph() plots the graph frame. [Optional.] plot_point() uses libplot routines to plot a single point, together with (possibly) a line extending to it from the last point, and a symbol. [Alternatively, plot_point_array() can be used, to plot an array of points.] end_graph() .. [The begin_graph()..end_graph() block can be repeated indefinitely if desired, to create a multigraph. set_graph_parameters() allows for repositioning of later graphs.] .. delete_multigrapher() deletes the point plotter. There is also a function end_polyline_and_flush(), which is useful for real-time display. */ #include "sys-defines.h" #include "libcommon.h" #include "plot.h" #include "extern.h" /* we use floating point libplot coordinates in the range [0,PLOT_SIZE] */ #define PLOT_SIZE 4096.0 #define FUZZ 0.000001 /* bd. on floating pt. roundoff error */ #define NEAR_EQUALITY(a, b, scale) (fabs((a) - (b)) < (FUZZ * fabs(scale))) typedef unsigned int outcode; /* for Cohen-Sutherland clipper */ enum { TOP = 0x1, BOTTOM = 0x2, RIGHT = 0x4, LEFT = 0x8 }; enum { ACCEPTED = 0x1, CLIPPED_FIRST = 0x2, CLIPPED_SECOND = 0x4 }; #define TRIAL_NUMBER_OF_TICK_INTERVALS 5 #define MAX_NUM_SUBTICKS 29 /* max num. of linearly spaced subticks */ #define RELATIVE_SUBTICK_SIZE 0.4 /* subtick_size / tick_size */ /* if a log axis spans >5.0 orders of magnitude, don't plot log subsubticks */ #define MAX_DECADES_WITH_LOG_SUBSUBTICKS 5.0 /* inter-tick spacing types, returned by scale1() and spacing_type() */ #define S_ONE 0 #define S_TWO 1 #define S_FIVE 2 #define S_TWO_FIVE 3 /* we don't use this one, but user may request it */ #define S_UNKNOWN -2 /* valid graph axis layout types; A_LOG2, anyone? */ #define A_LINEAR 0 #define A_LOG10 1 /* The x_trans and y_trans elements of a Multigrapher specify the current linear transformation from user coordinates to device coordinates. They are used both in the plotting of a graph frame, and in the plotting of data points within a graph. */ typedef struct { /* Input (user) coordinates, all floating point. These are the coordinates used in the original data points (or their base-10 logs, for an axis of log type). We'll map them to the unit interval [0.0,1.0]. */ double input_min, input_max; /* min, max */ double input_range; /* max - min, precomputed for speed */ /* If we're reversing axes, we'll then map [0.0,1.0] to [1.0,0.0] */ bool reverse; /* We'll map [0.0,1.0] to another (smaller) interval, linearly */ double squeezed_min, squeezed_max; /* min, max */ double squeezed_range; /* max - min */ /* Output [i.e., libplot] coordinates. The interval [0.0,1.0] will be mapped to this range, and the squeezed interval to a sub-range. This is so that the box within which points are plotted will be smaller than the full area of the graphics display. */ double output_min, output_max; /* min */ double output_range; /* max - min */ } Transform; /* Affine transformation macros */ /* X Scale: convert from user x value to normalized x coordinate (floating point, 0.0 to 1.0). */ #define XS(x) (((x) - multigrapher->x_trans.input_min)/multigrapher->x_trans.input_range) /* X Reflect: map [0,1] to [1,0], if that's called for */ #define XR(x) (multigrapher->x_trans.reverse ? 1.0 - (x) : (x)) /* X Squeeze: map [0,1] range for normalized x coordinate into a smaller interval, the x range for the plotting area within the graphics display */ #define XSQ(x) (multigrapher->x_trans.squeezed_min + (x) * multigrapher->x_trans.squeezed_range) /* X Plot: convert from normalized x coordinate to floating point libplot coordinate. */ #define XP(x) (multigrapher->x_trans.output_min + (x) * multigrapher->x_trans.output_range) /* X Value: convert from user x value (floating point) to floating point libplot coordinate. */ #define XV(x) XP(XSQ(XR(XS(x)))) /* X Normalize: the same, but do not perform reflection if any. (We use this for plotting of axes and their labels.) */ #define XN(y) XP(XSQ(XS(y))) /* Y Scale: convert from user y value to normalized y coordinate (floating point, 0.0 to 1.0). */ #define YS(y) (((y) - multigrapher->y_trans.input_min)/multigrapher->y_trans.input_range) /* Y Reflect: map [0,1] to [1,0], if that's called for */ #define YR(y) (multigrapher->y_trans.reverse ? 1.0 - (y) : (y)) /* Y Squeeze: map [0,1] range for normalized y coordinate into a smaller interval, the y range for the plotting area within the graphics display */ #define YSQ(y) (multigrapher->y_trans.squeezed_min + (y) * multigrapher->y_trans.squeezed_range) /* Y Plot: convert from normalized y coordinate to floating point libplot coordinate. */ #define YP(y) (multigrapher->y_trans.output_min + (y) * multigrapher->y_trans.output_range) /* Y Value: convert from user y value (floating point) to floating point libplot coordinate. (We use this for plotting of points.) */ #define YV(y) YP(YSQ(YR(YS(y)))) /* Y Normalize: the same, but do not perform reflection if any. (We use this for plotting of axes and their labels.) */ #define YN(y) YP(YSQ(YS(y))) /* Size Scale: convert distances, or sizes, from normalized coors to libplot coordinates. (Used for tick, symbol, and font sizes.) The min should really be precomputed. */ #define SS(x) \ (DMIN(multigrapher->x_trans.output_range * multigrapher->x_trans.squeezed_range, \ multigrapher->y_trans.output_range * multigrapher->y_trans.squeezed_range) * (x)) /* The `x_axis' and `y_axis' elements of a Multigrapher, which are of type `Axis', specify the layout of the two axes of a graph. They are used in the drawing of a graph frame. All elements that are doubles are expressed in user coordinates (unless the axis is logarithmic, in which case logs are taken before this structure is filled in). */ /* The `x_axis' and `y_axis' elements are filled in by calls to prepare_axis() when set_graph_parameters() is called. The only exceptions to this are the elements `max_width' and `non_user_ticks', which are filled in by draw_frame_of_graph(), as the frame for a graph is being drawn. */ typedef struct { const char *font_name; /* fontname for axis label and tick labels */ double font_size; /* font size for axis label and tick labels */ const char *label; /* axis label (a string) */ int type; /* axis layout type (A_LINEAR or A_LOG10) */ double tick_spacing; /* distance between ticks */ int min_tick_count, max_tick_count; /* tick location = count * spacing */ bool have_lin_subticks; /* does axis have linearly spaced subticks? */ double lin_subtick_spacing; /* distance between linearly spaced subticks */ int min_lin_subtick_count, max_lin_subtick_count; bool have_normal_subsubticks; /* does axis have logarithmic subsubticks?*/ bool user_specified_subsubticks; /* axis has user-spec'd subsubticks? */ double subsubtick_spacing; /* spacing for user-specified ones */ double other_axis_loc; /* location of intersection w/ other axis */ double alt_other_axis_loc; /* alternative loc. (e.g. right end vs. left)*/ bool switch_axis_end; /* other axis at right/top, not left/bottom? */ bool omit_ticks; /* just plain omit them (and their labels) ? */ double max_label_width; /* max width of labels placed on axis, in libplot coors (we update this during plotting, for y axis only) */ int labelled_ticks; /* number of labelled ticks, subticks, and subsubticks drawn on the axis (we update this during plotting, so we can advise the user to specify a tick spacing by hand if labelled_ticks <= 2) */ } Axis; /* The Multigrapher structure. A pointer to one of these is passed as the first argument to each Multigrapher method (e.g., plot_point()). */ struct MultigrapherStruct { /* multigrapher parameters (not updated over a multigrapher's lifetime) */ plPlotter *plotter; /* GNU libplot Plotter handle */ const char *output_format; /* type of libplot device driver [unused] */ const char *bg_color; /* color of background, if non-NULL */ bool save_screen; /* erase display when opening plotter? */ /* graph parameters (constant over any single graph) */ Transform x_trans, y_trans; /* user->device coor transformations */ Axis x_axis, y_axis; /* information on each axis */ grid_type grid_spec; /* frame specification */ double blankout_fraction; /* 1.0 means blank whole box before drawing */ bool no_rotate_y_label; /* useful for pre-X11R6 X servers */ double tick_size; /* fractional tick size */ double subtick_size; /* fractional subtick size (for linear axes) */ double frame_line_width; /* fractional width of lines in the frame */ double half_line_width; /* approx. half of this, in libplot coors */ const char *frame_color; /* color for frame (and graph, if no -C) */ const char *title; /* graph title */ const char *title_font_name; /* font for graph title */ double title_font_size; /* fractional height of graph title */ int clip_mode; /* 0, 1, or 2 (cf. clipping in gnuplot) */ /* following elements are updated during plotting of points; they're the chief repository for internal state */ bool first_point_of_polyline; /* true only at beginning of each polyline */ double oldpoint_x, oldpoint_y; /* last-plotted point */ int symbol; /* symbol being plotted at each point */ int linemode; /* linemode used for polyline */ }; /* forward references */ static int clip_line (Multigrapher *multigrapher, double *x0_p, double *y0_p, double *x1_p, double *y1_p); static int spacing_type (double spacing); static outcode compute_outcode (Multigrapher *multigrapher, double x, double y, bool tolerant); static void plot_abscissa_log_subsubtick (Multigrapher *multigrapher, double xval); static void plot_errorbar (Multigrapher *multigrapher, const Point *p); static void plot_ordinate_log_subsubtick (Multigrapher *multigrapher, double xval); static void prepare_axis (Axis *axisp, Transform *trans, double min, double max, double spacing, const char *font_name, double font_size, const char *label, double subsubtick_spacing, bool user_specified_subsubticks, bool round_to_next_tick, bool log_axis, bool reverse_axis, bool switch_axis_end, bool omit_ticks); static void print_tick_label (char *labelbuf, const Axis *axis, const Transform *transform, double val); static void scale1 (double min, double max, double *tick_spacing, int *tick_spacing_type); static void set_line_style (Multigrapher *multigrapher, int style, bool use_color); static void transpose_portmanteau (int *val); /* print_tick_label() prints a label on an axis tick. The format depends * on whether the axis is a log axis or a linear axis; also, the magnitude * of the axis labels. */ static void print_tick_label (char *labelbuf, const Axis *axis, const Transform *transform, double val) { int prec; char *eloc, *ptr; char labelbuf_tmp[64], incrbuf[64]; double spacing; bool big_exponents; double min, max; /* two possibilities: large/small exponent magnitudes */ min = (axis->type == A_LOG10 ? pow (10.0, transform->input_min) : transform->input_min); max = (axis->type == A_LOG10 ? pow (10.0, transform->input_max) : transform->input_max); big_exponents = (((min != 0.0 && fabs (log10 (fabs (min))) >= 4.0) || (max != 0.0 && fabs (log10 (fabs (max))) >= 4.0)) ? true : false); if (big_exponents) /* large exponents, rewrite as foo x 10^bar, using escape sequences */ { char *src = labelbuf_tmp, *dst = labelbuf; int exponent; char floatbuf[64]; char *fptr = floatbuf; double prefactor; sprintf (labelbuf_tmp, "%e", val); if ((eloc = strchr (labelbuf_tmp, (int)'e')) == NULL) return; if (axis->type == A_LOG10 && !axis->user_specified_subsubticks) /* a hack: this must be a power of 10, so just print "10^bar" */ { sscanf (++eloc, "%d", &exponent); sprintf (dst, "10\\sp%d\\ep", exponent); return; } /* special case: zero prints as `0', not 0.0x10^whatever */ if (val == 0.0) { *dst++ = '0'; *dst = '\0'; return; } while (src < eloc) *fptr++ = *src++; *fptr = '\0'; sscanf (floatbuf, "%lf", &prefactor); /* get foo */ sscanf (++src, "%d", &exponent); /* get bar */ spacing = (axis->type == A_LINEAR ? axis->tick_spacing : axis->subsubtick_spacing); /* user-specified, for log axis*/ sprintf (incrbuf, "%f", spacing / pow (10.0, (double)exponent)); ptr = strchr (incrbuf, (int)'.'); prec = 0; if (ptr != NULL) { int count = 0; while (*(++ptr)) { count++; if (*ptr != '0') prec = count; } } /* \sp ... \ep is start_superscript ... end_superscript, and \r6 is right-shift by 1/6 em. \mu is the `times' character. */ sprintf (dst, "%.*f\\r6\\mu10\\sp%d\\ep", prec, prefactor, exponent); return; } else /* small-size exponent magnitudes */ { if (axis->type == A_LOG10 && !axis->user_specified_subsubticks) /* a hack: this must be a (small) power of 10, so we'll just use %g format (same as %f, no trailing zeroes) */ { sprintf (labelbuf, "%.9g", val); return; } /* always use no. of digits of precision present in increment */ spacing = (axis->type == A_LINEAR ? axis->tick_spacing : axis->subsubtick_spacing); /* user-specified, for log axis*/ sprintf (incrbuf, "%.9f", spacing); ptr = strchr (incrbuf, (int)'.'); prec = 0; if (ptr != NULL) { int count = 0; while (*(++ptr)) { count++; if (*ptr != '0') prec = count; } } sprintf (labelbuf, "%.*f", prec, val); return; } } /* Algorithm SCALE1, for selecting an inter-tick spacing that will yield a * good-looking linear-format axis. The spacing is always 1.0, 2.0, or 5.0 * times a power of ten. * * Reference: Lewart, C. R., "Algorithms SCALE1, SCALE2, and * SCALE3 for Determination of Scales on Computer Generated * Plots", Communications of the ACM, 10 (1973), 639-640. * Also cited as ACM Algorithm 463. * * We call this routine even when the axis is logarithmic rather than * linear. In that case the arguments are the logs of the actual * arguments, so that it computes an optimal inter-tick factor rather than * an optimal inter-tick spacing. */ /* ARGS: min,max = data min, max tick_spacing = inter-tick spacing tick_spacing_type = 0,1,2 i.e. S_ONE,S_TWO,S_FIVE */ static void scale1 (double min, double max, double *tick_spacing, int *tick_spacing_type) { int k; double nal; double a, b; /* valid interval lengths */ static const double vint[] = { 1.0, 2.0, 5.0, 10.0 }; /* Corresponding breakpoints. The published algorithm uses geometric means, i.e. sqrt(2), sqrt(10), sqrt(50), but using sqrt(10)=3.16... will (if nticks=5, as we choose it to be) cause intervals of length 1.5 to yield an inter-tick distance of 0.2 rather than 0.5. So we could reduce it to 2.95. Similarly we could reduce sqrt(50) to 6.95 so that intervals of length 3.5 will yield an inter-tick distance of 1.0 rather than 0.5. */ static const double sqr[] = { M_SQRT2, 3.16228, 7.07107 }; /* compute trial inter-tick interval length */ a = (max - min) / TRIAL_NUMBER_OF_TICK_INTERVALS; a *= (max > min) ? 1.0 : -1.0; /* paranoia, max>min always */ if (a <= 0.0) { fprintf(stderr, "%s: error: bad trial inter-tick spacing %g\n", progname, a); exit (EXIT_FAILURE); } nal = floor(log10(a)); b = a * pow (10.0, -nal); /* 1.0 <= b < 10.0 */ /* round to closest permissible inter-tick interval length */ k = 0; do { if (b < sqr[k]) break; k++; } while (k < 3); *tick_spacing = (max > min ? 1.0 : -1.0) * vint[k] * pow (10.0, nal); /* for increment type, 0,1,2 means 1,2,5 times a power of 10 */ *tick_spacing_type = (k == 3 ? 0 : k); return; } /* Determine whether an inter-tick spacing (in practice, one specified by the user) is 1.0, 2.0, or 5.0 times a power of 10. */ static int spacing_type (double incr) { int i; int i_tenpower = (int)(floor(log10(incr))); double tenpower = 1.0; bool neg_power = false; if (i_tenpower < 0) { neg_power = true; i_tenpower = -i_tenpower; } for (i = 0; i < i_tenpower; i++) tenpower *= 10; if (neg_power) tenpower = 1.0 / tenpower; if (NEAR_EQUALITY(incr, tenpower, tenpower)) return S_ONE; else if (NEAR_EQUALITY(incr, 2 * tenpower, tenpower)) return S_TWO; else if (NEAR_EQUALITY(incr, 2.5 * tenpower, tenpower)) return S_TWO_FIVE; else if (NEAR_EQUALITY(incr, 5 * tenpower, tenpower)) return S_FIVE; else return S_UNKNOWN; } /* prepare_axis() fills in the Axis structure for an axis, and some of * the linear transformation variables in the Transform structure also. */ /* ARGS: user_specified_subticks = linear ticks on a log axis? round_to_next_tick = round limits to next tick mark? log_axis = log axis? reverse_axis = reverse min, max? switch_axis_end = intersection w/ other axis in alt. pos.? omit_ticks = suppress all ticks and tick labels? */ static void prepare_axis (Axis *axisp, Transform *trans, double min, double max, double spacing, const char *font_name, double font_size, const char *label, double subsubtick_spacing, bool user_specified_subsubticks, bool round_to_next_tick, bool log_axis, bool reverse_axis, bool switch_axis_end, bool omit_ticks) { double range; int tick_spacing_type = 0; double tick_spacing, lin_subtick_spacing; int min_tick_count, max_tick_count; int min_lin_subtick_count, max_lin_subtick_count; bool have_lin_subticks; if (min > max) /* paranoia, max < min is swapped at top level */ { fprintf(stderr, "%s: error: min > max for axis, not allowed\n", progname); exit (EXIT_FAILURE); } if (min == max) /* expand in a clever way */ { max = floor (max + 1.0); min = ceil (min - 1.0); } if (log_axis) /* log axis, data are stored in logarithmic form */ /* compute a tick spacing; user can't specify it */ { scale1 (min, max, &tick_spacing, &tick_spacing_type); if (tick_spacing <= 1.0) { tick_spacing = 1.0; tick_spacing_type = S_ONE; } } else /* linear axis */ { if (spacing == 0.0) /* i.e., not specified by user */ scale1 (min, max, &tick_spacing, &tick_spacing_type); else /* luser is boss, don't use SCALE1 */ { tick_spacing = spacing; tick_spacing_type = spacing_type (spacing); } } range = max - min; /* range is not negative */ if (round_to_next_tick) /* expand both limits to next tick */ { if (user_specified_subsubticks) /* Special Case. If user specified the `spacing' argument to -x or -y on a logarithmic axis, our usual tick-generating and tick-plotting algorithms are disabled. So we don't bother with min_tick_count or several other fields of the axis struct; instead we just compute a new (rounded) max, min, and range. Since most data are stored as logs, this is complicated. */ { double true_min = pow (10.0, min), true_max = pow (10.0, max); double true_range = true_max - true_min; int min_count, max_count; min_count = (int)(floor ((true_min + FUZZ * true_range) / subsubtick_spacing)); max_count = (int)(ceil ((true_max - FUZZ * true_range) / subsubtick_spacing)); /* avoid core dump, do *not* reduce minimum to zero! */ if (min_count > 0) min = log10 (min_count * subsubtick_spacing); max = log10 (max_count * subsubtick_spacing); range = max - min; min_tick_count = max_tick_count = 0; /* keep gcc happy */ } else /* normal `expand limits to next tick' case */ { min_tick_count = (int)(floor((min + FUZZ * range)/ tick_spacing)); max_tick_count = (int)(ceil((max - FUZZ * range)/ tick_spacing)); /* max_tick_count > min_tick_count always */ /* tickval = tick_spacing * count, for all count in [min_count,max_count]; must have >=2 ticks */ min = tick_spacing * min_tick_count; max = tick_spacing * max_tick_count; range = max - min; } } else /* don't expand limits to next tick */ { min_tick_count = (int)(ceil((min - FUZZ * range)/ tick_spacing)); max_tick_count = (int)(floor((max + FUZZ * range)/ tick_spacing)); /* max_tick_count <= min_tick_count is possible */ /* tickval = incr * count, for all count in [min_count,max_count]; can have 0,1,2,3... ticks */ } /* Allow 5 subticks per tick if S_FIVE or S_TWO_FIVE, 2 if S_TWO. Case S_ONE is special; we try 10, 5, and 2 in succession */ switch (tick_spacing_type) { case S_FIVE: case S_TWO_FIVE: lin_subtick_spacing = tick_spacing / 5; break; case S_TWO: lin_subtick_spacing = tick_spacing / 2; break; case S_ONE: lin_subtick_spacing = tick_spacing / 10; min_lin_subtick_count = (int)(ceil((min - FUZZ * range)/ lin_subtick_spacing)); max_lin_subtick_count = (int)(floor((max + FUZZ * range)/ lin_subtick_spacing)); if (max_lin_subtick_count - min_lin_subtick_count > MAX_NUM_SUBTICKS) { lin_subtick_spacing = tick_spacing / 5; min_lin_subtick_count = (int)(ceil((min - FUZZ * range)/ lin_subtick_spacing)); max_lin_subtick_count = (int)(floor((max + FUZZ * range)/ lin_subtick_spacing)); if (max_lin_subtick_count - min_lin_subtick_count > MAX_NUM_SUBTICKS) lin_subtick_spacing = tick_spacing / 2; } break; default: /* in default case, i.e. S_UNKNOWN, we won't plot linear subticks */ lin_subtick_spacing = tick_spacing; /* not actually needed, since not plotted */ break; } /* smallest possible inter-subtick factor for a log axis is 10.0 */ if (log_axis && lin_subtick_spacing <= 1.0) lin_subtick_spacing = 1.0; min_lin_subtick_count = (int)(ceil((min - FUZZ * range)/ lin_subtick_spacing)); max_lin_subtick_count = (int)(floor((max + FUZZ * range)/ lin_subtick_spacing)); have_lin_subticks = ((tick_spacing_type != S_UNKNOWN /* S_UNKNOWN -> no subticks */ && (max_lin_subtick_count - min_lin_subtick_count) <= MAX_NUM_SUBTICKS) ? true : false); /* fill in parameters for axis-specific affine transformation */ trans->input_min = min; trans->input_max = max; trans->input_range = range; /* precomputed for speed */ trans->reverse = reverse_axis; /* fill in axis-specific plot frame variables */ axisp->switch_axis_end = switch_axis_end; axisp->omit_ticks = omit_ticks; axisp->label = label; axisp->font_name = font_name; axisp->font_size = font_size; axisp->max_label_width = 0.0; axisp->type = log_axis ? A_LOG10 : A_LINEAR; axisp->tick_spacing = tick_spacing; axisp->min_tick_count = min_tick_count; axisp->max_tick_count = max_tick_count; axisp->have_lin_subticks = have_lin_subticks; axisp->lin_subtick_spacing = lin_subtick_spacing; axisp->min_lin_subtick_count = min_lin_subtick_count; axisp->max_lin_subtick_count = max_lin_subtick_count; axisp->user_specified_subsubticks = user_specified_subsubticks; axisp->subsubtick_spacing = subsubtick_spacing; axisp->labelled_ticks = 0; /* updated during drawing of frame */ if (log_axis) /* logarithmic axis */ /* do we have special logarithmic subsubticks, and should we label them? */ { if (max - min <= MAX_DECADES_WITH_LOG_SUBSUBTICKS + FUZZ) /* not too many orders of magnitude, so plot normal log subsubticks */ axisp->have_normal_subsubticks = true; else /* too many orders of magnitude, don't plot log subsubticks */ axisp->have_normal_subsubticks = false; } else /* linear axes don't have log subsubticks */ axisp->have_normal_subsubticks = false; } /* The following routines [new_multigrapher(), begin_graph(), * set_graph_parameters(), draw_frame_of_graph(), plot_point(), * end_graph(), delete_multigrapher()] are the basic routines of the * point-plotter . See descriptions at the head of this file. */ /* Create a new Multigrapher. The arguments, after the first, are the libplot Plotter parameters that the `graph' user can set on the command line. */ Multigrapher * new_multigrapher (const char *output_format, const char *bg_color, const char *bitmap_size, const char *emulate_color, const char *max_line_length, const char *meta_portable, const char *page_size, const char *rotation_angle, bool save_screen) { plPlotterParams *plotter_params; plPlotter *plotter; Multigrapher *multigrapher; multigrapher = (Multigrapher *)xmalloc (sizeof (Multigrapher)); /* set Plotter parameters */ plotter_params = pl_newplparams (); pl_setplparam (plotter_params, "BG_COLOR", (void *)bg_color); pl_setplparam (plotter_params, "BITMAPSIZE", (void *)bitmap_size); pl_setplparam (plotter_params, "EMULATE_COLOR", (void *)emulate_color); pl_setplparam (plotter_params, "MAX_LINE_LENGTH", (void *)max_line_length); pl_setplparam (plotter_params, "META_PORTABLE", (void *)meta_portable); pl_setplparam (plotter_params, "PAGESIZE", (void *)page_size); pl_setplparam (plotter_params, "ROTATION", (void *)rotation_angle); /* create Plotter and open it */ plotter = pl_newpl_r (output_format, NULL, stdout, stderr, plotter_params); if (plotter == (plPlotter *)NULL) return (Multigrapher *)NULL; pl_deleteplparams (plotter_params); multigrapher->plotter = plotter; if (pl_openpl_r (plotter) < 0) return (Multigrapher *)NULL; multigrapher->bg_color = bg_color; /* if called for, erase it; set up the user->device coor map */ if (!save_screen || bg_color) pl_erase_r (plotter); pl_fspace_r (plotter, 0.0, 0.0, (double)PLOT_SIZE, (double)PLOT_SIZE); return multigrapher; } int delete_multigrapher (Multigrapher *multigrapher) { int retval; retval = pl_closepl_r (multigrapher->plotter); if (retval >= 0) retval = pl_deletepl_r (multigrapher->plotter); free (multigrapher); return retval; } void begin_graph (Multigrapher *multigrapher, double scale, double trans_x, double trans_y) { pl_savestate_r (multigrapher->plotter); pl_fconcat_r (multigrapher->plotter, scale, 0.0, 0.0, scale, trans_x * PLOT_SIZE, trans_y * PLOT_SIZE); } void end_graph (Multigrapher *multigrapher) { pl_restorestate_r (multigrapher->plotter); } /* ARGS: Multigrapher *multigrapher double frame_line_width = fractional width of lines in the frame const char *frame_color = color for frame (and plot if no -C option) const char *title = graph title const char *title_font_name = font for graph title (string) double title_font_size = font size for graph title double tick_size = fractional size of ticks grid_type grid_spec = gridstyle (and tickstyle) spec double x_min, x_max, x_spacing double y_min, y_max, y_spacing bool spec_x_spacing bool spec_y_spacing double width, height, up, right const char *x_font_name double x_font_size const char *x_label const char *y_font_name double y_font_size const char *y_label bool no_rotate_y_label -- portmanteaux args -- int log_axis = whether axes should be logarithmic int round_to_next_tick = round limits to the next tick mark int switch_axis_end = put axes at right/top, not left/bottom? int omit_ticks = omit all ticks, tick labels from an axis? -- other args -- int clip_mode = 0, 1, or 2 double blankout_fraction = 1.0 means blank out whole box before plot bool transpose_axes */ void set_graph_parameters (Multigrapher *multigrapher, double frame_line_width, const char *frame_color, const char *title, const char *title_font_name, double title_font_size, double tick_size, grid_type grid_spec, double x_min, double x_max, double x_spacing, double y_min, double y_max, double y_spacing, bool spec_x_spacing, bool spec_y_spacing, double width, double height, double up, double right, const char *x_font_name, double x_font_size, const char *x_label, const char *y_font_name, double y_font_size, const char *y_label, bool no_rotate_y_label, int log_axis, int round_to_next_tick, int switch_axis_end, int omit_ticks, int clip_mode, double blankout_fraction, bool transpose_axes) { double x_subsubtick_spacing = 0.0, y_subsubtick_spacing = 0.0; /* local portmanteau variables */ int reverse_axis = 0; /* min > max on an axis? */ int user_specified_subsubticks = 0; /* i.e. linear ticks on a log axis? */ if (log_axis & X_AXIS) { if (spec_x_spacing) /* spacing is handled specially for log axes */ { spec_x_spacing = false; user_specified_subsubticks |= X_AXIS; x_subsubtick_spacing = x_spacing; } } if (log_axis & Y_AXIS) { if (spec_y_spacing) { /* spacing is handled specially for log axes */ spec_y_spacing = false; user_specified_subsubticks |= Y_AXIS; y_subsubtick_spacing = y_spacing; } } /* check for reversed axes (min > max) */ if (x_max < x_min) { reverse_axis |= X_AXIS; { double temp; temp = x_min; x_min = x_max; x_max = temp; } } if (x_max == x_min) { fprintf (stderr, "%s: separating identical upper and lower x limits\n", progname); /* separate them */ x_max += 1.0; x_min -= 1.0; } /* check for reversed axes (min > max) */ if (y_max < y_min) { reverse_axis |= Y_AXIS; { double temp; temp = y_min; y_min = y_max; y_max = temp; } } if (y_max == y_min) { fprintf (stderr, "%s: separating identical upper and lower y limits\n", progname); /* separate them */ y_max += 1.0; y_min -= 1.0; } /* At this point, min < max for each axis, if the user specified the two limits on an axis; reverse_axis portmanteau variable keeps track of whether either axis was discovered to be reversed. */ /* silently accept negative spacing as equivalent as positive */ if (spec_x_spacing) { if (x_spacing == 0.0) { fprintf (stderr, "%s: error: zero spacing between ticks on an axis\n", progname); exit (EXIT_FAILURE); } x_spacing = fabs (x_spacing); } if (spec_y_spacing) { if (y_spacing == 0.0) { fprintf (stderr, "%s: error: zero spacing between ticks on an axis\n", progname); exit (EXIT_FAILURE); } y_spacing = fabs (y_spacing); } /* now transpose the two axes (i.e. their portmanteau variables, labels, limits etc.) if transpose_axes was set */ if (transpose_axes) { const char *temp_string; double temp_double; transpose_portmanteau (&log_axis); transpose_portmanteau (&round_to_next_tick); transpose_portmanteau (&switch_axis_end); transpose_portmanteau (&omit_ticks); transpose_portmanteau (&reverse_axis); transpose_portmanteau (&user_specified_subsubticks); temp_string = x_label; x_label = y_label; y_label = temp_string; temp_double = x_min; x_min = y_min; y_min = temp_double; temp_double = x_max; x_max = y_max; y_max = temp_double; temp_double = x_spacing; x_spacing = y_spacing; y_spacing = temp_double; temp_double = x_subsubtick_spacing; x_subsubtick_spacing = y_subsubtick_spacing; y_subsubtick_spacing = temp_double; } /* fill in the Multigrapher struct */ multigrapher->frame_line_width = frame_line_width; multigrapher->frame_color = frame_color; multigrapher->no_rotate_y_label = no_rotate_y_label; multigrapher->blankout_fraction = blankout_fraction; if (title != NULL) multigrapher->title = xstrdup (title); else multigrapher->title = NULL; if (title_font_name != NULL) multigrapher->title_font_name = xstrdup (title_font_name); else multigrapher->title_font_name = NULL; multigrapher->title_font_size = title_font_size; multigrapher->tick_size = tick_size; multigrapher->subtick_size = RELATIVE_SUBTICK_SIZE * tick_size; multigrapher->grid_spec = grid_spec; multigrapher->clip_mode = clip_mode; /* fill in the Transform and Axis elements for each coordinate */ prepare_axis (&multigrapher->x_axis, &multigrapher->x_trans, x_min, x_max, x_spacing, x_font_name, x_font_size, x_label, x_subsubtick_spacing, (bool)(user_specified_subsubticks & X_AXIS), (bool)(round_to_next_tick & X_AXIS), (bool)(log_axis & X_AXIS), (bool)(reverse_axis & X_AXIS), (bool)(switch_axis_end & X_AXIS), (bool)(omit_ticks & X_AXIS)); prepare_axis (&multigrapher->y_axis, &multigrapher->y_trans, y_min, y_max, y_spacing, y_font_name, y_font_size, y_label, y_subsubtick_spacing, (bool)(user_specified_subsubticks & Y_AXIS), (bool)(round_to_next_tick & Y_AXIS), (bool)(log_axis & Y_AXIS), (bool)(reverse_axis & Y_AXIS), (bool)(switch_axis_end & Y_AXIS), (bool)(omit_ticks & Y_AXIS)); /* fill in additional parameters in the two Transform structures */ multigrapher->x_trans.squeezed_min = right; multigrapher->x_trans.squeezed_max = right + width; multigrapher->x_trans.squeezed_range = width; multigrapher->y_trans.squeezed_min = up; multigrapher->y_trans.squeezed_max = up + height; multigrapher->y_trans.squeezed_range = height; /* specify interval range for each coordinate, in libplot units */ multigrapher->x_trans.output_min = 0.0; multigrapher->x_trans.output_max = (double)PLOT_SIZE; multigrapher->x_trans.output_range = multigrapher->x_trans.output_max - multigrapher->x_trans.output_min; multigrapher->x_trans.output_min = 0.0; multigrapher->y_trans.output_max = (double)PLOT_SIZE; multigrapher->y_trans.output_range = multigrapher->y_trans.output_max - multigrapher->y_trans.output_min; /* fill in fields in Axis structs dealing with location of other axis */ if (multigrapher->grid_spec != AXES_AT_ORIGIN) /* Normal case */ { /* axes are at left/bottom */ multigrapher->x_axis.other_axis_loc = multigrapher->x_trans.input_min; multigrapher->y_axis.other_axis_loc = multigrapher->y_trans.input_min; /* secondary axes (used only if --switch-axis-end is specified) */ multigrapher->x_axis.alt_other_axis_loc = multigrapher->x_trans.input_max; multigrapher->y_axis.alt_other_axis_loc = multigrapher->y_trans.input_max; } else /* Special case: grid type #4, AXES_AT_ORIGIN */ { /* In this case (grid type #4), we don't allow the user to move the axis position by using the --switch-axis-end option. Each axis is at the value 0 (the origin) if the value 0 is between the limits of the opposing axis. Otherwise, the position is at the end closer to the value of 0. */ multigrapher->x_axis.other_axis_loc = (multigrapher->x_trans.input_min * multigrapher->x_trans.input_max <= 0.0) ? 0.0 : (multigrapher->x_trans.input_min > 0.0 ? multigrapher->x_trans.input_min : multigrapher->x_trans.input_max); multigrapher->y_axis.other_axis_loc = (multigrapher->y_trans.input_min * multigrapher->y_trans.input_max <= 0.0) ? 0.0 : (multigrapher->y_trans.input_min > 0.0 ? multigrapher->y_trans.input_min : multigrapher->y_trans.input_max); /* secondary axes are the same */ multigrapher->x_axis.alt_other_axis_loc = multigrapher->x_axis.other_axis_loc; multigrapher->y_axis.alt_other_axis_loc = multigrapher->y_axis.other_axis_loc; multigrapher->x_axis.switch_axis_end = (((multigrapher->x_trans.input_max - multigrapher->x_axis.other_axis_loc) < (multigrapher->x_axis.other_axis_loc - multigrapher->x_trans.input_min)) ? true : false); multigrapher->y_axis.switch_axis_end = (((multigrapher->y_trans.input_max - multigrapher->y_axis.other_axis_loc) < (multigrapher->y_axis.other_axis_loc - multigrapher->y_trans.input_min)) ? true : false); } /* The following is a version of (multigrapher->frame_line_width)/2 (expressed in terms of libplot coordinates) which the plotter uses as an offset, to get highly accurate positioning of ticks and labels. */ if (frame_line_width < 0.0 || pl_havecap_r (multigrapher->plotter, "WIDE_LINES") == 0) multigrapher->half_line_width = 0.0;/* N.B. <0.0 -> default width, pres. small */ else multigrapher->half_line_width = 0.5 * frame_line_width * multigrapher->x_trans.output_range; /* initialize the plotter state variables */ multigrapher->first_point_of_polyline = true; multigrapher->oldpoint_x = 0.0; multigrapher->oldpoint_y = 0.0; } /* draw_frame_of_graph() plots the graph frame (grid plus axis labels and title), using the multigrapher's variables (the multigrapher->x_axis, multigrapher->y_axis, multigrapher->x_trans, multigrapher->y_trans structures). It comprises several almost-independent tasks: 0. draw opaque white rectangle (``canvas''), if requested 1. draw title, if any, above drawing box 2. draw axes, and a full drawing box if requested 3. draw ticks, grid lines, and tick labels on abscissa; also subticks, if abscissa is a linear axis 4. draw ticks, grid lines, and tick labels on ordinate, also subticks, if ordinate is a linear axis 5. draw sub-tick marks, sub-grid lines, and sub-labels, if abscissa is a logarithmic axis 6. draw sub-tick marks, sub-grid lines, and sub-labels, if ordinate is a logarithmic axis 7. draw axis label on abscissa 8. draw axis label on ordinate (normally rotated 90 degrees) A savestate()--restorestate() pair is wrapped around these nine tasks. They are not quite independent because in (4) and (6) we keep track of the maximum width of the tick labels on the ordinate, to position the rotated ordinate label properly in (8). At the conclusion of the eight tasks, we warn the user if (i) he/she didn't specify a tick spacing for a logarithmic axis by hand, and (ii) our default algorithm for drawing ticks on a logarithmic axis has drawn too few ticks (i.e. <= 2 ticks) on the axis. Task #0 (drawing a white canvas) isn't always performed; only if the argument draw_canvas is set. It isn't set when we call this function for the first time; see graph.c. */ void draw_frame_of_graph (Multigrapher *multigrapher, bool draw_canvas) { static bool tick_warning_printed = false; /* when too few labelled ticks */ /* wrap savestate()--restorestate() around all 9 tasks */ pl_savestate_r (multigrapher->plotter); /* set color for graph frame */ if (multigrapher->frame_color) pl_pencolorname_r (multigrapher->plotter, multigrapher->frame_color); /* set line width as a fraction of size of display, <0.0 means default */ pl_flinewidth_r (multigrapher->plotter, multigrapher->frame_line_width * (double)PLOT_SIZE); /* axes (or box) will be drawn in solid line style */ pl_linemod_r (multigrapher->plotter, "solid"); /* turn off filling */ pl_filltype_r (multigrapher->plotter, 0); /* 0. DRAW AN OPAQUE WHITE BOX */ if (draw_canvas) { pl_savestate_r (multigrapher->plotter); /* use user-specified background color (if any) instead of white */ if (pl_havecap_r (multigrapher->plotter, "SETTABLE_BACKGROUND") != 0 && multigrapher->bg_color) pl_colorname_r (multigrapher->plotter, multigrapher->bg_color); else pl_colorname_r (multigrapher->plotter, "white"); pl_filltype_r (multigrapher->plotter, 1); /* turn on filling */ pl_fbox_r (multigrapher->plotter, XP(XSQ(0.5 - 0.5 * multigrapher->blankout_fraction)), YP(YSQ(0.5 - 0.5 * multigrapher->blankout_fraction)), XP(XSQ(0.5 + 0.5 * multigrapher->blankout_fraction)), YP(YSQ(0.5 + 0.5 * multigrapher->blankout_fraction))); pl_restorestate_r (multigrapher->plotter); } /* 1. DRAW THE TITLE, I.E. THE TOP LABEL */ if (multigrapher->grid_spec != NO_AXES && !multigrapher->y_axis.switch_axis_end /* no title if x axis is at top of plot */ && multigrapher->title != NULL && *multigrapher->title != '\0') { double title_font_size; /* switch to our font for drawing title */ pl_fontname_r (multigrapher->plotter, multigrapher->title_font_name); title_font_size = pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->title_font_size)); pl_fmove_r (multigrapher->plotter, XP(XSQ(0.5)), YP(YSQ(1.0 + (((multigrapher->grid_spec == AXES_AND_BOX || multigrapher->grid_spec == AXES) && (multigrapher->tick_size <= 0.0) ? 1.0 : 0.5) * fabs(multigrapher->tick_size)))) + 0.65 * title_font_size + multigrapher->half_line_width); /* title centered, bottom spec'd */ pl_alabel_r (multigrapher->plotter, 'c', 'b', multigrapher->title); } /* 2. DRAW AXES FOR THE PLOT */ switch (multigrapher->grid_spec) { case AXES_AND_BOX_AND_GRID: case AXES_AND_BOX: /* draw a box, not just a pair of axes */ pl_fbox_r (multigrapher->plotter, XP(XSQ(0.0)), YP(YSQ(0.0)), XP(XSQ(1.0)), YP(YSQ(1.0))); break; case AXES: { double xstart, ystart, xmid, ymid, xend, yend; xstart = (multigrapher->x_axis.switch_axis_end ? XN(multigrapher->x_axis.other_axis_loc) - multigrapher->half_line_width : XN(multigrapher->x_axis.alt_other_axis_loc) + multigrapher->half_line_width); ystart = (multigrapher->y_axis.switch_axis_end ? YN(multigrapher->y_axis.alt_other_axis_loc) : YN(multigrapher->y_axis.other_axis_loc)); xmid = (multigrapher->x_axis.switch_axis_end ? XN(multigrapher->x_axis.alt_other_axis_loc) : XN(multigrapher->x_axis.other_axis_loc)); ymid = ystart; xend = xmid; yend = (multigrapher->y_axis.switch_axis_end ? YN(multigrapher->y_axis.other_axis_loc) - multigrapher->half_line_width : YN(multigrapher->y_axis.alt_other_axis_loc) + multigrapher->half_line_width); pl_fmove_r (multigrapher->plotter, xstart, ystart); pl_fcont_r (multigrapher->plotter, xmid, ymid); pl_fcont_r (multigrapher->plotter, xend, yend); } break; case AXES_AT_ORIGIN: { double xpos, ypos; xpos = (multigrapher->x_axis.switch_axis_end ? XN(multigrapher->x_axis.other_axis_loc) : XN(multigrapher->x_axis.alt_other_axis_loc)); ypos = (multigrapher->y_axis.switch_axis_end ? YN(multigrapher->y_axis.alt_other_axis_loc) : YN(multigrapher->y_axis.other_axis_loc)); pl_fline_r (multigrapher->plotter, xpos, YP(YSQ(0.0)) - multigrapher->half_line_width, xpos, YP(YSQ(1.0)) + multigrapher->half_line_width); pl_fline_r (multigrapher->plotter, XP(XSQ(0.0)) - multigrapher->half_line_width, ypos, XP(XSQ(1.0)) + multigrapher->half_line_width, ypos); } break; case NO_AXES: default: break; } /* 3. PLOT TICK MARKS, GRID LINES, AND TICK LABELS ON ABSCISSA */ if (multigrapher->grid_spec != NO_AXES && !multigrapher->x_axis.omit_ticks && !multigrapher->x_axis.user_specified_subsubticks) { int i; double xval, xrange = multigrapher->x_trans.input_max - multigrapher->x_trans.input_min; /* there is no way you could use longer labels on tick marks! */ char labelbuf[2048]; /* switch to our font for drawing x axis label and tick labels */ pl_fontname_r (multigrapher->plotter, multigrapher->x_axis.font_name); pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->x_axis.font_size)); for (i = multigrapher->x_axis.min_tick_count; i <= multigrapher->x_axis.max_tick_count; i++) /* tick range can be empty */ { xval = i * multigrapher->x_axis.tick_spacing; /* discard tick locations outside plotting area */ if (xval < multigrapher->x_trans.input_min - FUZZ * xrange || xval > multigrapher->x_trans.input_max + FUZZ * xrange) continue; /* Plot the abscissa tick labels. */ if (!multigrapher->y_axis.switch_axis_end && !(multigrapher->grid_spec == AXES_AT_ORIGIN /* don't plot label if it could run into an axis */ && NEAR_EQUALITY (xval, multigrapher->x_axis.other_axis_loc, multigrapher->x_trans.input_range) && (multigrapher->y_axis.other_axis_loc != multigrapher->y_trans.input_min) && (multigrapher->y_axis.other_axis_loc != multigrapher->y_trans.input_max))) /* print labels below bottom boundary */ { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc) - (SS ((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75) * fabs(multigrapher->tick_size)) + multigrapher->half_line_width)); print_tick_label (labelbuf, &multigrapher->x_axis, &multigrapher->x_trans, (multigrapher->x_axis.type == A_LOG10) ? pow (10.0, xval) : xval); pl_alabel_r (multigrapher->plotter, 'c', 't', labelbuf); multigrapher->x_axis.labelled_ticks++; } else /* print labels above top boundary */ if (multigrapher->y_axis.switch_axis_end && !(multigrapher->grid_spec == AXES_AT_ORIGIN /* don't plot label if it could run into an axis */ && NEAR_EQUALITY (xval, multigrapher->x_axis.other_axis_loc, multigrapher->x_trans.input_range) && (multigrapher->y_axis.other_axis_loc != multigrapher->y_trans.input_min) && (multigrapher->y_axis.other_axis_loc != multigrapher->y_trans.input_max))) { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc) + (SS ((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75) * fabs(multigrapher->tick_size)) + multigrapher->half_line_width)); print_tick_label (labelbuf, &multigrapher->x_axis, &multigrapher->x_trans, (multigrapher->x_axis.type == A_LOG10) ? pow (10.0, xval) : xval); pl_alabel_r (multigrapher->plotter, 'c', 'b', labelbuf); multigrapher->x_axis.labelled_ticks++; } /* Plot the abscissa tick marks, and vertical grid lines. */ switch (multigrapher->grid_spec) { case AXES_AND_BOX_AND_GRID: pl_linemod_r (multigrapher->plotter, "dotted"); pl_fmove_r (multigrapher->plotter, XV(xval), YP(YSQ(0.0))); pl_fcont_r (multigrapher->plotter, XV(xval), YP(YSQ(1.0))); pl_linemod_r (multigrapher->plotter, "solid"); /* fall through */ case AXES_AND_BOX: if (!multigrapher->y_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc) - (SS (multigrapher->tick_size) + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } else { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc) + (SS (multigrapher->tick_size) + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } /* fall through */ case AXES: case AXES_AT_ORIGIN: if (!multigrapher->y_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc) + (SS (multigrapher->tick_size) + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } else { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc) - (SS (multigrapher->tick_size) + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } break; default: /* shouldn't happen */ break; } } if (multigrapher->x_axis.have_lin_subticks) { double subtick_size; /* libplot coordinates */ /* linearly spaced subticks on log axes are as long as reg. ticks */ subtick_size = (multigrapher->x_axis.type == A_LOG10 ? SS(multigrapher->tick_size) : SS(multigrapher->subtick_size)); /* Plot the linearly spaced subtick marks on the abscissa */ for (i = multigrapher->x_axis.min_lin_subtick_count; i <= multigrapher->x_axis.max_lin_subtick_count; i++) /* tick range can be empty */ { xval = i * multigrapher->x_axis.lin_subtick_spacing; /* discard subtick locations outside plotting area */ if (xval < multigrapher->x_trans.input_min - FUZZ * xrange || xval > multigrapher->x_trans.input_max + FUZZ * xrange) continue; switch (multigrapher->grid_spec) { case AXES_AND_BOX_AND_GRID: case AXES_AND_BOX: /* draw on both sides */ if (!multigrapher->y_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc) - (subtick_size + (subtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } else { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc) + (subtick_size + (subtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } /* fall through */ case AXES: case AXES_AT_ORIGIN: if (!multigrapher->y_axis.switch_axis_end) /* draw on only one side */ { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc) + (subtick_size + (subtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } else { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc) - (subtick_size + (subtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } break; default: /* shouldn't happen */ break; } } } /* plot a vertical dotted line at x = 0 */ if (multigrapher->grid_spec != AXES_AT_ORIGIN && multigrapher->x_axis.type == A_LINEAR && multigrapher->x_trans.input_min * multigrapher->x_trans.input_max < 0.0) { pl_linemod_r (multigrapher->plotter, "dotted"); pl_fline_r (multigrapher->plotter, XV(0.0), YP(YSQ(0.0)), XV(0.0), YP(YSQ(1.0))); pl_linemod_r (multigrapher->plotter, "solid"); } } /* 4. PLOT TICK MARKS, GRID LINES, AND TICK LABELS ON ORDINATE */ if (multigrapher->grid_spec != NO_AXES && !multigrapher->y_axis.omit_ticks && !multigrapher->y_axis.user_specified_subsubticks) { int i; double yval, yrange = multigrapher->y_trans.input_max - multigrapher->y_trans.input_min; /* there is no way you could use longer labels on tick marks! */ char labelbuf[2048]; /* switch to our font for drawing y axis label and tick labels */ pl_fontname_r (multigrapher->plotter, multigrapher->y_axis.font_name); pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->y_axis.font_size)); for (i = multigrapher->y_axis.min_tick_count; i <= multigrapher->y_axis.max_tick_count; i++) /* range can be empty */ { yval = i * multigrapher->y_axis.tick_spacing; /* discard tick locations outside plotting area */ if (yval < multigrapher->y_trans.input_min - FUZZ * yrange || yval > multigrapher->y_trans.input_max + FUZZ * yrange) continue; /* Plot the ordinate tick labels. */ if (!multigrapher->x_axis.switch_axis_end && !(multigrapher->grid_spec == AXES_AT_ORIGIN /* don't plot label if it could run into an axis */ && NEAR_EQUALITY (yval, multigrapher->y_axis.other_axis_loc, multigrapher->y_trans.input_range) && (multigrapher->x_axis.other_axis_loc != multigrapher->x_trans.input_min) && (multigrapher->x_axis.other_axis_loc != multigrapher->x_trans.input_max))) /* print labels to left of left boundary */ { double new_width; pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc) - (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75) * fabs(multigrapher->tick_size)) + multigrapher->half_line_width), YV (yval)); print_tick_label (labelbuf, &multigrapher->y_axis, &multigrapher->y_trans, (multigrapher->y_axis.type == A_LOG10) ? pow (10.0, yval) : yval); new_width = pl_flabelwidth_r (multigrapher->plotter, labelbuf); pl_alabel_r (multigrapher->plotter, 'r', 'c', labelbuf); multigrapher->y_axis.max_label_width = DMAX(multigrapher->y_axis.max_label_width, new_width); multigrapher->y_axis.labelled_ticks++; } else /* print labels to right of right boundary */ if (multigrapher->x_axis.switch_axis_end && !(multigrapher->grid_spec == AXES_AT_ORIGIN /* don't plot label if it could run into an axis */ && NEAR_EQUALITY (yval, multigrapher->y_axis.other_axis_loc, multigrapher->y_trans.input_range) && (multigrapher->x_axis.other_axis_loc != multigrapher->x_trans.input_min) && (multigrapher->x_axis.other_axis_loc != multigrapher->x_trans.input_max))) { double new_width; pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc) + (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75) * fabs(multigrapher->tick_size)) + multigrapher->half_line_width), YV (yval)); print_tick_label (labelbuf, &multigrapher->y_axis, &multigrapher->y_trans, (multigrapher->y_axis.type == A_LOG10) ? pow (10.0, yval) : yval); new_width = pl_flabelwidth_r (multigrapher->plotter, labelbuf); pl_alabel_r (multigrapher->plotter, 'l', 'c', labelbuf); multigrapher->y_axis.max_label_width = DMAX(multigrapher->y_axis.max_label_width, new_width); multigrapher->y_axis.labelled_ticks++; } /* Plot the tick marks on the y-axis, and horizontal grid lines. */ switch (multigrapher->grid_spec) { case AXES_AND_BOX_AND_GRID: pl_linemod_r (multigrapher->plotter, "dotted"); pl_fmove_r (multigrapher->plotter, XP(XSQ(0.0)), YV (yval)); pl_fcont_r (multigrapher->plotter, XP(XSQ(1.0)), YV (yval)); pl_linemod_r (multigrapher->plotter, "solid"); /* fall through */ case AXES_AND_BOX: if (!multigrapher->x_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc) - (SS (multigrapher->tick_size) + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } else { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc) + (SS (multigrapher->tick_size) + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } /* fall through */ case AXES: case AXES_AT_ORIGIN: if (!multigrapher->x_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc) + (SS (multigrapher->tick_size) + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } else { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc) - (SS (multigrapher->tick_size) + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } break; default: /* shouldn't happen */ break; } } if (multigrapher->y_axis.have_lin_subticks) { double subtick_size; /* libplot coordinates */ /* linearly spaced subticks on a log axis are as long as regular ticks */ subtick_size = (multigrapher->y_axis.type == A_LOG10 ? SS(multigrapher->tick_size) : SS(multigrapher->subtick_size)); /* Plot the linearly spaced subtick marks on the ordinate */ for (i = multigrapher->y_axis.min_lin_subtick_count; i <= multigrapher->y_axis.max_lin_subtick_count; i++) /* range can be empty */ { yval = i * multigrapher->y_axis.lin_subtick_spacing; /* discard subtick locations outside plotting area */ if (yval < multigrapher->y_trans.input_min - FUZZ * yrange || yval > multigrapher->y_trans.input_max + FUZZ * yrange) continue; /* Plot the tick marks on the y-axis, and horizontal grid lines. */ switch (multigrapher->grid_spec) { case AXES_AND_BOX_AND_GRID: case AXES_AND_BOX: if (!multigrapher->x_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc) - (subtick_size + (subtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } else { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc) + (subtick_size + (subtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } /* fall through */ case AXES: case AXES_AT_ORIGIN: if (!multigrapher->x_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc) + (subtick_size + (subtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } else { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc) - (subtick_size + (subtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } break; default: /* shouldn't happen */ break; } } } /* plot a horizontal dotted line at y = 0 */ if (multigrapher->grid_spec != AXES_AT_ORIGIN && multigrapher->y_axis.type == A_LINEAR && multigrapher->y_trans.input_min * multigrapher->y_trans.input_max < 0.0) { pl_linemod_r (multigrapher->plotter, "dotted"); pl_fline_r (multigrapher->plotter, XP(XSQ(0.0)), YV(0.0), XP(XSQ(1.0)), YV(0.0)); pl_linemod_r (multigrapher->plotter, "solid"); } } /* 5. DRAW LOGARITHMIC SUBSUBTICKS AND THEIR LABELS ON ABSCISSA */ /* first, draw normal logarithmic subsubticks if any */ if (multigrapher->grid_spec != NO_AXES && multigrapher->x_axis.have_normal_subsubticks && !multigrapher->x_axis.user_specified_subsubticks && !multigrapher->x_axis.omit_ticks) { int i, m, imin, imax; double xval, xrange = multigrapher->x_trans.input_max - multigrapher->x_trans.input_min; /* compute an integer range (of powers of 10) large enough to include the entire desired axis */ imin = (int)(floor (multigrapher->x_trans.input_min - FUZZ * xrange)); imax = (int)(ceil (multigrapher->x_trans.input_max + FUZZ * xrange)); for (i = imin; i < imax; i++) { for (m = 1; m <= 9 ; m++) { xval = i + log10 ((double)m); /* Plot subsubtick and label, if desired. */ /* N.B. if tick is outside axis range, nothing will be printed */ plot_abscissa_log_subsubtick (multigrapher, xval); } } } /* second, draw user-specified logarithmic subsubticks instead, if any */ if (multigrapher->grid_spec != NO_AXES && multigrapher->x_axis.user_specified_subsubticks && !multigrapher->x_axis.omit_ticks) { int i, imin, imax; double xval, xrange = multigrapher->x_trans.input_max - multigrapher->x_trans.input_min; /* compute an integer range large enough to include the entire desired axis */ imin = (int)(floor (pow (10.0, multigrapher->x_trans.input_min - FUZZ * xrange) / multigrapher->x_axis.subsubtick_spacing)); imax = (int)(ceil (pow (10.0, multigrapher->x_trans.input_max + FUZZ * xrange) / multigrapher->x_axis.subsubtick_spacing)); /* draw user-specified subsubticks */ for (i = imin; i <= imax; i++) { xval = log10 (i * multigrapher->x_axis.subsubtick_spacing); /* Plot subsubtick and label, if desired. */ /* N.B. if tick is outside axis range, nothing will be printed */ plot_abscissa_log_subsubtick (multigrapher, xval); } } /* 6. DRAW LOGARITHMIC SUBSUBTICKS AND THEIR LABELS ON ORDINATE */ /* first, draw normal logarithmic subsubticks if any */ if (multigrapher->grid_spec != NO_AXES && multigrapher->y_axis.have_normal_subsubticks && !multigrapher->y_axis.user_specified_subsubticks && !multigrapher->y_axis.omit_ticks) { int i, m, imin, imax; double yval, yrange = multigrapher->y_trans.input_max - multigrapher->y_trans.input_min; /* compute an integer range (of powers of 10) large enough to include the entire desired axis */ imin = (int)(floor (multigrapher->y_trans.input_min - FUZZ * yrange)); imax = (int)(ceil (multigrapher->y_trans.input_max + FUZZ * yrange)); /* draw normal subticks */ for (i = imin; i < imax; i++) { for (m = 1; m <= 9; m++) { yval = i + log10 ((double)m); /* Plot subsubtick and label, if desired. */ /* N.B. if tick is outside axis range, nothing will be printed */ plot_ordinate_log_subsubtick (multigrapher, yval); } } } /* second, draw user-specified logarithmic subsubticks instead, if any */ if (multigrapher->grid_spec != NO_AXES && multigrapher->y_axis.user_specified_subsubticks && !multigrapher->y_axis.omit_ticks) { int i, imin, imax; double yval, yrange = multigrapher->y_trans.input_max - multigrapher->y_trans.input_min; /* compute an integer range large enough to include the entire desired axis */ imin = (int)(floor (pow (10.0, multigrapher->y_trans.input_min - FUZZ * yrange) / multigrapher->y_axis.subsubtick_spacing)); imax = (int)(ceil (pow (10.0, multigrapher->y_trans.input_max + FUZZ * yrange) / multigrapher->y_axis.subsubtick_spacing)); /* draw user-specified subsubticks */ for (i = imin; i <= imax; i++) { yval = log10 (i * multigrapher->y_axis.subsubtick_spacing); /* Plot subsubtick and label, if desired. */ /* N.B. if tick is outside axis range, nothing will be printed */ plot_ordinate_log_subsubtick (multigrapher, yval); } } /* 7. DRAW THE ABSCISSA LABEL */ if ((multigrapher->grid_spec != NO_AXES) && multigrapher->x_axis.label != NULL && multigrapher->x_axis.label != '\0') { double x_axis_font_size; double xloc; /* switch to our font for drawing x axis label and tick labels */ pl_fontname_r (multigrapher->plotter, multigrapher->x_axis.font_name); x_axis_font_size = pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->x_axis.font_size)); if (multigrapher->grid_spec != AXES_AT_ORIGIN) /* center the label on the axis */ xloc = 0.5 * (multigrapher->x_trans.input_max + multigrapher->x_trans.input_min); else { if ((multigrapher->y_axis.other_axis_loc == multigrapher->y_trans.input_min) || (multigrapher->y_axis.other_axis_loc == multigrapher->y_trans.input_max)) xloc = 0.5 * (multigrapher->x_trans.input_max + multigrapher->x_trans.input_min); else /* center label in the larger of the two halves */ xloc = multigrapher->x_trans.input_max-multigrapher->x_axis.other_axis_loc >= multigrapher->x_axis.other_axis_loc-multigrapher->x_trans.input_min ? 0.5 * (multigrapher->x_trans.input_max + multigrapher->x_axis.other_axis_loc) : 0.5 * (multigrapher->x_axis.other_axis_loc + multigrapher->x_trans.input_min); } if (!multigrapher->y_axis.switch_axis_end) /* axis on bottom, label below it */ { pl_fmove_r (multigrapher->plotter, XV (xloc), YN (multigrapher->y_axis.other_axis_loc) - (SS ((multigrapher->tick_size >= 0.0 ? 0.875 : 2.125) * fabs(multigrapher->tick_size)) + (6 * x_axis_font_size)/5 + multigrapher->half_line_width)); pl_alabel_r (multigrapher->plotter, 'c', 't', multigrapher->x_axis.label); } else /* axis on top, label above it */ { pl_fmove_r (multigrapher->plotter, XV (xloc), YN (multigrapher->y_axis.alt_other_axis_loc) + (SS ((multigrapher->tick_size >= 0.0 ? 0.875 : 2.125) * fabs(multigrapher->tick_size)) + (6 * x_axis_font_size)/5 + multigrapher->half_line_width)); pl_alabel_r (multigrapher->plotter, 'c', 'b', multigrapher->x_axis.label); } } /* 8. DRAW THE ORDINATE LABEL */ if ((multigrapher->grid_spec != NO_AXES) && (multigrapher->y_axis.label != NULL && *(multigrapher->y_axis.label) != '\0')) { double y_axis_font_size; double yloc; /* switch to our font for drawing y axis label and tick labels */ pl_fontname_r (multigrapher->plotter, multigrapher->y_axis.font_name); y_axis_font_size = pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->y_axis.font_size)); if (multigrapher->grid_spec != AXES_AT_ORIGIN) /* center the label on the axis */ yloc = 0.5 * (multigrapher->y_trans.input_min + multigrapher->y_trans.input_max); else { if ((multigrapher->x_axis.other_axis_loc == multigrapher->x_trans.input_min) || (multigrapher->x_axis.other_axis_loc == multigrapher->x_trans.input_max)) yloc = 0.5 * (multigrapher->y_trans.input_min + multigrapher->y_trans.input_max); else /* center label in the larger of the two halves */ yloc = multigrapher->y_trans.input_max-multigrapher->y_axis.other_axis_loc >= multigrapher->y_axis.other_axis_loc-multigrapher->y_trans.input_min ? 0.5 * (multigrapher->y_trans.input_max + multigrapher->y_axis.other_axis_loc) : 0.5 * (multigrapher->y_axis.other_axis_loc + multigrapher->y_trans.input_min); } /* a relic of temps perdus */ #define libplot_has_font_metrics 1 if (!multigrapher->x_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc) - (libplot_has_font_metrics ? (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75) * fabs(multigrapher->tick_size)) + 1.15 * multigrapher->y_axis.max_label_width + 0.5 * y_axis_font_size + multigrapher->half_line_width) : (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75) * fabs(multigrapher->tick_size)) /* backup */ + 1.0 * y_axis_font_size + multigrapher->half_line_width)), YV(yloc)); if (libplot_has_font_metrics && !multigrapher->no_rotate_y_label) /* can rotate label */ { pl_textangle_r (multigrapher->plotter, 90); pl_alabel_r (multigrapher->plotter, 'c', 'x', multigrapher->y_axis.label); pl_textangle_r (multigrapher->plotter, 0); } else /* non-rotated axis label, right justified */ pl_alabel_r (multigrapher->plotter, 'r', 'c', multigrapher->y_axis.label); } else { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc) + (libplot_has_font_metrics ? (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75) * fabs(multigrapher->tick_size)) + 1.15 * multigrapher->y_axis.max_label_width + 0.5 * y_axis_font_size + multigrapher->half_line_width) : (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75) * fabs(multigrapher->tick_size)) /* backup */ + 1.0 * y_axis_font_size + multigrapher->half_line_width)), YV(yloc)); if (libplot_has_font_metrics && !multigrapher->no_rotate_y_label) /* can rotate label */ { pl_textangle_r (multigrapher->plotter, 90); pl_alabel_r (multigrapher->plotter, 'c', 't', multigrapher->y_axis.label); pl_textangle_r (multigrapher->plotter, 0); } else /* non-rotated axis label, left justified */ pl_alabel_r (multigrapher->plotter, 'l', 'c', multigrapher->y_axis.label); } } /* END OF TASKS */ /* flush frame to device */ pl_flushpl_r (multigrapher->plotter); pl_restorestate_r (multigrapher->plotter); if (multigrapher->grid_spec != NO_AXES) { if (!tick_warning_printed && ((!multigrapher->x_axis.omit_ticks && multigrapher->x_axis.labelled_ticks <= 2) || (!multigrapher->y_axis.omit_ticks && multigrapher->y_axis.labelled_ticks <= 2))) { fprintf (stderr, "%s: too few labelled axis ticks, adjust tick spacing manually\n", progname); tick_warning_printed = true; } } } /* plot_abscissa_log_subsubtick() and plot_ordinate_log_subsubtick() are called to plot both normal log subticks and special (user-requested) ones */ /* ARGS: xval = log of location */ static void plot_abscissa_log_subsubtick (Multigrapher *multigrapher, double xval) { double xrange = multigrapher->x_trans.input_max - multigrapher->x_trans.input_min; /* there is no way you could use longer labels on tick marks! */ char labelbuf[2048]; double tick_size = SS(multigrapher->tick_size); /* for positioning labels */ double subsubtick_size = SS(multigrapher->subtick_size); /* switch to our font for drawing x axis label and tick labels */ pl_fontname_r (multigrapher->plotter, multigrapher->x_axis.font_name); pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->x_axis.font_size)); /* discard subsubtick locations outside plotting area */ if (xval < multigrapher->x_trans.input_min - FUZZ * xrange || xval > multigrapher->x_trans.input_max + FUZZ * xrange) return; /* label subsubtick if it seems appropriate */ if (multigrapher->x_axis.user_specified_subsubticks) { print_tick_label (labelbuf, &multigrapher->x_axis, &multigrapher->x_trans, pow (10.0, xval)); if (!multigrapher->y_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc) - ((tick_size >= 0 ? 0.75 : 1.75) * fabs((double)tick_size) + multigrapher->half_line_width)); pl_alabel_r (multigrapher->plotter, 'c', 't', labelbuf); multigrapher->x_axis.labelled_ticks++; } else { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc) + ((tick_size >= 0 ? 0.75 : 1.75) * fabs((double)tick_size) + multigrapher->half_line_width)); pl_alabel_r (multigrapher->plotter, 'c', 'b', labelbuf); multigrapher->x_axis.labelled_ticks++; } } /* draw subsubtick */ switch (multigrapher->grid_spec) { case AXES_AND_BOX_AND_GRID: pl_linemod_r (multigrapher->plotter, "dotted"); pl_fmove_r (multigrapher->plotter, XV (xval), YP(YSQ(0.0))); pl_fcont_r (multigrapher->plotter, XV (xval), YP(YSQ(1.0))); pl_linemod_r (multigrapher->plotter, "solid"); /* fall through */ case AXES_AND_BOX: if (!multigrapher->y_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc) - (subsubtick_size + (subsubtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } else { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc) + (subsubtick_size + (subsubtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } /* fall through */ case AXES: case AXES_AT_ORIGIN: if (!multigrapher->y_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.other_axis_loc) + (subsubtick_size + (subsubtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } else { pl_fmove_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc)); pl_fcont_r (multigrapher->plotter, XV (xval), YN (multigrapher->y_axis.alt_other_axis_loc) - (subsubtick_size + (subsubtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width))); } break; default: /* shouldn't happen */ break; } } /* ARGS: yval = log of location */ static void plot_ordinate_log_subsubtick (Multigrapher *multigrapher, double yval) { double yrange = multigrapher->y_trans.input_max - multigrapher->y_trans.input_min; /* there is no way you could use longer labels on tick marks! */ char labelbuf[2048]; double tick_size = SS(multigrapher->tick_size); /* for positioning labels */ double subsubtick_size = SS(multigrapher->subtick_size); /* switch to our font for drawing y axis label and tick labels */ pl_fontname_r (multigrapher->plotter, multigrapher->y_axis.font_name); pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->y_axis.font_size)); /* discard subsubtick locations outside plotting area */ if (yval < multigrapher->y_trans.input_min - FUZZ * yrange || yval > multigrapher->y_trans.input_max + FUZZ * yrange) return; /* label subsubtick if it seems appropriate */ if (multigrapher->y_axis.user_specified_subsubticks) { double new_width; print_tick_label (labelbuf, &multigrapher->y_axis, &multigrapher->y_trans, pow (10.0, yval)); if (!multigrapher->x_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XN(multigrapher->x_axis.other_axis_loc) - ((tick_size >= 0 ? 0.75 : 1.75) * fabs((double)tick_size) + multigrapher->half_line_width), YV (yval)); new_width = pl_flabelwidth_r (multigrapher->plotter, labelbuf); pl_alabel_r (multigrapher->plotter, 'r', 'c', labelbuf); multigrapher->y_axis.max_label_width = DMAX(multigrapher->y_axis.max_label_width, new_width); multigrapher->y_axis.labelled_ticks++; } else { pl_fmove_r (multigrapher->plotter, XN(multigrapher->x_axis.alt_other_axis_loc) + ((tick_size >= 0 ? 0.75 : 1.75) * fabs((double)tick_size) + multigrapher->half_line_width), YV (yval)); new_width = pl_flabelwidth_r (multigrapher->plotter, labelbuf); pl_alabel_r (multigrapher->plotter, 'l', 'c', labelbuf); multigrapher->y_axis.max_label_width = DMAX(multigrapher->y_axis.max_label_width, new_width); multigrapher->y_axis.labelled_ticks++; } } /* draw subsubtick */ switch (multigrapher->grid_spec) { case AXES_AND_BOX_AND_GRID: pl_linemod_r (multigrapher->plotter, "dotted"); pl_fmove_r (multigrapher->plotter, XP(XSQ(0.0)), YV (yval)); pl_fcont_r (multigrapher->plotter, XP(XSQ(1.0)), YV (yval)); pl_linemod_r (multigrapher->plotter, "solid"); /* fall through */ case AXES_AND_BOX: if (!multigrapher->x_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc) - (subsubtick_size + (subsubtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } else { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc) + (subsubtick_size + (subsubtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } /* fall through */ case AXES: case AXES_AT_ORIGIN: if (!multigrapher->x_axis.switch_axis_end) { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.other_axis_loc) + (subsubtick_size + (subsubtick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } else { pl_fmove_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc), YV (yval)); pl_fcont_r (multigrapher->plotter, XN (multigrapher->x_axis.alt_other_axis_loc) - (subsubtick_size + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width : -multigrapher->half_line_width)), YV (yval)); } break; default: /* shouldn't happen */ break; } } /* set_line_style() maps from line modes to physical line modes. See * explanation at head of file. */ static void set_line_style (Multigrapher *multigrapher, int style, bool use_color) { if (!use_color) /* monochrome */ { if (style > 0) /* don't issue pl_linemod_r() if style<=0, since no polyline will be drawn */ { int i; i = (style - 1) % NO_OF_LINEMODES; pl_linemod_r (multigrapher->plotter, linemodes[i]); } /* use same color as used for plot frame */ pl_colorname_r (multigrapher->plotter, multigrapher->frame_color); } else /* color */ { int i, j; if (style > 0) /* solid lines, various colors */ { i = ((style - 1) / NO_OF_LINEMODES) % NO_OF_LINEMODES; j = (style - 1) % NO_OF_LINEMODES; pl_linemod_r (multigrapher->plotter, linemodes[i]); } else if (style == 0) /* use first color, as if -m 1 was spec'd */ /* (no line will be drawn) */ j = 0; else /* neg. pl_linemode_r (no line will be drawn)*/ j = (-style - 1) % (NO_OF_LINEMODES - 1); pl_colorname_r (multigrapher->plotter, colorstyle[j]); } } /* plot_point_array() calls plot_point() on each point in an array of * points. */ void plot_point_array (Multigrapher *multigrapher, const Point *p, int length) { int index; for (index = 0; index < length; index++) plot_point (multigrapher, &(p[index])); } /* plot_point() plots a single point, including the appropriate symbol and * errorbar(s) if any. It may call either pl_fcont_r() or pl_fmove_r(), * depending on whether the pendown flag is set or not. Gnuplot-style * clipping (clip mode = 0,1,2) is supported. * * plot_point() makes heavy use of the multigrapher->x_trans and * multigrapher->y_trans structures, which specify the linear * transformation from user coordinates to device coordinates. It also * updates the multigrapher's internal state variables. */ void plot_point (Multigrapher *multigrapher, const Point *point) { double local_x0, local_y0, local_x1, local_y1; int clipval; /* If new polyline is beginning, take its line style, color/monochrome attribute, and line width and fill fraction attributes from the first point of the polyline. We assume all such attribute fields are the same for all points in the polyline (our point reader arranges this for us). */ if (!(point->pendown) || multigrapher->first_point_of_polyline) { int intfill; set_line_style (multigrapher, point->linemode, point->use_color); /* N.B. linewidth < 0.0 means use libplot default */ pl_flinewidth_r (multigrapher->plotter, point->line_width * (double)PLOT_SIZE); if (point->fill_fraction < 0.0) intfill = 0; /* transparent */ else /* guaranteed to be <= 1.0 */ intfill = 1 + IROUND((1.0 - point->fill_fraction) * 0xfffe); pl_filltype_r (multigrapher->plotter, intfill); } /* determine endpoints of new line segment (for the first point of a polyline, use a zero-length line segment) */ if (multigrapher->first_point_of_polyline) { local_x0 = point->x; local_y0 = point->y; } else { local_x0 = multigrapher->oldpoint_x; local_y0 = multigrapher->oldpoint_y; } local_x1 = point->x; local_y1 = point->y; /* save current point for use as endpoint of next line segment */ multigrapher->oldpoint_x = point->x; multigrapher->oldpoint_y = point->y; /* apply Cohen-Sutherland clipper to new line segment */ clipval = clip_line (multigrapher, &local_x0, &local_y0, &local_x1, &local_y1); if (!(clipval & ACCEPTED)) /* rejected in toto */ { pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y)); /* move with pen up */ multigrapher->first_point_of_polyline = false; return; } /* not rejected, ideally move with pen down */ if (point->pendown && (point->linemode > 0)) { switch (multigrapher->clip_mode) /* gnuplot style clipping (0,1, or 2) */ { case 0: if ((clipval & CLIPPED_FIRST) || (clipval & CLIPPED_SECOND)) /* clipped on at least one end, so move with pen up */ pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y)); else /* line segment within box, so move with pen down */ { if (!multigrapher->first_point_of_polyline) pl_fcont_r (multigrapher->plotter, XV (point->x), YV (point->y)); else pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y)); } break; case 1: default: if ((clipval & CLIPPED_FIRST) && (clipval & CLIPPED_SECOND)) /* both OOB, so move with pen up */ pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y)); else /* at most one point is OOB */ { if (clipval & CLIPPED_FIRST) /*current pt. OOB, new pt. not OOB*/ { if (!multigrapher->first_point_of_polyline) { /* move to clipped current point, draw line segment */ pl_fmove_r (multigrapher->plotter, XV (local_x0), YV (local_y0)); pl_fcont_r (multigrapher->plotter, XV (point->x), YV (point->y)); } else pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y)); } else /* current point not OOB, new point OOB */ { if (!multigrapher->first_point_of_polyline) { /* draw line segment to clipped new point */ pl_fcont_r (multigrapher->plotter, XV (local_x1), YV (local_y1)); /* N.B. lib's notion of position now differs from ours */ } else pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y)); } } break; case 2: if ((clipval & CLIPPED_FIRST) || multigrapher->first_point_of_polyline) /* move to clipped current point if necc. */ pl_fmove_r (multigrapher->plotter, XV (local_x0), YV (local_y0)); /* draw line segment to clipped new point */ pl_fcont_r (multigrapher->plotter, XV (local_x1), YV (local_y1)); if (clipval & CLIPPED_SECOND) /* new point OOB, so move to new point, breaking polyline */ pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y)); break; } } else /* linemode=0 or pen up; so move with pen up */ pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y)); multigrapher->first_point_of_polyline = false; /* if target point is OOB, return without plotting symbol or errorbar */ if (clipval & CLIPPED_SECOND) return; /* plot symbol and errorbar, doing a pl_savestate_r()--pl_restorestate() to keep from breaking the polyline under construction (if any) */ if (point->symbol >= 32) /* yow, a character */ { /* will do a font change, so save & restore state */ pl_savestate_r (multigrapher->plotter); plot_errorbar (multigrapher, point); pl_fontname_r (multigrapher->plotter, point->symbol_font_name); pl_fmarker_r (multigrapher->plotter, XV(point->x), YV(point->y), point->symbol, SS(point->symbol_size)); pl_restorestate_r (multigrapher->plotter); } else if (point->symbol > 0) /* a marker symbol */ { if (point->linemode > 0) /* drawing a line, so (to keep from breaking it) save & restore state*/ { pl_savestate_r (multigrapher->plotter); plot_errorbar (multigrapher, point); /* may or may not have one */ pl_fmarker_r (multigrapher->plotter, XV(point->x), YV(point->y), point->symbol, SS(point->symbol_size)); pl_restorestate_r (multigrapher->plotter); } else /* not drawing a line, so just place the marker */ { plot_errorbar (multigrapher, point); pl_fmarker_r (multigrapher->plotter, XV(point->x), YV(point->y), point->symbol, SS(point->symbol_size)); } } else if (point->symbol == 0 && point->linemode == 0) /* backward compatibility: -m 0 (even with -S 0) plots a dot */ { plot_errorbar (multigrapher, point); pl_fmarker_r (multigrapher->plotter, XV(point->x), YV(point->y), M_DOT, SS(point->symbol_size)); } else /* no symbol, but may be an errorbar */ plot_errorbar (multigrapher, point); return; } /* clip_line() takes two points, the endpoints of a line segment, and * destructively passes back two points: the endpoints of the line segment * clipped by Cohen-Sutherland to the rectangular plotting area. Return * value contains bitfields ACCEPTED, CLIPPED_FIRST, and CLIPPED_SECOND. */ static int clip_line (Multigrapher *multigrapher, double *x0_p, double *y0_p, double *x1_p, double *y1_p) { double x0 = *x0_p; double y0 = *y0_p; double x1 = *x1_p; double y1 = *y1_p; outcode outcode0 = compute_outcode (multigrapher, x0, y0, true); outcode outcode1 = compute_outcode (multigrapher, x1, y1, true); bool accepted; int clipval = 0; for ( ; ; ) { if (!(outcode0 | outcode1)) /* accept */ { accepted = true; break; } else if (outcode0 & outcode1) /* reject */ { accepted = false; break; } else { /* at least one endpoint is outside; choose one that is */ outcode outcode_out = (outcode0 ? outcode0 : outcode1); double x, y; /* intersection with clip edge */ if (outcode_out & RIGHT) { x = multigrapher->x_trans.input_max; y = y0 + (y1 - y0) * (multigrapher->x_trans.input_max - x0) / (x1 - x0); } else if (outcode_out & LEFT) { x = multigrapher->x_trans.input_min; y = y0 + (y1 - y0) * (multigrapher->x_trans.input_min - x0) / (x1 - x0); } else if (outcode_out & TOP) { x = x0 + (x1 - x0) * (multigrapher->y_trans.input_max - y0) / (y1 - y0); y = multigrapher->y_trans.input_max; } else { x = x0 + (x1 - x0) * (multigrapher->y_trans.input_min - y0) / (y1 - y0); y = multigrapher->y_trans.input_min; } if (outcode_out == outcode0) { x0 = x; y0 = y; outcode0 = compute_outcode (multigrapher, x0, y0, true); } else { x1 = x; y1 = y; outcode1 = compute_outcode (multigrapher, x1, y1, true); } } } if (accepted) { clipval |= ACCEPTED; if ((x0 != *x0_p) || (y0 != *y0_p)) clipval |= CLIPPED_FIRST; if ((x1 != *x1_p) || (y1 != *y1_p)) clipval |= CLIPPED_SECOND; *x0_p = x0; *y0_p = y0; *x1_p = x1; *y1_p = y1; } return clipval; } /* Compute usual Cohen-Sutherland outcode, containing bitfields LEFT, RIGHT, BOTTOM, TOP. Nine possibilities: {LEFT, interior, RIGHT} x {BOTTOM, interior, TOP}. The `tolerant' flag specifies how we handle points on the boundary. */ static outcode compute_outcode (Multigrapher *multigrapher, double x, double y, bool tolerant) { outcode code = 0; double xfuzz = FUZZ * multigrapher->x_trans.input_range; double yfuzz = FUZZ * multigrapher->y_trans.input_range; int sign = (tolerant == true ? 1 : -1); if (x > multigrapher->x_trans.input_max + sign * xfuzz) code |= RIGHT; else if (x < multigrapher->x_trans.input_min - sign * xfuzz) code |= LEFT; if (y > multigrapher->y_trans.input_max + sign * yfuzz) code |= TOP; else if (y < multigrapher->y_trans.input_min - sign * yfuzz) code |= BOTTOM; return code; } static void transpose_portmanteau (int *val) { bool xtrue, ytrue; int newval; xtrue = ((*val & X_AXIS) ? true : false); ytrue = ((*val & Y_AXIS) ? true : false); newval = (xtrue ? Y_AXIS : 0) | (ytrue ? X_AXIS : 0); *val = newval; } static void plot_errorbar (Multigrapher *multigrapher, const Point *p) { if (p->have_x_errorbar || p->have_y_errorbar) /* save & restore state, since we invoke pl_linemod_r() */ { pl_savestate_r (multigrapher->plotter); pl_linemod_r (multigrapher->plotter, "solid"); if (p->have_x_errorbar) { pl_fline_r (multigrapher->plotter, XV(p->xmin), YV(p->y) - 0.5 * SS(p->symbol_size), XV(p->xmin), YV(p->y) + 0.5 * SS(p->symbol_size)); pl_fline_r (multigrapher->plotter, XV(p->xmin), YV(p->y), XV(p->xmax), YV(p->y)); pl_fline_r (multigrapher->plotter, XV(p->xmax), YV(p->y) - 0.5 * SS(p->symbol_size), XV(p->xmax), YV(p->y) + 0.5 * SS(p->symbol_size)); } if (p->have_y_errorbar) { pl_fline_r (multigrapher->plotter, XV(p->x) - 0.5 * SS(p->symbol_size), YV(p->ymin), XV(p->x) + 0.5 * SS(p->symbol_size), YV(p->ymin)); pl_fline_r (multigrapher->plotter, XV(p->x), YV(p->ymin), XV(p->x), YV(p->ymax)); pl_fline_r (multigrapher->plotter, XV(p->x) - 0.5 * SS(p->symbol_size), YV(p->ymax), XV(p->x) + 0.5 * SS(p->symbol_size), YV(p->ymax)); } pl_restorestate_r (multigrapher->plotter); } } /* An alternative means of ending a polyline in progress. Rather than ending it by passing plot_point() a point with the `pendown' flag not set, one may call this function. This yields faster response in real-time work; e.g. in reader.c it is called by read_and_plot_file() after all dataset(s) have been read from the file and plotted. */ void end_polyline_and_flush (Multigrapher *multigrapher) { pl_endpath_r (multigrapher->plotter); pl_flushpl_r (multigrapher->plotter); multigrapher->first_point_of_polyline = true; }