/* GNUPLOT - gadgets.c */
/*[
* Copyright 2000, 2004 Thomas Williams, Colin Kelley
*
* Permission to use, copy, and distribute this software and its
* documentation for any purpose with or without fee is hereby granted,
* provided that the above copyright notice appear in all copies and
* that both that copyright notice and this permission notice appear
* in supporting documentation.
*
* Permission to modify the software is granted, but not the right to
* distribute the complete modified source code. Modifications are to
* be distributed as patches to the released version. Permission to
* distribute binaries produced by compiling modified sources is granted,
* provided you
* 1. distribute the corresponding source modifications from the
* released version in the form of a patch file along with the binaries,
* 2. add special version identification to distinguish your version
* in addition to the base release version number,
* 3. provide your name and address as the primary contact for the
* support of your modified version, and
* 4. retain our contact information in regard to use of the base
* software.
* Permission to distribute the released version of the source code along
* with corresponding source modifications in the form of a patch file is
* granted with same provisions 2 through 4 for binary distributions.
*
* This software is provided "as is" without express or implied warranty
* to the extent permitted by applicable law.
]*/
#include "gadgets.h"
#include "alloc.h"
#include "command.h"
#include "graph3d.h" /* for map3d_position_r() */
#include "graphics.h"
#include "plot3d.h" /* For is_plot_with_palette() */
#include "axis.h" /* For CB_AXIS */
#include "pm3d.h"
/* This file contains mainly a collection of global variables that
* describe the status of several parts of the gnuplot plotting engine
* that are used by both 2D and 3D plots, and thus belong neither to
* graphics.c nor graph3d.c, alone. This is not a very clean solution,
* but better than mixing internal status and the user interface as we
* used to have it, in set.c and setshow.h */
legend_key keyT;
/* Description of the color box associated with CB_AXIS */
color_box_struct color_box; /* initialized in init_color() */
color_box_struct default_color_box = {SMCOLOR_BOX_DEFAULT, 'v', 1, LT_BLACK, LAYER_FRONT, 0,
{screen, screen, screen, 0.90, 0.2, 0.0},
{screen, screen, screen, 0.05, 0.6, 0.0}, FALSE,
{0,0,0,0} };
/* The graph box (terminal coordinates) calculated by boundary() or boundary3d() */
BoundingBox plot_bounds;
/* The bounding box for 3D plots prior to applying view transformations */
BoundingBox page_bounds;
/* The bounding box for the entire drawable area of current terminal */
BoundingBox canvas;
/* The bounding box against which clipping is to be done */
BoundingBox *clip_area = &plot_bounds;
/* 'set size', 'set origin' settings */
float xsize = 1.0; /* scale factor for size */
float ysize = 1.0; /* scale factor for size */
float zsize = 1.0; /* scale factor for size */
float xoffset = 0.0; /* x origin */
float yoffset = 0.0; /* y origin */
float aspect_ratio = 0.0; /* don't attempt to force it */
int aspect_ratio_3D = 0; /* 2 will put x and y on same scale, 3 for z also */
/* EAM Augest 2006 -
redefine margin as t_position so that absolute placement is possible */
/* space between left edge and plot_bounds.xleft in chars (-1: computed) */
t_position lmargin = DEFAULT_MARGIN_POSITION;
/* space between bottom and plot_bounds.ybot in chars (-1: computed) */
t_position bmargin = DEFAULT_MARGIN_POSITION;
/* space between right edge and plot_bounds.xright in chars (-1: computed) */
t_position rmargin = DEFAULT_MARGIN_POSITION;
/* space between top edge and plot_bounds.ytop in chars (-1: computed) */
t_position tmargin = DEFAULT_MARGIN_POSITION;
/* Pointer to first 'set dashtype' definition in linked list */
struct custom_dashtype_def *first_custom_dashtype = NULL;
/* Pointer to the start of the linked list of 'set label' definitions */
struct text_label *first_label = NULL;
/* Pointer to first 'set linestyle' definition in linked list */
struct linestyle_def *first_linestyle = NULL;
struct linestyle_def *first_perm_linestyle = NULL;
struct linestyle_def *first_mono_linestyle = NULL;
/* Pointer to first 'set style arrow' definition in linked list */
struct arrowstyle_def *first_arrowstyle = NULL;
/* Listhead for pixmaps */
t_pixmap *pixmap_listhead = NULL;
/* Holds the properties from 'set style parallelaxis' */
struct pa_style parallel_axis_style = DEFAULT_PARALLEL_AXIS_STYLE;
/* Holds properties for 'set style spiderplot' */
struct spider_web spiderplot_style = DEFAULT_SPIDERPLOT_STYLE;
/* set arrow */
struct arrow_def *first_arrow = NULL;
/* Pointer to first object instance in linked list */
struct object *first_object = NULL;
/* Pointer to array of grid walls */
struct object grid_wall[5] = {WALL_Y0, WALL_X0, WALL_Y1, WALL_X1, WALL_Z0};
/* 'set title' status */
text_label title = EMPTY_LABELSTRUCT;
/* 'set timelabel' status */
text_label timelabel = EMPTY_LABELSTRUCT;
int timelabel_bottom = TRUE;
/* flag for polar mode */
TBOOLEAN polar = FALSE;
TBOOLEAN inverted_raxis = FALSE;
/* toggle spiderplot mode on/off */
TBOOLEAN spiderplot = FALSE;
/* zero threshold, may _not_ be 0! */
double zero = ZERO;
/* Status of 'set pointsize' and 'set pointintervalbox' commands */
double pointsize = 1.0;
double pointintervalbox = 1.0;
/* used for filled points */
t_colorspec background_fill = BACKGROUND_COLORSPEC;
/* box width (automatic) for plot style "with boxes" */
double boxwidth = -1.0;
/* whether box width is absolute (default) or relative */
TBOOLEAN boxwidth_is_absolute = TRUE;
/* set border */
int draw_border = 31; /* The current settings */
int user_border = 31; /* What the user last set explicitly */
int border_layer = LAYER_FRONT;
# define DEFAULT_BORDER_LP { 0, LT_BLACK, 0, DASHTYPE_SOLID, 0, 0, 1.0, 1.0, DEFAULT_P_CHAR, BLACK_COLORSPEC, DEFAULT_DASHPATTERN }
struct lp_style_type border_lp = DEFAULT_BORDER_LP;
const struct lp_style_type default_border_lp = DEFAULT_BORDER_LP;
const struct lp_style_type background_lp = {0, LT_BACKGROUND, 0, DASHTYPE_SOLID, 0, 0, 1.0, 0.0, DEFAULT_P_CHAR, BACKGROUND_COLORSPEC, DEFAULT_DASHPATTERN};
TBOOLEAN cornerpoles = TRUE;
/* set clip */
TBOOLEAN clip_lines1 = TRUE;
TBOOLEAN clip_lines2 = FALSE;
TBOOLEAN clip_points = FALSE;
TBOOLEAN clip_radial = FALSE;
static int clip_line(int *, int *, int *, int *);
/* set samples */
int samples_1 = SAMPLES;
int samples_2 = SAMPLES;
/* set angles */
double ang2rad = 1.0; /* 1 or pi/180, tracking angles_format */
enum PLOT_STYLE data_style = POINTSTYLE;
enum PLOT_STYLE func_style = LINES;
TBOOLEAN parametric = FALSE;
TBOOLEAN in_parametric = FALSE;
/* If last plot was a 3d one. */
TBOOLEAN is_3d_plot = FALSE;
/* Flag to signal that the existing data is valid for a quick refresh */
TRefresh_Allowed refresh_ok = E_REFRESH_NOT_OK;
/* FIXME: do_plot should be able to figure this out on its own! */
int refresh_nplots = 0;
/* Flag to show that volatile input data is present */
TBOOLEAN volatile_data = FALSE;
fill_style_type default_fillstyle = { FS_EMPTY, 100, 0, DEFAULT_COLORSPEC } ;
/* Default rectangle style - background fill, black border */
struct object default_rectangle = DEFAULT_RECTANGLE_STYLE;
struct object default_circle = DEFAULT_CIRCLE_STYLE;
struct object default_ellipse = DEFAULT_ELLIPSE_STYLE;
/* filledcurves style options */
filledcurves_opts filledcurves_opts_data = EMPTY_FILLEDCURVES_OPTS;
filledcurves_opts filledcurves_opts_func = EMPTY_FILLEDCURVES_OPTS;
#ifdef BACKWARD_COMPATIBILITY
TBOOLEAN prefer_line_styles = FALSE;
#endif
/* If current terminal claims to be monochrome, don't try to send it colors */
#define monochrome_terminal ((t->flags & TERM_MONOCHROME) != 0)
histogram_style histogram_opts = DEFAULT_HISTOGRAM_STYLE;
boxplot_style boxplot_opts = DEFAULT_BOXPLOT_STYLE;
/* WINDOWID to be filled by terminals running on X11 (x11, wxt, qt, ...) */
int current_x11_windowid = 0;
textbox_style textbox_opts[NUM_TEXTBOX_STYLES];
/*
* Image data or pm3d quadrangles can be masked by first loading a set
* of masking polygons via dummy plotting style "with mask".
*/
struct iso_curve *mask_2Dpolygon_set = NULL;
struct iso_curve *mask_3Dpolygon_set = NULL;
static TBOOLEAN interior(double x, double y, struct coordinate *mask, int nv);
/*****************************************************************/
/* Routines that deal with global objects defined in this module */
/*****************************************************************/
/* Clipping to the bounding box: */
/* Test a single point to be within the BoundingBox.
* Sets the returned integers 4 l.s.b. as follows:
* bit 0 if to the left of xleft.
* bit 1 if to the right of xright.
* bit 2 if below of ybot.
* bit 3 if above of ytop.
* 0 is returned if inside.
*/
int
clip_point(int x, int y)
{
int ret_val = 0;
if (!clip_area)
return 0;
if ((int)x < clip_area->xleft)
ret_val |= 0x01;
if ((int)x > clip_area->xright)
ret_val |= 0x02;
if ((int)y < clip_area->ybot)
ret_val |= 0x04;
if ((int)y > clip_area->ytop)
ret_val |= 0x08;
return ret_val;
}
/* Clip the given line to drawing coords defined by BoundingBox.
* This routine uses the cohen & sutherland bit mapping for fast clipping -
* see "Principles of Interactive Computer Graphics" Newman & Sproull page 65.
*/
int
draw_clip_line(int x1, int y1, int x2, int y2)
{
struct termentry *t = term;
int state;
state = clip_line(&x1, &y1, &x2, &y2);
if (state != 0) {
(*t->move) (x1, y1);
(*t->vector) (x2, y2);
}
return state;
}
/* Draw a contiguous line path which may be clipped. Compared to
* draw_clip_line(), this routine moves to a coordinate only when
* necessary.
*/
void
draw_clip_polygon(int points, gpiPoint *p)
{
int i;
int x1, y1, x2, y2;
int pos1, pos2, clip_ret;
struct termentry *t = term;
TBOOLEAN continuous = TRUE;
if (points move)(x1, y1);
newpath();
for (i = 1; i < points; i++) {
x2 = p[i].x;
y2 = p[i].y;
pos2 = clip_point(x2, y2);
clip_ret = clip_line(&x1, &y1, &x2, &y2);
if (clip_ret) {
/* there is a line to draw */
if (pos1) /* first vertex was recalculated, move to new start point */
(*t->move)(x1, y1);
(*t->vector)(x2, y2);
} else {
/* Path is not continuous; make sure closepath is not called */
continuous = FALSE;
}
x1 = p[i].x;
y1 = p[i].y;
/* The end point and the line do not necessarily have the same
* status. The end point can be 'inside', but the whole line is
* 'outside'. Do not update pos1 in this case. Bug #1268.
*/
if (!(clip_ret == 0 && pos2 == 0))
pos1 = pos2;
}
/* Only call closepath if the polygon is truly closed; otherwise */
/* a spurious line connecting the start and end is generated. */
if (continuous)
closepath();
}
/*
* arrow is specified in terminal coordinates
* but we use double rather than int so that the precision is sufficient
* to orient and draw the arrow head correctly even for very short vectors.
*/
void
draw_clip_arrow( double dsx, double dsy, double dex, double dey, t_arrow_head head)
{
struct termentry *t = term;
int sx = axis_map_toint(dsx);
int sy = axis_map_toint(dsy);
int ex = axis_map_toint(dex);
int ey = axis_map_toint(dey);
int dx, dy;
/* Don't draw head if the arrow itself is clipped */
if (clip_point(sx,sy))
head &= ~BACKHEAD;
if (clip_point(ex,ey))
head &= ~END_HEAD;
/* clip_line returns 0 if the whole thing is out of range */
if (!clip_line(&sx, &sy, &ex, &ey))
return;
/* Special case code for short vectors */
/* Most terminals are OK with using this code for long vectors also. */
/* However some terminals (e.g. tikz) look terrible because the shaft of a */
/* long vector overruns the head when the head is drawn with HEADS_ONLY. */
/* FIXME: this is a very ad hoc definition of "short". */
dx = abs(ex-sx);
dy = abs(ey-sy);
if (dx < 25 && dy < 25) {
/* draw the body of the vector (rounding errors are a problem) */
if (dx > 1 || dy > 1)
if (!((t->flags & TERM_IS_LATEX)))
(*t->arrow)(sx, sy, ex, ey, SHAFT_ONLY | head);
/* if we're not supposed to be drawing any heads, we're done */
if ((head & BOTH_HEADS) == NOHEAD)
return;
/* If this is truly a 0-vector, then we CAN'T draw the head */
/* because the orientation of the head is indeterminate */
if (dsx == dex && dsy == dey)
return;
/* If the head size is fixed we are free to change the length of */
/* very short vectors so that the orientation is accurate. */
if (curr_arrow_headfixedsize) {
/* Direction vector in (dex,dey). I need to convert this to integers
* with a scale that's large-enough to give me good angular resolution,
* but small-enough to not overflow the data type.
*/
double rescale = 1000. / GPMAX( fabs(dex-dsx), fabs(dey-dsy) );
int newlenx = (dex - dsx) * rescale;
int newleny = (dey - dsy) * rescale;
if (head & END_HEAD)
(*t->arrow)(ex - newlenx, ey - newleny, ex, ey, END_HEAD|HEADS_ONLY);
if (head & BACKHEAD)
(*t->arrow)(sx, sy, sx + newlenx, sy + newleny, BACKHEAD|HEADS_ONLY);
} else {
(*t->arrow)(sx, sy, ex, ey, head|HEADS_ONLY);
}
} else {
/* The normal case, draw the whole thing at once */
(*t->arrow)(sx, sy, ex, ey, head);
}
}
/* Clip the given line to drawing coords defined by BoundingBox.
* This routine uses the cohen & sutherland bit mapping for fast clipping -
* see "Principles of Interactive Computer Graphics" Newman & Sproull page 65.
* Return 0: entire line segment is outside bounding box
* 1: entire line segment is inside bounding box
* -1: line segment has been clipped to bounding box
*/
int
clip_line(int *x1, int *y1, int *x2, int *y2)
{
/* Apr 2014: This algorithm apparently assumed infinite precision
* integer arithmetic. It was failing when passed coordinates that
* were hugely out of bounds because tests for signedness of the
* form (dx * dy > 0) would overflow rather than correctly evaluating
* to (sign(dx) == sign(dy)). Worse yet, the numerical values are
* used to determine which end of the segment to update.
* This is now addressed by making dx and dy (double) rather than (int)
* but it might be better to hard-code the sign tests.
*/
double dx, dy, x, y;
int x_intr[4], y_intr[4], count, pos1, pos2;
int x_max, x_min, y_max, y_min;
pos1 = clip_point(*x1, *y1);
pos2 = clip_point(*x2, *y2);
if (!pos1 && !pos2)
return 1; /* segment is totally in */
if (pos1 & pos2)
return 0; /* segment is totally out. */
/* Here part of the segment MAY be inside. test the intersection
* of this segment with the 4 boundaries for hopefully 2 intersections
* in. If none are found segment is totaly out.
* Under rare circumstances there may be up to 4 intersections (e.g.
* when the line passes directly through at least one corner).
*/
count = 0;
dx = (double)(*x2) - (double)(*x1);
dy = (double)(*y2) - (double)(*y1);
/* Find intersections with the x parallel bbox lines: */
if (dy != 0) {
x = (clip_area->ybot - *y2) * dx / dy + *x2; /* Test for clip_area->ybot boundary. */
if (x >= clip_area->xleft && x xright) {
x_intr[count] = x;
y_intr[count++] = clip_area->ybot;
}
x = (clip_area->ytop - *y2) * dx / dy + *x2; /* Test for clip_area->ytop boundary. */
if (x >= clip_area->xleft && x xright) {
x_intr[count] = x;
y_intr[count++] = clip_area->ytop;
}
}
/* Find intersections with the y parallel bbox lines: */
if (dx != 0) {
y = (clip_area->xleft - *x2) * dy / dx + *y2; /* Test for clip_area->xleft boundary. */
if (y >= clip_area->ybot && y ytop) {
x_intr[count] = clip_area->xleft;
y_intr[count++] = y;
}
y = (clip_area->xright - *x2) * dy / dx + *y2; /* Test for clip_area->xright boundary. */
if (y >= clip_area->ybot && y ytop) {
x_intr[count] = clip_area->xright;
y_intr[count++] = y;
}
}
if (count < 2)
return 0;
/* check which intersections to use, for more than two intersections the first two may be identical */
if ((count > 2) && (x_intr[0] == x_intr[1]) && (y_intr[0] == y_intr[1])) {
x_intr[1] = x_intr[2];
y_intr[1] = y_intr[2];
}
if (*x1 < *x2) {
x_min = *x1;
x_max = *x2;
} else {
x_min = *x2;
x_max = *x1;
}
if (*y1 < *y2) {
y_min = *y1;
y_max = *y2;
} else {
y_min = *y2;
y_max = *y1;
}
if (pos1 && pos2) { /* Both were out - update both */
/* EAM Sep 2008 - preserve direction of line segment */
if ((dx*(x_intr[1]-x_intr[0]) < 0)
|| (dy*(y_intr[1]-y_intr[0]) < 0)) {
*x1 = x_intr[1];
*y1 = y_intr[1];
*x2 = x_intr[0];
*y2 = y_intr[0];
} else {
*x1 = x_intr[0];
*y1 = y_intr[0];
*x2 = x_intr[1];
*y2 = y_intr[1];
}
} else if (pos1) { /* Only x1/y1 was out - update only it */
if (dx * (*x2 - x_intr[0]) + dy * (*y2 - y_intr[0]) > 0) {
*x1 = x_intr[0];
*y1 = y_intr[0];
} else {
*x1 = x_intr[1];
*y1 = y_intr[1];
}
} else { /* Only x2/y2 was out - update only it */
/* Same difference here, again */
if (dx * (x_intr[0] - *x1) + dy * (y_intr[0] - *y1) > 0) {
*x2 = x_intr[0];
*y2 = y_intr[0];
} else {
*x2 = x_intr[1];
*y2 = y_intr[1];
}
}
if (*x1 < x_min || *x1 > x_max || *x2 < x_min || *x2 > x_max || *y1 < y_min || *y1 > y_max || *y2 < y_min || *y2 > y_max)
return 0;
return -1;
}
/* test if coordinates of a vertex are inside boundary box. The start
and end points for the clip_boundary must be in correct order for
this to work properly (see respective definitions in clip_polygon()). */
TBOOLEAN
vertex_is_inside(gpiPoint test_vertex, gpiPoint *clip_boundary)
{
if (clip_boundary[1].x > clip_boundary[0].x) /*bottom edge*/
if (test_vertex.y >= clip_boundary[0].y) return TRUE;
if (clip_boundary[1].x < clip_boundary[0].x) /*top edge*/
if (test_vertex.y clip_boundary[0].y) /*right edge*/
if (test_vertex.x = clip_boundary[1].x) return TRUE;
return FALSE;
}
void
intersect_polyedge_with_boundary(gpiPoint first, gpiPoint second, gpiPoint *intersect, gpiPoint *clip_boundary)
{
/* This routine is called only if one point is outside and the other
* is inside, which implies that clipping is needed at a horizontal
* boundary, that second.y is different from first.y and no division
* by zero occurs. Same for vertical boundary and x coordinates.
* Conversion to double is needed to prevent integer overflow.
*/
double dx = (second.x - first.x);
double dy = (second.y - first.y);
if (clip_boundary[0].y == clip_boundary[1].y) { /* horizontal */
(*intersect).y = clip_boundary[0].y;
(*intersect).x = first.x
+ (double)(clip_boundary[0].y - first.y) * dx/dy;
} else { /* vertical */
(*intersect).x = clip_boundary[0].x;
(*intersect).y = first.y
+ (double)(clip_boundary[0].x - first.x) * dy/dx;
}
}
/* Clip the given polygon to a single edge of the bounding box. */
void
clip_polygon_to_boundary(gpiPoint *in, gpiPoint *out, int in_length, int *out_length, gpiPoint *clip_boundary)
{
gpiPoint prev, curr; /* start and end point of current polygon edge. */
int j;
*out_length = 0;
if (in_length xleft; /* top left */
clip_boundary[0].y = clip_area->ytop;
clip_boundary[1].x = clip_area->xleft; /* bottom left */
clip_boundary[1].y = clip_area->ybot;
clip_boundary[2].x = clip_area->xright; /* bottom right */
clip_boundary[2].y = clip_area->ybot;
clip_boundary[3].x = clip_area->xright; /* top right */
clip_boundary[3].y = clip_area->ytop;
clip_boundary[4] = clip_boundary[0];
memcpy(tmp_corners, in, in_length * sizeof(gpiPoint));
for (i = 0; i < 4; i++) {
clip_polygon_to_boundary(tmp_corners, out, in_length, out_length, clip_boundary+i);
memcpy(tmp_corners, out, *out_length * sizeof(gpiPoint));
in_length = *out_length;
}
}
/* Two routines to emulate move/vector sequence using line drawing routine. */
static int move_pos_x, move_pos_y;
void
clip_move(int x, int y)
{
move_pos_x = x;
move_pos_y = y;
}
void
clip_vector(int x, int y)
{
draw_clip_line(move_pos_x, move_pos_y, x, y);
move_pos_x = x;
move_pos_y = y;
}
/*
* draw_polar_clip_line() assumes that the endpoints have already
* been categorized as INRANGE/OUTRANGE, and that "set clip radial"
* is in effect.
*/
void
draw_polar_clip_line( double xbeg, double ybeg, double xend, double yend)
{
double R; /* radius of limiting circle */
double a, b; /* line expressed as y = a*x + b */
double x1, y1, x2, y2; /* Intersections of line and circle */
double Q, Q2; /* sqrt term of quadratic equation */
TBOOLEAN vertical = FALSE; /* flag for degenerate case */
TBOOLEAN beg_inrange, end_inrange;
if (R_AXIS.set_max == -VERYLARGE)
goto outside;
R = R_AXIS.set_max - R_AXIS.set_min;
/* If both endpoints are inside the limiting circle, draw_clip_line suffices */
beg_inrange = (xbeg*xbeg + ybeg*ybeg) R)
goto outside;
vertical = TRUE;
y1 = sqrt(R*R - x1*x1);
y2 = -y1;
if (!inrange(y1,ybeg,yend) && !inrange(y2,ybeg,yend))
goto outside;
}
/* If one of the original endpoints was INRANGE then use it */
/* rather than the second intersection point. */
if (vertical) {
y1 = GPMIN(y1, GPMAX(ybeg, yend));
y2 = GPMAX(y2, GPMIN(ybeg, yend));
} else if (beg_inrange) {
if (!inrange(x1, xbeg, xend)) {
x1 = xbeg;
y1 = ybeg;
} else {
x2 = xbeg;
y2 = ybeg;
}
} else if (end_inrange) {
if (!inrange(x1, xbeg, xend)) {
x1 = xend;
y1 = yend;
} else {
x2 = xend;
y2 = yend;
}
} else {
/* Both OUTRANGE. Are they on the same side of the circle? */
if (!inrange(x1, xbeg, xend))
goto outside;
}
/* Draw the part of the line inside the bounding circle */
(term->move)(map_x(x1), map_y(y1));
(term->vector)(map_x(x2), map_y(y2));
/* fall through */
outside:
/* Leave current position at unclipped endpoint */
(term->move)(map_x(xend), map_y(yend));
return;
}
/* Common routines for setting text or line color from t_colorspec */
void
apply_pm3dcolor(struct t_colorspec *tc)
{
struct termentry *t = term;
double cbval;
/* V5 - term->linetype(LT_BLACK) would clobber the current */
/* dashtype so instead we use term->set_color(black). */
static t_colorspec black = BLACK_COLORSPEC;
/* Replace colorspec with that of the requested line style */
struct lp_style_type style;
if (tc->type == TC_LINESTYLE) {
lp_use_properties(&style, tc->lt);
tc = &style.pm3d_color;
}
if (tc->type == TC_DEFAULT) {
t->set_color(&black);
return;
}
if (tc->type == TC_LT) {
t->set_color(tc);
return;
}
if (tc->type == TC_RGB) {
/* There are several plausible ways monochrome terminals might handle color request
* (1) Allow all color requests despite the label "monochrome"
* (2) Choose any color you want so long as it is black
* (3) Convert colors to gray scale (NTSC?)
*
* We go for a modified (1):
* Monochrome terminals are still allowed to display rgb variable colors.
*/
if (monochrome_terminal && tc->value >= 0)
t->set_color(&black);
else
t->set_color(tc);
return;
}
/* Leave unchanged. (used only by "set errorbars"??) */
if (tc->type == TC_VARIABLE)
return;
switch (tc->type) {
case TC_Z:
set_color(cb2gray(tc->value));
break;
case TC_CB:
if (CB_AXIS.log)
cbval = (tc->value value;
else
cbval = tc->value;
set_color(cb2gray(cbval));
break;
case TC_FRAC:
set_color(sm_palette.positive == SMPAL_POSITIVE ? tc->value : 1-tc->value);
break;
default:
break; /* cannot happen */
}
}
void
reset_textcolor(const struct t_colorspec *tc)
{
if (tc->type != TC_DEFAULT)
term->linetype(LT_BLACK);
}
void
default_arrow_style(struct arrow_style_type *arrow)
{
static const struct lp_style_type tmp_lp_style =
{0, LT_SOLID, 0, DASHTYPE_SOLID, 0, 0, 1.0, 0.0, DEFAULT_P_CHAR,
DEFAULT_COLORSPEC, DEFAULT_DASHPATTERN};
arrow->tag = -1;
arrow->layer = LAYER_BACK;
arrow->lp_properties = tmp_lp_style;
arrow->head = 1;
arrow->head_length = 0.0;
arrow->head_lengthunit = first_axes;
arrow->head_angle = 15.0;
arrow->head_backangle = 90.0;
arrow->headfill = AS_NOFILL;
arrow->head_fixedsize = FALSE;
}
void
apply_head_properties(struct arrow_style_type *arrow_properties)
{
curr_arrow_headfilled = arrow_properties->headfill;
curr_arrow_headfixedsize = arrow_properties->head_fixedsize;
curr_arrow_headlength = 0;
if (arrow_properties->head_length > 0) {
/* set head length+angle for term->arrow */
double xtmp, ytmp;
struct position headsize = {first_axes,graph,graph,0.,0.,0.};
headsize.x = arrow_properties->head_length;
headsize.scalex = arrow_properties->head_lengthunit;
map_position_r(&headsize, &xtmp, &ytmp, "arrow");
curr_arrow_headangle = arrow_properties->head_angle;
curr_arrow_headbackangle = arrow_properties->head_backangle;
curr_arrow_headlength = xtmp;
}
}
/* Used by arrow-drawing code in graphics.c graph3d.c */
double effective_aspect_ratio(void)
{
#ifdef _WIN32
if (strcmp(term->name, "windows") == 0) {
double xscale = (axis_array[FIRST_X_AXIS].term_upper - axis_array[FIRST_X_AXIS].term_lower)
/ (axis_array[FIRST_X_AXIS].max - axis_array[FIRST_X_AXIS].min);
double yscale = (axis_array[FIRST_Y_AXIS].term_upper - axis_array[FIRST_Y_AXIS].term_lower)
/ (axis_array[FIRST_Y_AXIS].max - axis_array[FIRST_Y_AXIS].min);
double aspect_ratio = fabs(yscale/xscale);
return aspect_ratio;
}
#endif
return (double)term->v_tic / (double)term->h_tic;
}
void
free_labels(struct text_label *label)
{
struct text_label *temp;
char *master_font = label->font;
/* Labels generated by 'plot with labels' all use the same font.
* Free it once and then skip the duplicate appearances below.
* Watchpoint labels also all use the same font, taken from the style,
* but that controlling copy should not be freed here.
*/
if (label->tag == LABEL_TAG_PLOTLABELS || label->tag == LABEL_TAG_VARIABLE_ROTATE)
free(master_font);
while (label) {
if (label->text)
free(label->text);
if (label->font != master_font)
free(label->font);
temp=label->next;
free(label);
label = temp;
}
}
void
get_offsets(
struct text_label *this_label,
int *htic, int *vtic)
{
if ((this_label->lp_properties.flags & LP_SHOW_POINTS)) {
*htic = (pointsize * term->h_tic * 0.5);
*vtic = (pointsize * term->v_tic * 0.5);
} else {
*htic = 0;
*vtic = 0;
}
if (is_3d_plot) {
int htic2, vtic2;
map3d_position_r(&(this_label->offset), &htic2, &vtic2, "get_offsets");
*htic += htic2;
*vtic += vtic2;
} else {
double htic2, vtic2;
map_position_r(&(this_label->offset), &htic2, &vtic2, "get_offsets");
*htic += (int)htic2;
*vtic += (int)vtic2;
}
}
/*
* Write one label, with all the trimmings.
* This routine is used for both 2D and 3D plots.
*/
void
write_label(int x, int y, struct text_label *this_label)
{
int htic, vtic;
int justify = JUST_TOP; /* This was the 2D default; 3D had CENTRE */
textbox_style *textbox = NULL;
apply_pm3dcolor(&(this_label->textcolor));
ignore_enhanced(this_label->noenhanced);
/* The text itself */
if (this_label->hypertext) {
if (this_label->text && *(this_label->text)) {
/* Treat text as hypertext */
char *font = this_label->font;
if (font)
term->set_font(font);
if (term->hypertext)
term->hypertext(TERM_HYPERTEXT_TOOLTIP, this_label->text);
if (font)
term->set_font("");
}
} else {
/* A normal label (always print text) */
get_offsets(this_label, &htic, &vtic);
if (this_label->boxed < 0)
textbox = &textbox_opts[0];
else if (this_label->boxed > 0)
textbox = &textbox_opts[this_label->boxed];
/* Initialize the bounding box accounting */
if (textbox && term->boxed_text && (textbox->opaque || !textbox->noborder))
(*term->boxed_text)(x + htic, y + vtic, TEXTBOX_INIT);
if (this_label->rotate && (*term->text_angle) (this_label->rotate)) {
write_multiline(x + htic, y + vtic, this_label->text,
this_label->pos, justify, this_label->rotate,
this_label->font);
(*term->text_angle) (0);
} else {
write_multiline(x + htic, y + vtic, this_label->text,
this_label->pos, justify, 0, this_label->font);
}
}
if (textbox && term->boxed_text && (textbox->opaque || !textbox->noborder))
{
/* Adjust the bounding box margins */
(*term->boxed_text)((int)(textbox->xmargin * 100.),
(int)(textbox->ymargin * 100.), TEXTBOX_MARGINS);
/* Blank out the box and reprint the label */
if (textbox->opaque) {
apply_pm3dcolor(&textbox->fillcolor);
(*term->boxed_text)(0,0, TEXTBOX_BACKGROUNDFILL);
/* The epslatex/cairolatex terminals reuse a previously saved textbox
* so explicitly re-writing it here is at best redundant.
*/
if ((term->flags & TERM_REUSES_BOXTEXT) == 0) {
apply_pm3dcolor(&(this_label->textcolor));
/* Init for each of fill and border */
if (!textbox->noborder)
(*term->boxed_text)(x + htic, y + vtic, TEXTBOX_INIT);
if (this_label->rotate && (*term->text_angle) (this_label->rotate)) {
write_multiline(x + htic, y + vtic, this_label->text,
this_label->pos, justify, this_label->rotate,
this_label->font);
(*term->text_angle) (0);
} else {
write_multiline(x + htic, y + vtic, this_label->text,
this_label->pos, justify, 0, this_label->font);
}
}
}
/* Draw the bounding box */
if (!textbox->noborder) {
(*term->linewidth)(textbox->linewidth);
apply_pm3dcolor(&textbox->border_color);
(*term->boxed_text)(0,0, TEXTBOX_OUTLINE);
}
(*term->boxed_text)(0,0, TEXTBOX_FINISH);
}
/* The associated point, if any */
/* write_multiline() clips text to on_page; do the same for any point */
if ((this_label->lp_properties.flags & LP_SHOW_POINTS) && on_page(x,y)) {
term_apply_lp_properties(&this_label->lp_properties);
if (this_label->lp_properties.p_type == PT_CHARACTER) {
(*term->justify_text) (CENTRE);
(*term->put_text) (x, y, this_label->lp_properties.p_char);
} else if (this_label->lp_properties.p_type >= 0) {
(*term->point) (x, y, this_label->lp_properties.p_type);
}
/* the default label color is that of border */
term_apply_lp_properties(&border_lp);
}
ignore_enhanced(FALSE);
}
/* STR points to a label string, possibly with several lines separated
by \n. Return the number of characters in the longest line. If
LINES is not NULL, set *LINES to the number of lines in the
label. */
int
label_width(const char *str, int *lines)
{
char *lab = NULL, *s, *e;
int mlen, len, l;
if (!str || *str == '\0') {
if (lines)
*lines = 0;
return (0);
}
l = mlen = len = 0;
lab = gp_alloc(strlen(str) + 2, "in label_width");
strcpy(lab, str);
strcat(lab, "\n");
s = lab;
while ((e = (char *) strchr(s, '\n')) != NULL) {
*e = '\0';
len = estimate_strlen(s, NULL); /* = e-s ? */
if (len > mlen)
mlen = len;
if (len || l || *str == '\n')
l++;
s = ++e;
}
/* lines = NULL => not interested - div */
if (lines)
*lines = l;
free(lab);
return (mlen);
}
/*
* Here so that it can be shared by the 2D and 3D code
*/
void
do_timelabel(int x, int y)
{
struct text_label temp = timelabel;
char str[MAX_LINE_LEN+1];
time_t now;
if (timelabel.rotate == 0 && !timelabel_bottom)
y -= term->v_char;
time(&now);
strftime(str, MAX_LINE_LEN, timelabel.text, localtime(&now));
temp.text = str;
write_label(x, y, &temp);
}
void
init_gadgets()
{
int i;
static t_position y0_wall_corners[5] = WALL_Y0_CORNERS;
static t_position x0_wall_corners[5] = WALL_X0_CORNERS;
static t_position y1_wall_corners[5] = WALL_Y1_CORNERS;
static t_position x1_wall_corners[5] = WALL_X1_CORNERS;
static t_position z0_wall_corners[5] = WALL_Z0_CORNERS;
for (i=0; ilayer == LAYER_DEPTHORDER)
return TRUE;
obj = obj->next;
}
return FALSE;
}
/*
* Place overall title on the canvas (shared by plot and splot).
*/
void
place_title(int title_x, int title_y)
{
if (title.text) {
/* NB: write_label applies text color but does not reset it */
write_label(title_x, title_y, &title);
reset_textcolor(&(title.textcolor));
}
}
/*
* Image pixels or pm3d quadrangles can be filtered by masking against
* a set of polygons that was stored in mask_XXpolygon_set by a previous
* plot/splot command with style POLYGONMASK ("with mask").
* These routines implement a test based on the Jordan curve theorem:
* If a line through a point intersects the bounding curve of a polygon
* an even number of times, the point is exterior. If the line intersects
* the bounding curve an odd number of times, the point is interior.
*
* interior(x,y,) returns TRUE if the point is interior to
* the polygon defined by a list of vertices.
*
* masked(x,y,) returns TRUE if the point is *not*
* interior to any of the polygons in the set.
*/
static TBOOLEAN
interior( double x, double y, struct coordinate *mask, int mask_vertices )
{
int count = 0;
int i, j;
if (mask == NULL)
return FALSE;
for (i = 0, j = mask_vertices-1; i < mask_vertices; j = i++) {
if ( ((mask[i].y > y) != (mask[j].y > y))
&& (x < (mask[j].x-mask[i].x) * (y-mask[i].y) / (mask[j].y-mask[i].y) + mask[i].x)
) {
count++;
}
}
return (count & 0x1);
}
TBOOLEAN
masked( double x, double y, struct iso_curve *mask_set )
{
struct iso_curve *mask;
if (!mask_set)
return FALSE;
for (mask = mask_set; mask; mask = mask->next) {
if (interior(x, y, mask->points, mask->p_count))
return FALSE;
}
return TRUE;
}
/*
* The 2D input stage places polygons in a single list of vertices
* with an UNDEFINED point acting as a separator between polygons.
* construct_2D_mask_set() creates a linked list of iso_curves,
* each pointing to one of the polygons in the original list.
* This allows both the 2D and 3D code to use the same masking tests.
*/
void
construct_2D_mask_set( struct coordinate *points, int p_count )
{
struct iso_curve *mask = mask_2Dpolygon_set;
struct coordinate *polygon;
struct coordinate *end_of_list;
int nv;
/* Free previous mask set, if any */
while (mask) {
struct iso_curve *temp = mask;
mask = mask->next;
free(temp);
}
mask_2Dpolygon_set = NULL;
if (points == NULL || p_count < 3)
return;
/* point type UNDEFINED separates multiple polygons */
end_of_list = &points[p_count];
polygon = &points[0];
nv = 0;
while (&polygon[nv] < end_of_list) {
for (nv = 1; &polygon[nv] < end_of_list; nv++) {
if (polygon[nv].type == UNDEFINED)
break;
}
mask = gp_alloc( sizeof(struct iso_curve), "2D mask set");
mask->next = mask_2Dpolygon_set;
mask->points = polygon;
mask->p_count = nv;
mask->p_max = 0;
mask_2Dpolygon_set = mask;
polygon = &polygon[nv];
nv = 0;
if (polygon < end_of_list)
polygon++;
}
}