ngspice/src/frontend/plotting/graf.c

1559 lines
47 KiB
C
Raw Normal View History

2000-04-27 22:03:57 +02:00
/**********
Copyright 1990 Regents of the University of California. All rights reserved.
Author: 1988 Jeffrey M. Hsu
**********/
/*
* Most of the gr_ module resides here, in particular, gr_init
* and gr_point, expect for the gr_ grid routines.
*
*/
#include "ngspice/ngspice.h"
#include "ngspice/cpdefs.h" /* for CP_ */
#include "ngspice/cpextern.h"
#include "ngspice/plot.h"
#include "ngspice/ftedebug.h" /* for iplot */
#include "ngspice/dvec.h" /* for struct dvec */
#include "ngspice/ftedefs.h" /* for FTEextern.h and IPOINT{MIN,MAX} */
#include "ngspice/fteinput.h"
#include "ngspice/ftedbgra.h"
#include "ngspice/ftedev.h"
#include "ngspice/graph.h"
#include "ngspice/grid.h"
2015-03-10 18:25:26 +01:00
#include "ngspice/sim.h"
#include "ngspice/stringskip.h"
#include "ngspice/evtproto.h"
#include "breakp2.h"
#include "display.h"
2000-04-27 22:03:57 +02:00
#include "graf.h"
#include "graphdb.h"
#include "runcoms.h"
#include "terminal.h"
#include "outitf.h"
2000-04-27 22:03:57 +02:00
2000-04-27 22:03:57 +02:00
static void gr_start_internal(struct dvec *dv, bool copyvec);
static void setflag(struct plot *plot, struct dbcomm *db,
bool value, short mode);
static char *getitright(char *buf, double num);
2000-04-27 22:03:57 +02:00
/* for legends, set in gr_start, reset in gr_iplot and gr_init */
static struct {
int plotno;
int color; /* for assigning unique colors */
int linestyle; /* for assigning line styles */
} cur;
2000-04-27 22:03:57 +02:00
/* invariant: currentgraph contains the current graph */
/* These are what gets plotted as points when you specify point plots */
static char pointchars[128];
2009-12-31 16:42:28 +01:00
#define DEFPOINTCHARS "ox+#*abcdefhgijklmnpqrstuvwyz"
2000-04-27 22:03:57 +02:00
/* Buffer for ticmarks if given a list */
static char ticbuf[1024];
static char *ticlist = ticbuf;
#define MAXTICS 100
#define XFACTOR 1 /* How much to expand the X scale during iplot. */
#define YFACTOR 0.2 /* How much to expand the Y scale during iplot. */
2000-04-27 22:03:57 +02:00
2000-04-27 22:03:57 +02:00
/*
* Start of a new graph.
* Fill in the data that gets displayed.
* Difference from old gr_init
* we don't try to determine the look of the screen from here
* leave to lower level routines
*
*/
int gr_init(double *xlims, double *ylims, /* The size of the screen. */
const char *xname,
const char *plotname, /* What to label things. */
const char *hcopy, /* The raster file. */
int nplots, /* How many plots there will be. */
double xdelta, double ydelta, /* Line increments for the scale. */
GRIDTYPE gridtype, /* The grid type */
PLOTTYPE plottype, /* and the plot type. */
const char *xlabel,
const char *ylabel, /* Labels for axes. */
int xtype, int ytype, /* The types of the data graphed. */
const char *pname,
2020-02-16 11:52:59 +01:00
const char *commandline, /* For xi_zoomdata() */
int prevgraph) /* plot id, if started from a previous plot*/
2000-04-27 22:03:57 +02:00
{
GRAPH *graph, *pgraph;
2000-04-27 22:03:57 +02:00
wordlist *wl;
2010-11-16 21:38:24 +01:00
NG_IGNORE(nplots);
2000-04-27 22:03:57 +02:00
if ((graph = NewGraph()) == (GRAPH *) NULL) {
return FALSE;
}
2000-04-27 22:03:57 +02:00
/*
The global currentgraph will always be the current graph.
2000-04-27 22:03:57 +02:00
*/
SetGraphContext(graph->graphid);
graph->onevalue = (xname ? FALSE : TRUE);
/* communicate filename to plot 5 driver */
if (hcopy) {
graph->devdep = copy(hcopy);
graph->n_byte_devdep = strlen(hcopy) + 1;
}
2000-04-27 22:03:57 +02:00
cur.plotno = 0;
2000-04-27 22:03:57 +02:00
/* note: should do only once, maybe in gr_init_once */
if (!cp_getvar("pointchars", CP_STRING,
pointchars, sizeof(pointchars))) {
(void) strcpy(pointchars, DEFPOINTCHARS);
}
2000-04-27 22:03:57 +02:00
if (!cp_getvar("ticmarks", CP_NUM, &graph->ticmarks, 0)) {
if (cp_getvar("ticmarks", CP_BOOL, NULL, 0)) {
graph->ticmarks = 10;
}
else {
graph->ticmarks = 0;
}
2000-04-27 22:03:57 +02:00
}
if (!cp_getvar("ticchar", CP_STRING, graph->ticchar, 1)) {
strcpy(graph->ticchar, "X");
}
if (cp_getvar("ticlist", CP_LIST, ticlist, 0)) {
wl = vareval("ticlist");
ticlist = wl_flatten(wl);
graph->ticdata = readtics(ticlist);
}
else {
graph->ticdata = NULL;
}
2000-04-27 22:03:57 +02:00
cp_getvar("nolegend", CP_BOOL, &(graph->nolegend), 0);
cp_getvar("nounits", CP_BOOL, &(graph->nounits), 0);
2000-04-27 22:03:57 +02:00
if (!xlims || !ylims) {
internalerror("gr_init: no range specified");
return FALSE;
2000-04-27 22:03:57 +02:00
}
/* save upper and lower limits */
graph->data.xmin = xlims[0];
graph->data.xmax = xlims[1];
graph->data.ymin = ylims[0];
graph->data.ymax = ylims[1];
/* get title into plot window */
if (!pname) {
pname = "(unknown)";
}
if (!plotname) {
plotname = "(unknown)";
}
graph->plotname = tprintf("%s: %s", pname, plotname);
2000-04-27 22:03:57 +02:00
/* restore background color from previous graph, e.g. for zooming,
it will be used in NewViewport(graph) */
if (prevgraph > 0) {
pgraph = FindGraph(prevgraph);
if (pgraph)
graph->mgraphid = prevgraph;
else
prevgraph = graph->mgraphid = 0;
}
else {
graph->mgraphid = 0;
}
2000-04-27 22:03:57 +02:00
/* note: have enum here or some better convention */
if (NewViewport(graph) == 1) {
/* note: where is the error message generated? */
/* note: undo tmallocs */
fprintf(cp_err, "Can't open viewport for graphics.\n");
return (FALSE);
2000-04-27 22:03:57 +02:00
}
/* restore data from previous graph, e.g. for zooming */
if (prevgraph > 0) {
int i;
/* transmit colors */
for (i = 0; i < 25; i++) {
graph->colorarray[i] = pgraph->colorarray[i];
}
strcpy(graph->ticchar, pgraph->ticchar);
graph->ticdata = pgraph->ticdata;
graph->ticmarks = pgraph->ticmarks;
graph->nolegend = pgraph->nolegend;
}
2000-04-27 22:03:57 +02:00
/* layout decisions */
/* note: have to do before gr_fixgrid and after NewViewport */
graph->viewportxoff = graph->fontwidth * 8; /* 8 lines on left */
graph->viewportyoff = graph->fontheight * 4; /* 4 on bottom */
2000-04-27 22:03:57 +02:00
DevClear();
graph->grid.gridtype = gridtype;
graph->plottype = plottype;
graph->grid.xdatatype = xtype;
graph->grid.ydatatype = ytype;
graph->grid.xdelta = xdelta;
graph->grid.ydelta = ydelta;
graph->grid.ysized = 0;
graph->grid.xsized = 0;
if (!graph->onevalue) {
if (xlabel) {
graph->grid.xlabel = copy(xlabel);
}
else {
graph->grid.xlabel = copy(xname);
}
if (ylabel) {
graph->grid.ylabel = copy(ylabel);
}
else {
graph->grid.ylabel = (char *) NULL;
}
}
else {
if (xlabel) {
graph->grid.xlabel = copy(xlabel);
}
else {
graph->grid.xlabel = copy("real");
}
if (ylabel) {
graph->grid.ylabel = copy(ylabel);
}
else {
graph->grid.ylabel = copy("imag");
}
2000-04-27 22:03:57 +02:00
}
2000-04-27 22:03:57 +02:00
gr_resize_internal(graph);
gr_redrawgrid(graph);
/* Set up colors and line styles. */
if (dispdev->numlinestyles == 1) {
cur.linestyle = 0; /* Use the same one all the time. */
}
else {
cur.linestyle = 1;
}
2000-04-27 22:03:57 +02:00
/* XXX Special exception for SMITH */
if (dispdev->numcolors > 2 &&
(graph->grid.gridtype == GRID_SMITH ||
graph->grid.gridtype == GRID_SMITHGRID)) {
cur.color = 3;
}
else {
cur.color = 1;
}
2000-04-27 22:03:57 +02:00
graph->commandline = copy(commandline);
return TRUE;
2000-04-27 22:03:57 +02:00
}
/* Once the line compression code is thorougly tested, checking code can
* be removed. But for now ...
*/
#define LINE_COMPRESSION_CHECKS
/* Data and functions for line compression:
* try to not keep drawing the same pixels by combining co-linear segments.
*/
static struct {
enum { EMPTY, LINE, VERTICAL } state;
int x_start, y_start, x_end, y_end;
int lc_min, lc_max;
#define prev_x2 lc_min // Alternate name
#ifdef LINE_COMPRESSION_CHECKS
struct dvec *dv; // Sanity checking.
#endif
} LC;
/* Flush pending line drawing. */
static void LC_flush(void)
{
switch (LC.state) {
case EMPTY:
return;
case LINE:
DevDrawLine(LC.x_start, LC.y_start, LC.x_end, LC.y_end, FALSE);
break;
case VERTICAL:
DevDrawLine(LC.x_start, LC.lc_min, LC.x_start, LC.lc_max, FALSE);
break;
}
LC.state = EMPTY;
}
/* This replaces DevDrawLine() - low-level line drawing call. */
#ifdef LINE_COMPRESSION_CHECKS
static void drawLine(int x1, int y1, int x2, int y2, struct dvec *dv)
{
if (LC.dv) {
if (LC.dv != dv) {
fprintf(cp_err, "LC: DV changed from %s to %s!\n",
LC.dv->v_name, dv->v_name);
LC_flush();
LC.dv = dv;
}
} else {
LC.dv = dv;
if (LC.state != EMPTY) {
fprintf(cp_err, "LC: State %d but DV NULL.\n", (int)LC.state);
LC_flush();
}
}
#else
static void drawLine(int x1, int y1, int x2, int y2)
{
#endif
switch (LC.state) {
refill:
LC_flush();
// Fall through ...
case EMPTY:
if (x1 == x2) {
/* Vertical */
LC.state = VERTICAL;
LC.x_start = x1;
LC.y_end = y2;
if (y1 < y2) {
LC.lc_min = y1;
LC.lc_max = y2;
} else {
LC.lc_min = y2;
LC.lc_max = y1;
}
} else {
LC.state = LINE;
LC.prev_x2 = x2;
/* Store with LC.x_start < LC.x_end. */
if (x1 < x2) {
LC.x_start = x1;
LC.y_start = y1;
LC.x_end = x2;
LC.y_end = y2;
} else {
LC.x_start = x2;
LC.y_start = y2;
LC.x_end = x1;
LC.y_end = y1;
}
}
break;
case LINE:
if ((int64_t)(x2 - x1) * (LC.y_end - LC.y_start) !=
(int64_t)(y2 - y1) * (LC.x_end - LC.x_start)) {
/* Not in line. */
goto refill;
}
if (x1 != LC.prev_x2) {
/* Not contiguous. */
if (x1 > LC.x_end) {
if (x2 > LC.x_end) {
/* Hole. */
goto refill;
}
LC.x_end = x1;
LC.y_end = y1;
} else if (x1 < LC.x_start) {
if (x2 < LC.x_start) {
/* Hole. */
goto refill;
}
LC.x_start = x1;
LC.y_start = y1;
}
}
if (x2 > LC.x_end) {
LC.x_end = x2;
LC.y_end = y2;
} else if (x2 < LC.x_start) {
LC.x_start = x2;
LC.y_start = y2;
}
LC.prev_x2 = x2;
break;
case VERTICAL:
if (x1 != LC.x_start || x2 != LC.x_start)
goto refill;
if (y1 != LC.y_end) {
/* Not contiguous, check for hole. */
if (y1 < LC.lc_min) {
if (y2 < LC.lc_min)
goto refill;
LC.lc_min = y1;
} else if (y1 > LC.lc_max) {
if (y2 > LC.lc_max)
goto refill;
LC.lc_max = y1;
}
}
if (y2 < LC.lc_min)
LC.lc_min = y2;
else if (y2 > LC.lc_max)
LC.lc_max = y2;
LC.y_end = y2;
break;
}
}
2000-04-27 22:03:57 +02:00
/*
* Add a point to the curve we're currently drawing.
* Should be in between a gr_init() and a gr_end()
* except when iplotting, very bad hack
2000-04-27 22:03:57 +02:00
* Differences from old gr_point:
* We save points here, instead of in lower levels.
* Assume we are in right context
* Save points in data space (not screen space).
* We pass two points in so we can multiplex plots.
*
*/
void gr_point(struct dvec *dv,
double newx, double newy,
double oldx, double oldy,
int np)
2000-04-27 22:03:57 +02:00
{
int oldtox, oldtoy; /* value before clipping */
char pointc[2];
int fromx, fromy, tox, toy;
int ymin, dummy;
DatatoScreen(currentgraph, oldx, oldy, &fromx, &fromy);
DatatoScreen(currentgraph, newx, newy, &tox, &toy);
/* note: we do not particularly want to clip here */
2000-04-27 22:03:57 +02:00
oldtox = tox; oldtoy = toy;
if (!currentgraph->grid.circular) {
if (clip_line(&fromx, &fromy, &tox, &toy,
currentgraph->viewportxoff, currentgraph->viewportyoff,
currentgraph->viewport.width + currentgraph->viewportxoff,
currentgraph->viewport.height + currentgraph->viewportyoff)) {
return;
}
}
else {
if (clip_to_circle(&fromx, &fromy, &tox, &toy,
currentgraph->grid.xaxis.circular.center,
currentgraph->grid.yaxis.circular.center,
currentgraph->grid.xaxis.circular.radius))
return;
2000-04-27 22:03:57 +02:00
}
if (currentgraph->plottype != PLOT_POINT) {
SetLinestyle(dv->v_linestyle);
}
else {
/* if PLOT_POINT,
2000-04-27 22:03:57 +02:00
don't want to plot an endpoint which have been clipped */
if (tox != oldtox || toy != oldtoy) {
return;
}
2000-04-27 22:03:57 +02:00
}
SetColor(dv->v_color);
2000-04-27 22:03:57 +02:00
switch (currentgraph->plottype) {
double *tics;
case PLOT_LIN:
case PLOT_RETLIN:
/* If it's a linear plot, ignore first point since we don't
want to connect with oldx and oldy. */
if (np) {
#ifdef LINE_COMPRESSION_CHECKS
drawLine(fromx, fromy, tox, toy, dv);
#else
drawLine(fromx, fromy, tox, toy);
#endif
} else {
LC_flush(); // May be retrace with non-monotonic x-axis
}
2016-09-17 11:56:35 +02:00
if ((tics = currentgraph->ticdata) != NULL) {
for (; *tics < HUGE; tics++)
if (*tics == (double) np) {
DevDrawText(currentgraph->ticchar, (int) (tox - currentgraph->fontwidth / 2),
(int) (toy - currentgraph->fontheight / 2), 0);
break;
}
}
else if ((currentgraph->ticmarks >0) && (np > 0) &&
(np % currentgraph->ticmarks == 0)) {
/* Draw an 'x' */
DevDrawText(currentgraph->ticchar, (int) (tox - currentgraph->fontwidth / 2),
(int) (toy - currentgraph->fontheight / 2), 0);
}
2000-04-27 22:03:57 +02:00
break;
case PLOT_COMB:
2000-04-27 22:03:57 +02:00
DatatoScreen(currentgraph,
0.0, currentgraph->datawindow.ymin,
&dummy, &ymin);
#ifdef LINE_COMPRESSION_CHECKS
drawLine(tox, ymin, tox, toy, dv);
#else
drawLine(tox, ymin, tox, toy);
#endif
2000-04-27 22:03:57 +02:00
break;
case PLOT_POINT:
2000-04-27 22:03:57 +02:00
/* Here, gi_linestyle is the character used for the point. */
2011-04-30 14:29:19 +02:00
pointc[0] = (char) dv->v_linestyle;
2000-04-27 22:03:57 +02:00
pointc[1] = '\0';
DevDrawText(pointc, (int) (tox - currentgraph->fontwidth / 2),
(int) (toy - currentgraph->fontheight / 2), 0);
default:
2000-04-27 22:03:57 +02:00
break;
}
}
static void gr_start_internal(struct dvec *dv, bool copyvec)
2000-04-27 22:03:57 +02:00
{
struct dveclist *link;
/* Do something special with poles and zeros. Poles are 'x's, and
* zeros are 'o's. */
2015-03-10 18:25:26 +01:00
if (dv->v_type == SV_POLE) {
dv->v_linestyle = 'x';
return;
} else if (dv->v_type == SV_ZERO) {
dv->v_linestyle = 'o';
return;
}
2000-04-27 22:03:57 +02:00
/* Find a (hopefully) new line style and color. */
if (currentgraph->plottype == PLOT_POINT) {
if (pointchars[cur.linestyle - 1]) {
cur.linestyle++;
} else {
cur.linestyle = 2;
}
} else if ((cur.linestyle > 0) &&
(++cur.linestyle == dispdev->numlinestyles)) {
cur.linestyle = 2;
}
if ((cur.color > 0) && (++cur.color == dispdev->numcolors))
cur.color = (((currentgraph->grid.gridtype == GRID_SMITH ||
currentgraph->grid.gridtype == GRID_SMITHGRID) &&
(dispdev->numcolors > 3)) ? 4 : 2);
if (currentgraph->plottype == PLOT_POINT) {
dv->v_linestyle = pointchars[cur.linestyle - 2];
} else {
dv->v_linestyle = cur.linestyle;
}
dv->v_color = cur.color;
2000-04-27 22:03:57 +02:00
/* Save the data so we can refresh */
link = TMALLOC(struct dveclist, 1);
2000-04-27 22:03:57 +02:00
link->next = currentgraph->plotdata;
/* Either reuse input vector or copy depnding on copyvec */
2000-04-27 22:03:57 +02:00
if (copyvec) {
link->vector = vec_copy(dv);
/* vec_copy doesn't set v_color or v_linestyle */
link->vector->v_color = dv->v_color;
link->vector->v_linestyle = dv->v_linestyle;
link->vector->v_flags |= VF_PERMANENT;
link->f_own_vector = TRUE;
} else {
link->vector = dv;
link->f_own_vector = FALSE;
2000-04-27 22:03:57 +02:00
}
currentgraph->plotdata = link;
/* Copy the scale vector, add it to the vector as v_scale
* and use the copy instead of the original scale vector if requested */
{
struct dvec * const custom_scale = dv->v_scale;
if (custom_scale != (struct dvec*) NULL) {
if (copyvec) {
currentgraph->plotdata->vector->v_scale = vec_copy(dv->v_scale);
currentgraph->plotdata->vector->v_scale->v_flags |= VF_PERMANENT;
}
}
}
2000-04-27 22:03:57 +02:00
/* Put the legend entry on the screen. */
if (!currentgraph->nolegend)
drawlegend(currentgraph, cur.plotno++, dv);
}
/* Start one plot of a graph */
void gr_start(struct dvec *dv)
2000-04-27 22:03:57 +02:00
{
gr_start_internal(dv, TRUE);
} /* end of function gr_start */
2000-04-27 22:03:57 +02:00
2000-04-27 22:03:57 +02:00
/* make sure the linestyles in this graph don't exceed the number of
linestyles available in the current display device */
2000-04-27 22:03:57 +02:00
void
gr_relinestyle(GRAPH *graph)
{
struct dveclist *link;
for (link = graph->plotdata; link; link = link->next) {
if (graph->plottype == PLOT_POINT)
continue;
if (!(link->vector->v_linestyle < dispdev->numlinestyles))
link->vector->v_linestyle %= dispdev->numlinestyles;
if (!(link->vector->v_color < dispdev->numcolors))
link->vector->v_color %= dispdev->numcolors;
2000-04-27 22:03:57 +02:00
}
}
/* PN static */
void drawlegend(GRAPH *graph, int plotno, struct dvec *dv)
2000-04-27 22:03:57 +02:00
{
const int x = (plotno % 2) ?
graph->viewportxoff : (graph->viewport.width / 2);
const int x_base = x + graph->viewport.width / 20;
const int y = graph->absolute.height - graph->fontheight
- ((plotno + 2) / 2) * (graph->fontheight);
const int i = y + graph->fontheight / 2 + 1;
SetColor(dv->v_color);
2000-04-27 22:03:57 +02:00
if (graph->plottype == PLOT_POINT) {
char buf[16];
2000-04-27 22:03:57 +02:00
(void) sprintf(buf, "%c : ", dv->v_linestyle);
DevDrawText(buf, x_base - 3 * graph->fontwidth, y, 0);
}
else {
2000-04-27 22:03:57 +02:00
SetLinestyle(dv->v_linestyle);
DevDrawLine(x, i, x + graph->viewport.width / 20, i, FALSE);
2000-04-27 22:03:57 +02:00
}
SetColor(1);
DevDrawText(dv->v_name, x + graph->viewport.width / 20
+ graph->fontwidth, y, 0);
}
2000-04-27 22:03:57 +02:00
2000-04-27 22:03:57 +02:00
/* end one plot of a graph */
void gr_end(struct dvec *dv)
2000-04-27 22:03:57 +02:00
{
LC_flush();
#ifdef LINE_COMPRESSION_CHECKS
if (LC.dv && LC.dv != dv)
fprintf(cp_err, "LC: DV changed in gr_end()!\n");
else
LC.dv = NULL;
#else
2010-11-16 21:38:24 +01:00
NG_IGNORE(dv);
#endif
DevUpdate();
2000-04-27 22:03:57 +02:00
}
2000-04-27 22:03:57 +02:00
/* Print text in the bottom line. */
void gr_pmsg(char *text)
2000-04-27 22:03:57 +02:00
{
char buf[BSIZE_SP];
buf[0] = '\0';
2000-04-27 22:03:57 +02:00
DevUpdate();
2000-04-27 22:03:57 +02:00
if (cp_getvar("device", CP_STRING, buf, sizeof(buf)) && !(strcmp("/dev/tty", buf) == 0))
fprintf(cp_err, "%s", text);
else if (currentgraph->grid.xlabel)
/* MW. grid.xlabel may be NULL */
DevDrawText(text, currentgraph->viewport.width -
(int) (strlen(currentgraph->grid.xlabel) + 3) *
currentgraph->fontwidth,
currentgraph->absolute.height - currentgraph->fontheight, 0);
2000-04-27 22:03:57 +02:00
else
fprintf(cp_err, " %s \n", text);
DevUpdate();
2000-04-27 22:03:57 +02:00
}
void gr_clean(void)
2000-04-27 22:03:57 +02:00
{
DevUpdate();
2000-04-27 22:03:57 +02:00
}
2000-04-27 22:03:57 +02:00
/* call this routine after viewport size changes */
void gr_resize(GRAPH *graph)
2000-04-27 22:03:57 +02:00
{
double oldxratio, oldyratio;
double scalex, scaley;
struct _keyed *k;
oldxratio = graph->aspectratiox;
oldyratio = graph->aspectratioy;
graph->grid.xsized = 0;
graph->grid.ysized = 0;
gr_resize_internal(graph);
/* scale keyed text */
scalex = oldxratio / graph->aspectratiox;
scaley = oldyratio / graph->aspectratioy;
2000-04-27 22:03:57 +02:00
for (k = graph->keyed; k; k = k->next) {
k->x = (int)((k->x - graph->viewportxoff) * scalex + graph->viewportxoff);
k->y = (int)((k->y - graph->viewportyoff) * scaley + graph->viewportyoff);
2000-04-27 22:03:57 +02:00
}
/* X also generates an expose after a resize.
This is handled in X10 by not redrawing on resizes and waiting
for the expose event to redraw. In X11, the expose routine
tries to be clever and only redraws the region specified in an
expose event, which does not cover the entire region of the
plot if the resize was from a small window to a larger window.
So in order to keep the clever X11 expose event handling, we
have the X11 resize routine pull out expose events for that
window, and we redraw on resize also. */
2000-04-27 22:03:57 +02:00
#ifdef X_DISPLAY_MISSING
gr_redraw(graph);
#endif
}
/* PN static */
void gr_resize_internal(GRAPH *graph)
2000-04-27 22:03:57 +02:00
{
if (!graph->grid.xsized)
graph->viewport.width = (int)(graph->absolute.width -
1.4 * graph->viewportxoff);
2000-04-27 22:03:57 +02:00
if (!graph->grid.ysized)
graph->viewport.height = graph->absolute.height -
2 * graph->viewportyoff;
2000-04-27 22:03:57 +02:00
gr_fixgrid(graph, graph->grid.xdelta, graph->grid.ydelta,
graph->grid.xdatatype, graph->grid.ydatatype);
2000-04-27 22:03:57 +02:00
/* cache width and height info to make DatatoScreen go fast */
/* note: XXX see if this is actually used anywhere */
graph->datawindow.width = graph->datawindow.xmax -
graph->datawindow.xmin;
2000-04-27 22:03:57 +02:00
graph->datawindow.height = graph->datawindow.ymax -
graph->datawindow.ymin;
2000-04-27 22:03:57 +02:00
/* cache (datawindow size) / (viewport size) */
graph->aspectratiox = graph->datawindow.width / graph->viewport.width;
graph->aspectratioy = graph->datawindow.height / graph->viewport.height;
}
2000-04-27 22:03:57 +02:00
/* redraw everything in struct graph */
void gr_redraw(GRAPH *graph)
2000-04-27 22:03:57 +02:00
{
struct dveclist *link;
/* establish current graph so default graphic calls will work right */
PushGraphContext(graph);
DevClear();
/* redraw grid */
gr_redrawgrid(graph);
cur.plotno = 0;
for (link = graph->plotdata; link; link = link->next) {
/* redraw legend */
if (!graph->nolegend)
drawlegend(graph, cur.plotno++, link->vector);
/* replot data
if onevalue, pass it a NULL scale
otherwise, if vec has its own scale, pass that
else pass vec's plot's scale
*/
ft_graf(link->vector,
graph->onevalue ? NULL :
(link->vector->v_scale ?
link->vector->v_scale :
link->vector->v_plot->pl_scale),
TRUE);
2000-04-27 22:03:57 +02:00
}
gr_restoretext(graph);
PopGraphContext();
}
void gr_restoretext(GRAPH *graph)
2000-04-27 22:03:57 +02:00
{
struct _keyed *k;
/* restore text */
for (k = graph->keyed; k; k = k->next) {
SetColor(k->colorindex);
DevDrawText(k->text, k->x, k->y, 0);
2000-04-27 22:03:57 +02:00
}
}
/* Do some incremental plotting. There are 3 cases:
*
* First, if length < IPOINTMIN, don't do anything.
*
* Second, if length = IPOINTMIN, plot what we have so far. This step
* is essentially the initializaiton for the graph.
*
* Third, if length > IPOINTMIN, plot the last points and resize if
* needed.
*
2000-04-27 22:03:57 +02:00
* Note we don't check for pole / zero because they are of length 1.
*
* FIXME: there is a problem with multiple iplots that use the same
* vector, namely, that vector has the same color throughout. This is
* another reason why we need to pull color and linestyle out of dvec
* XXX Or maybe even something more drastic ??
* It would be better to associate a color with an instance using a
* vector than the vector itself, for which color is something artificial. */
static int iplot(struct plot *pl, struct dbcomm *db)
2000-04-27 22:03:57 +02:00
{
double window;
int len = pl->pl_scale->v_length;
if (ft_grdb) {
fprintf(cp_err, "Entering iplot, len = %d\n", len);
}
/* Do simple check for exit first */
window = db->db_value1;
if (len < 2 || db->db_op > len) { /* Nothing yet */
return 0;
}
struct dvec *v, *xs = pl->pl_scale;
double *lims, dy;
double start, stop, step;
bool changed = FALSE;
int id, yt;
double xlims[2], ylims[2];
int inited = 0;
int n_vec_plot = 0;
2000-04-27 22:03:57 +02:00
/* Exit if nothing is being plotted */
for (v = pl->pl_dvecs; v; v = v->v_next) {
if (v->v_flags & VF_PLOT) {
++n_vec_plot;
}
}
2000-04-27 22:03:57 +02:00
if (n_vec_plot == 0) {
return 0;
}
id = db->db_graphid;
if (!id) { /* Do initialization */
unsigned int index, node_len;
char commandline[4196];
strcpy(commandline, "plot ");
index = 5;
resumption = FALSE;
2000-04-27 22:03:57 +02:00
/* Draw the grid for the first time, and plot everything. */
2000-04-27 22:03:57 +02:00
lims = ft_minmax(xs, TRUE);
xlims[0] = lims[0];
xlims[1] = lims[1];
if (window) {
if (xlims[1] - xlims[0] > window) {
xlims[1] += window / 3.0; // Assume increasing scale.
xlims[0] = xlims[1] - window;
} else {
xlims[1] = xlims[0] + window;
}
}
2000-04-27 22:03:57 +02:00
ylims[0] = HUGE;
ylims[1] = -ylims[0];
for (v = pl->pl_dvecs; v; v = v->v_next) {
2000-04-27 22:03:57 +02:00
if (v->v_flags & VF_PLOT) {
lims = ft_minmax(v, TRUE);
if (ylims[0] > lims[0]) {
ylims[0] = lims[0];
}
if (ylims[1] < lims[1]) {
ylims[1] = lims[1];
}
node_len = (unsigned int)snprintf(commandline + index,
(sizeof commandline) - index,
"%s ", v->v_name);
if (commandline[index + node_len - 1] == ' ') // Not truncated
index += node_len;
else
commandline[index] = '\0'; // Crop partial name
2000-04-27 22:03:57 +02:00
}
}
/* Generate a small difference between ymin and ymax
to catch the y=const case */
if (ylims[0] == ylims[1]) {
ylims[1] += 1e-9;
}
if (ft_grdb) {
fprintf(cp_err,
"iplot: at start xlims = %G, %G, ylims = %G, %G\n",
xlims[0], xlims[1], ylims[0], ylims[1]);
}
for (yt = pl->pl_dvecs->v_type, v = pl->pl_dvecs->v_next; v;
v = v->v_next) {
if ((v->v_flags & VF_PLOT) && ((int) v->v_type != yt)) {
2015-03-10 18:25:26 +01:00
yt = SV_NOTYPE;
2000-04-27 22:03:57 +02:00
break;
}
}
2000-04-27 22:03:57 +02:00
(void) gr_init(xlims, ylims, xs->v_name,
pl->pl_title, NULL, n_vec_plot, 0.0, 0.0,
GRID_LIN, PLOT_LIN, xs->v_name, "V", xs->v_type, yt,
2020-02-16 11:52:59 +01:00
plot_cur->pl_typename, commandline, 0);
for (v = pl->pl_dvecs; v; v = v->v_next) {
2000-04-27 22:03:57 +02:00
if (v->v_flags & VF_PLOT) {
gr_start_internal(v, FALSE);
ft_graf(v, xs, TRUE);
2000-04-27 22:03:57 +02:00
}
}
2000-04-27 22:03:57 +02:00
inited = 1;
} else {
if (!currentgraph) /* Window was closed? */
return 0;
/* Plot the latest points and resize if needed.
* First see if we have to make the screen bigger.
*/
2000-04-27 22:03:57 +02:00
dy = (isreal(xs) ? xs->v_realdata[len - 1] :
realpart(xs->v_compdata[len - 1]));
if (ft_grdb) {
2010-09-19 18:30:39 +02:00
fprintf(cp_err, "x = %G\n", dy);
}
2000-04-27 22:03:57 +02:00
if (!if_tranparams(ft_curckt, &start, &stop, &step) ||
!ciprefix("tran", pl->pl_typename)) {
2000-04-27 22:03:57 +02:00
stop = HUGE;
start = - stop;
}
/* checking for x lo */
if (dy < currentgraph->data.xmin) {
2000-04-27 22:03:57 +02:00
changed = TRUE;
if (ft_grdb) {
fprintf(cp_err, "resize: xlo %G -> %G\n",
currentgraph->data.xmin,
currentgraph->data.xmin -
(currentgraph->data.xmax - currentgraph->data.xmin)
* XFACTOR);
}
/* set the new x lo value */
if (window) {
currentgraph->data.xmin = dy - (window / 3.0);
} else {
currentgraph->data.xmin -=
(currentgraph->data.xmax - currentgraph->data.xmin) *
XFACTOR;
2000-04-27 22:03:57 +02:00
}
if (currentgraph->data.xmin < start)
currentgraph->data.xmin = start;
2000-04-27 22:03:57 +02:00
}
/* checking for x hi */
if (window && changed) {
currentgraph->data.xmax = currentgraph->data.xmin + window;
} else if (dy > currentgraph->data.xmax) {
2000-04-27 22:03:57 +02:00
changed = TRUE;
if (ft_grdb) {
2010-09-19 18:30:39 +02:00
fprintf(cp_err, "resize: xhi %G -> %G\n",
currentgraph->data.xmax,
currentgraph->data.xmax +
(currentgraph->data.xmax - currentgraph->data.xmin)
* XFACTOR);
}
/* set the new x hi value */
if (window) {
currentgraph->data.xmax = dy + (window / 3.0);
currentgraph->data.xmin = currentgraph->data.xmax - window;
} else {
currentgraph->data.xmax +=
(currentgraph->data.xmax - currentgraph->data.xmin) *
XFACTOR;
2000-04-27 22:03:57 +02:00
}
if (currentgraph->data.xmax > stop)
currentgraph->data.xmax = stop;
2000-04-27 22:03:57 +02:00
}
if (currentgraph->data.xmax < currentgraph->data.xmin)
currentgraph->data.xmax = currentgraph->data.xmin;
/* checking for all y values */
2000-04-27 22:03:57 +02:00
for (v = pl->pl_dvecs; v; v = v->v_next) {
int l;
if (!(v->v_flags & VF_PLOT)) {
2000-04-27 22:03:57 +02:00
continue;
}
l = v->v_length - 1;
dy = (isreal(v) ? v->v_realdata[l] :
realpart(v->v_compdata[l]));
if (ft_grdb) {
2010-09-19 18:30:39 +02:00
fprintf(cp_err, "y = %G\n", dy);
}
/* checking for y lo */
2000-04-27 22:03:57 +02:00
while (dy < currentgraph->data.ymin) {
changed = TRUE;
if (ft_grdb) {
fprintf(cp_err, "resize: ylo %G -> %G\n",
currentgraph->data.ymin,
currentgraph->data.ymin -
(currentgraph->data.ymax - currentgraph->data.ymin)
* YFACTOR);
}
/* set the new y lo value */
2000-04-27 22:03:57 +02:00
currentgraph->data.ymin -=
(currentgraph->data.ymax - currentgraph->data.ymin)
* YFACTOR;
/* currentgraph->data.ymin +=
(dy - currentgraph->data.ymin) * YFACTOR;*/
/* currentgraph->data.ymin = dy;
currentgraph->data.ymin *= (1 + YFACTOR); */
2000-04-27 22:03:57 +02:00
}
/* checking for y hi */
2000-04-27 22:03:57 +02:00
while (dy > currentgraph->data.ymax) {
changed = TRUE;
if (ft_grdb) {
fprintf(cp_err, "resize: yhi %G -> %G\n",
currentgraph->data.ymax,
currentgraph->data.ymax +
(currentgraph->data.ymax - currentgraph->data.ymin)
* YFACTOR);
}
/* set the new y hi value */
2000-04-27 22:03:57 +02:00
currentgraph->data.ymax +=
(currentgraph->data.ymax - currentgraph->data.ymin) *
YFACTOR;
/* currentgraph->data.ymax +=
(dy - currentgraph->data.ymax) * YFACTOR;*/
/* currentgraph->data.ymax = dy;
currentgraph->data.ymax *= (1 + YFACTOR); */
2000-04-27 22:03:57 +02:00
}
}
if (currentgraph->data.ymax < currentgraph->data.ymin)
currentgraph->data.ymax = currentgraph->data.ymin;
2000-04-27 22:03:57 +02:00
if (changed) {
/* Redraw everything. */
gr_pmsg("Resizing screen");
gr_resize(currentgraph);
#ifndef X_DISPLAY_MISSING
2000-04-27 22:03:57 +02:00
gr_redraw(currentgraph);
#endif
} else {
/* Draw latest point for each vector. */
for (v = pl->pl_dvecs; v; v = v->v_next) {
if (v->v_flags & VF_PLOT) {
if (v->v_flags & VF_EVENT_NODE) {
int last;
if (xs->v_type != SV_TIME) {
/* There will be only one value, ignore. */
continue;
}
last = v->v_length - 1;
if (len > 2 &&
v->v_scale->v_realdata[last] <
xs->v_realdata[len - 1]) {
/* No recent additions to this vector:
* draw/extend horizontal line showing last value.
* The rest is drawn in callback new_event().
*/
gr_point(v,
xs->v_realdata[len - 1],
v->v_realdata[last],
v->v_scale->v_realdata[last],
v->v_realdata[last],
len - 1);
}
} else {
/* Just connect the last two points.
* This won't be done with curve interpolation,
* so it might look funny. */
gr_point(v,
(isreal(xs) ? xs->v_realdata[len - 1] :
realpart(xs->v_compdata[len - 1])),
(isreal(v) ? v->v_realdata[len - 1] :
realpart(v->v_compdata[len - 1])),
(isreal(xs) ? xs->v_realdata[len - 2] :
realpart(xs->v_compdata[len - 2])),
(isreal(v) ? v->v_realdata[len - 2] :
realpart(v->v_compdata[len - 2])),
len - 1);
}
LC_flush(); // Disable line compression here ..
#ifdef LINE_COMPRESSION_CHECKS
LC.dv = NULL; // ... and suppress warnings.
#endif
}
}
#if BAD
}
else {
/* Just connect the last two points. This won't be done
* with curve interpolation, so it might look funny. */
for (v = pl->pl_dvecs; v; v = v->v_next) {
if ((v->v_flags & VF_PLOT) && !(v->v_flags & VF_EVENT_NODE)) {
gr_point(v,
(isreal(xs) ? xs->v_realdata[len - 1] :
realpart(xs->v_compdata[len - 1])),
(isreal(v) ? v->v_realdata[len - 1] :
realpart(v->v_compdata[len - 1])),
(isreal(xs) ? xs->v_realdata[len - 2] :
realpart(xs->v_compdata[len - 2])),
(isreal(v) ? v->v_realdata[len - 2] :
realpart(v->v_compdata[len - 2])),
len - 1);
LC_flush(); // Disable line compression here ..
#ifdef LINE_COMPRESSION_CHECKS
LC.dv = NULL; // ... and suppress warnings.
#endif
2000-04-27 22:03:57 +02:00
}
}
#endif // BAD
2000-04-27 22:03:57 +02:00
}
}
DevUpdate();
return inited;
2000-04-27 22:03:57 +02:00
}
static void setflag(struct plot *plot, struct dbcomm *db,
bool value, short mode)
2000-04-27 22:03:57 +02:00
{
struct dvec *v;
struct dbcomm *dc;
if (db->db_type == DB_IPLOTALL || db->db_type == DB_TRACEALL) {
for (v = plot->pl_dvecs; v; v = v->v_next)
if (value)
v->v_flags |= mode;
else
v->v_flags &= (short) ~mode;
return;
2000-04-27 22:03:57 +02:00
}
2000-04-27 22:03:57 +02:00
for (dc = db; dc; dc = dc->db_also) {
if (dc->db_nodename1 == NULL)
continue;
v = vec_fromplot(dc->db_nodename1, plot);
2000-04-27 22:03:57 +02:00
if (!v || v->v_plot != plot) {
if (!eq(dc->db_nodename1, "0") && value) {
fprintf(cp_err, "Warning: node %s non-existent in %s.\n",
dc->db_nodename1, plot->pl_name);
/* note: XXX remove it from dbs, so won't get further errors */
}
continue;
2000-04-27 22:03:57 +02:00
}
if (value)
v->v_flags |= mode;
else
v->v_flags &= (short) ~mode;
2000-04-27 22:03:57 +02:00
}
}
static char *getitright(char *buf, double num)
2000-04-27 22:03:57 +02:00
{
char *p;
int k;
sprintf(buf, " % .5g", num);
p = strchr(buf, '.');
2000-04-27 22:03:57 +02:00
if (p) {
return p - 4;
2000-04-27 22:03:57 +02:00
} else {
k = (int) strlen(buf);
if (k > 8)
return buf + 4;
else /* k >= 4 */
return buf + k - 4;
2000-04-27 22:03:57 +02:00
}
}
static int hit, hit2;
void reset_trace(void)
2000-04-27 22:03:57 +02:00
{
hit = -1;
hit2 = -1;
2000-04-27 22:03:57 +02:00
}
/* This function is called from XSPICE whan an event node that is
* being i-plotted has a confirmed new value.
*/
#ifdef XSPICE
static Mif_Boolean_t new_event(double when, Mif_Value_t *val,
void *ctx, int is_last)
{
struct dbcomm *db = (struct dbcomm *)ctx;
struct dvec *v;
int last;
if (db->db_type == DB_DEADIPLOT)
return MIF_TRUE;
v = vec_fromplot(db->db_nodename1, plot_cur);
if (!v || !v->v_scale)
return MIF_TRUE;
/* Extend vectors. */
last = v->v_length - 1;
AddRealValueToVector(v->v_scale, when);
AddRealValueToVector(v->v_scale, when);
AddRealValueToVector(v, v->v_realdata[last]);
AddRealValueToVector(v, val->rvalue);
if (db->db_graphid) {
GRAPH *gr;
gr = FindGraph(db->db_graphid);
if (gr) {
PushGraphContext(gr);
/* Draw horizontal and vertical lines. */
gr_point(v, when, v->v_realdata[last],
v->v_scale->v_realdata[last], v->v_realdata[last],
last > 0);
gr_point(v, when, val->rvalue, when, v->v_realdata[last], 1);
if (is_last) {
struct dvec *xs;
/* Extend horizontally to the end of the time-step. */
xs = v->v_plot->pl_scale;
gr_point(v, xs->v_realdata[xs->v_length - 1], val->rvalue,
when, val->rvalue, 1);
}
LC_flush();
#ifdef LINE_COMPRESSION_CHECKS
LC.dv = NULL; // ... and suppress warnings.
#endif
PopGraphContext();
}
}
return MIF_FALSE;
}
#endif
void gr_iplot(struct plot *plot)
2000-04-27 22:03:57 +02:00
{
static REQUEST reqst = { checkup_option, NULL };
struct dbcomm *db, *dc;
int dontpop; /* So we don't pop w/o push. */
char buf[30];
if (Have_graph) {
/* There is at least one graph. Process input on graph windows. */
Input(&reqst, NULL);
}
2000-04-27 22:03:57 +02:00
hit = 0;
for (db = dbs; db; db = db->db_next) {
if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) {
#ifdef XSPICE
if (db->db_iteration == 0) {
struct dvec *v;
/* First call: find any XSPICE event nodes in the node
* list and set up plotting. There is a parallel path
* for pushing new event values into their corresponding
* vectors and plotting them.
*/
for (dc = db; dc; dc = dc->db_also) {
struct dbcomm *dd;
int dup = 0;
if (dc->db_nodename1 == NULL)
continue;
/* Duplicated names will corrupt the plot. */
for (dd = db; dd != dc; dd = dd->db_also) {
if (!strcmp(dc->db_nodename1, dd->db_nodename1)) {
dup = 1;
break;
}
}
if (dup)
continue;
v = vec_fromplot(dc->db_nodename1, plot);
if (!v) {
fprintf(cp_err,
"Warning: node %s non-existent in %s.\n",
dc->db_nodename1, plot->pl_name);
}
if (v && (v->v_flags & VF_EVENT_NODE)) {
if ((v->v_flags & VF_PERMANENT) == 0) {
vec_new(v);
v->v_flags |= VF_PERMANENT; // Make it findable.
if (v->v_scale) {
v->v_scale->v_flags |= VF_PERMANENT;
vec_new(v->v_scale);
}
}
EVTnew_value_call(dc->db_nodename1, new_event,
Evt_Cbt_Plot, dc);
}
}
db->db_iteration = 1;
}
#endif
if (db->db_graphid) {
GRAPH *gr;
2000-04-27 22:03:57 +02:00
gr = FindGraph(db->db_graphid);
if (!gr)
continue;
PushGraphContext(gr);
}
2000-04-27 22:03:57 +02:00
/* Temporarily set plot flag on matching vector. */
setflag(plot, db, TRUE, VF_PLOT);
2000-04-27 22:03:57 +02:00
dontpop = 0;
if (iplot(plot, db)) {
/* Graph just assigned, place id into every struct dbcomm
* as event nodes need that.
*/
for (dc = db; dc; dc = dc->db_also)
dc->db_graphid = currentgraph->graphid;
dontpop = 1;
}
2000-04-27 22:03:57 +02:00
setflag(plot, db, FALSE, VF_PLOT);
if (!dontpop && db->db_graphid)
PopGraphContext();
} else if (db->db_type == DB_TRACENODE || db->db_type == DB_TRACEALL) {
struct dvec *v, *u;
int len;
setflag(plot, db, TRUE, VF_PRINT);
len = plot->pl_scale->v_length;
dontpop = 0;
for (v = plot->pl_dvecs; v; v = v->v_next) {
if (v->v_flags & VF_PRINT) {
u = plot->pl_scale;
if (len <= 1 || hit <= 0 || hit2 < 0) {
if (len <= 1 || hit2 < 0) {
term_clear();
}
else {
term_home();
}
hit = 1;
hit2 = 1;
printf(
"\tExecution trace (remove with the \"delete\" command)");
term_cleol();
printf("\n");
if (u) {
printf("%12s:", u->v_name);
if (isreal(u)) {
printf("%s",
getitright(buf, u->v_realdata[len - 1]));
}
else {
/* MW. Complex data here, realdata is NULL
(why someone use realdata here again) */
printf("%s",
getitright(buf, u->v_compdata[len - 1].cx_real));
printf(", %s",
getitright(buf, u->v_compdata[len - 1].cx_imag));
}
term_cleol();
printf("\n");
}
}
if (v == u) {
continue;
}
printf("%12s:", v->v_name);
if (isreal(v)) {
printf("%s", getitright(buf, v->v_realdata[len - 1]));
}
else {
/* MW. Complex data again */
printf("%s", getitright(buf, v->v_compdata[len - 1].cx_real));
printf(", %s", getitright(buf, v->v_compdata[len - 1].cx_imag));
}
term_cleol();
printf("\n");
}
}
setflag(plot, db, FALSE, VF_PRINT);
}
2000-04-27 22:03:57 +02:00
}
}
/* This gets called after iplotting is done. We clear out the
* db_graphid fields. Copy the dvecs, which we referenced by
* reference, so DestroyGraph gets to free its own copy.
*
* Note: This is a clear case for separating the linestyle and color
* fields from dvec. */
2000-04-27 22:03:57 +02:00
void gr_end_iplot(void)
2000-04-27 22:03:57 +02:00
{
struct dbcomm *db, *prev, *next;
GRAPH *graph;
struct dveclist *link;
struct dvec *dv;
prev = NULL;
for (db = dbs; db; prev = db, db = next) {
next = db->db_next;
if (db->db_type == DB_DEADIPLOT) {
if (db->db_graphid) {
DestroyGraph(db->db_graphid);
if (prev)
prev->db_next = next;
else
ft_curckt->ci_dbs = dbs = next;
dbfree1(db);
}
}
else if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) {
db->db_iteration = 0; // Reset XSPICE event plotting
if (db->db_graphid) {
/* get private copy of dvecs */
graph = FindGraph(db->db_graphid);
link = graph->plotdata;
while (link) {
dv = link->vector;
link->vector = vec_copy(dv);
/* vec_copy doesn't set v_color or v_linestyle */
link->vector->v_color = dv->v_color;
link->vector->v_linestyle = dv->v_linestyle;
link->vector->v_flags |= VF_PERMANENT;
link = link->next;
}
db->db_graphid = 0;
} else {
/* warn that this wasn't plotted */
fprintf(cp_err, "Warning: iplot %d was not executed.\n",
db->db_number);
}
2000-04-27 22:03:57 +02:00
}
}
}
double *readtics(char *string)
2000-04-27 22:03:57 +02:00
{
2012-12-01 16:04:47 +01:00
int k;
char *words, *worde;
double *tics, *ticsk;
2000-04-27 22:03:57 +02:00
tics = TMALLOC(double, MAXTICS);
ticsk = tics;
words = string;
2000-04-27 22:03:57 +02:00
2012-12-01 16:04:47 +01:00
for (k = 0; *words && k < MAXTICS; words = worde) {
2000-04-27 22:03:57 +02:00
words = skip_ws(words);
2000-04-27 22:03:57 +02:00
worde = words;
while (isalpha_c(*worde) || isdigit_c(*worde))
worde++;
2000-04-27 22:03:57 +02:00
if (*worde)
*worde++ = '\0';
2000-04-27 22:03:57 +02:00
sscanf(words, "%lf", ticsk++);
2000-04-27 22:03:57 +02:00
k++;
2000-04-27 22:03:57 +02:00
}
*ticsk = HUGE;
return (tics);
2000-04-27 22:03:57 +02:00
}