1 /**********
2  * Copyright 1992 Regents of the University of California.  All rights reserved.
3  * Author: 1992 David A. Gates, U. C. Berkeley CAD Group
4  *
5  * Author: 2008 Stefano Pedretti
6 **********/
7 
8 /*
9  * gnuplot plots.
10  */
11 
12 #include "ngspice/ngspice.h"
13 #include "ngspice/cpdefs.h"
14 #include "ngspice/ftedefs.h"
15 #include "ngspice/dvec.h"
16 #include "ngspice/fteparse.h"
17 #include "gnuplot.h"
18 #if defined(__MINGW32__) || defined(_MSC_VER)
19 #undef BOOLEAN
20 #include <windows.h>
21 #else
22 #include <unistd.h>
23 #endif
24 #include <locale.h>
25 
26 #define GP_MAXVECTORS 64
27 
28 
29 static void
quote_gnuplot_string(FILE * stream,char * s)30 quote_gnuplot_string(FILE *stream, char *s)
31 {
32     fputc('"', stream);
33 
34     for (; *s; s++)
35         /* NOTE: The FALLTHROUGH comment is used to suppress a GCC warning
36          * when flag -Wimplicit-fallthrough is present */
37         switch (*s) {
38         case '\n':
39             fputs("\\n", stream);
40             break;
41         case '"':
42         case '\\':
43             fputc('\\', stream);
44             /* FALLTHROUGH */
45         default:
46             fputc(*s, stream);
47         }
48 
49     fputc('"', stream);
50 }
51 
52 
ft_gnuplot(double * xlims,double * ylims,double xdel,double ydel,const char * filename,const char * title,const char * xlabel,const char * ylabel,GRIDTYPE gridtype,PLOTTYPE plottype,struct dvec * vecs)53 void ft_gnuplot(double *xlims, double *ylims,
54         double xdel, double ydel,
55         const char *filename, const char *title,
56         const char *xlabel, const char *ylabel,
57         GRIDTYPE gridtype, PLOTTYPE plottype,
58         struct dvec *vecs)
59 {
60     FILE *file, *file_data;
61     struct dvec *v, *scale = NULL;
62     double xval, yval, prev_xval, extrange;
63     int i, dir, numVecs, linewidth, gridlinewidth, err, terminal_type;
64     bool xlog, ylog, nogrid, markers, nolegend;
65     char buf[BSIZE_SP], pointstyle[BSIZE_SP], *text, plotstyle[BSIZE_SP], terminal[BSIZE_SP];
66 
67     char filename_data[128];
68     char filename_plt[128];
69 
70 #ifdef SHARED_MODULE
71     char* llocale = setlocale(LC_NUMERIC, NULL);
72     setlocale(LC_NUMERIC, "C");
73 #endif
74 
75     snprintf(filename_data, 128, "%s.data", filename);
76     snprintf(filename_plt, 128, "%s.plt", filename);
77 
78     /* Sanity checking. */
79     for (v = vecs, numVecs = 0; v; v = v->v_link2)
80         numVecs++;
81 
82     if (numVecs == 0) {
83         return;
84     } else if (numVecs > GP_MAXVECTORS) {
85         fprintf(cp_err, "Error: too many vectors for gnuplot.\n");
86         return;
87     }
88 
89     if (ylims && (fabs((ylims[1]-ylims[0])/ylims[0]) < 1.0e-6)) {
90         fprintf(cp_err, "Error: range min ... max too small for using gnuplot.\n");
91         fprintf(cp_err, "  Consider plotting with offset %g.\n", ylims[0]);
92         return;
93     }
94 
95     extrange = 0.05 * (ylims[1] - ylims[0]);
96 
97     if (!cp_getvar("gnuplot_terminal", CP_STRING,
98             terminal, sizeof(terminal))) {
99         terminal_type = 1;
100     }
101     else {
102         terminal_type = 1;
103         if (cieq(terminal,"png")) {
104             terminal_type = 2;
105         }
106         else if (cieq(terminal,"png/quit")) {
107             terminal_type = 3;
108         }
109         else if (cieq(terminal, "eps")) {
110             terminal_type = 4;
111         }
112         else if (cieq(terminal, "eps/quit")) {
113             terminal_type = 5;
114         }
115         else if (cieq(terminal, "xterm")) {
116             terminal_type = 6;
117         }
118     }
119 
120     /* get linewidth for plotting the graph from .spiceinit */
121     if (!cp_getvar("xbrushwidth", CP_NUM, &linewidth, 0))
122         linewidth = 1;
123     if (linewidth < 1)
124         linewidth = 1;
125     /* get linewidth for grid from .spiceinit */
126     if (!cp_getvar("xgridwidth", CP_NUM, &gridlinewidth, 0))
127         gridlinewidth = 1;
128     if (gridlinewidth < 1)
129         gridlinewidth = 1;
130 
131 
132     if (!cp_getvar("pointstyle", CP_STRING, pointstyle, sizeof(pointstyle))) {
133         markers = FALSE;
134     } else {
135         if (cieq(pointstyle,"markers"))
136             markers = TRUE;
137         else
138             markers = FALSE;
139     }
140 
141     if (!cp_getvar("nolegend", CP_BOOL, NULL, 0)) {
142         nolegend = FALSE;
143     }
144     else {
145         nolegend = TRUE;
146     }
147 
148     /* Make sure the gridtype is supported. */
149     switch (gridtype) {
150     case GRID_LIN:
151         nogrid = xlog = ylog = FALSE;
152         break;
153     case GRID_XLOG:
154         xlog = TRUE;
155         nogrid = ylog = FALSE;
156         break;
157     case GRID_YLOG:
158         ylog = TRUE;
159         nogrid = xlog = FALSE;
160         break;
161     case GRID_LOGLOG:
162         xlog = ylog = TRUE;
163         nogrid = FALSE;
164         break;
165     case GRID_NONE:
166         nogrid = TRUE;
167         xlog = ylog = FALSE;
168         break;
169     default:
170         fprintf(cp_err, "Error: grid type unsupported by gnuplot.\n");
171         return;
172     }
173 
174     /* Open the output gnuplot file. */
175     if ((file = fopen(filename_plt, "w")) == NULL) {
176         perror(filename);
177         return;
178     }
179 
180     /* Set up the file header. */
181 #if !defined(__MINGW32__) && !defined(_MSC_VER) && !defined(__CYGWIN__)
182     fprintf(file, "set terminal X11 noenhanced\n");
183 #elif defined(__CYGWIN__)
184 #ifndef EXT_ASC
185         fprintf(file, "set encoding utf8\n");
186 #endif
187 #else
188 #ifndef EXT_ASC
189         fprintf(file, "set encoding utf8\n");
190 #endif
191     fprintf(file, "set termoption noenhanced\n");
192 #endif
193     if (title) {
194         text = cp_unquote(title);
195         fprintf(file, "set title ");
196         quote_gnuplot_string(file, text);
197         fprintf(file, "\n");
198         tfree(text);
199     }
200     if (xlabel) {
201         text = cp_unquote(xlabel);
202         fprintf(file, "set xlabel ");
203         quote_gnuplot_string(file, text);
204         fprintf(file, "\n");
205         tfree(text);
206     }
207     if (ylabel) {
208         text = cp_unquote(ylabel);
209         fprintf(file, "set ylabel ");
210         quote_gnuplot_string(file, text);
211         fprintf(file, "\n");
212         tfree(text);
213     }
214     if (!nogrid) {
215         if (gridlinewidth > 1)
216             fprintf(file, "set grid lw %d \n" , gridlinewidth);
217         else
218             fprintf(file, "set grid\n");
219     }
220     if (xlog) {
221         fprintf(file, "set logscale x\n");
222         if (xlims)
223             fprintf(file, "set xrange [%1.0e:%1.0e]\n",
224                 pow(10, floor(log10(xlims[0]))), pow(10, ceil(log10(xlims[1]))));
225         fprintf(file, "set mxtics 10\n");
226         fprintf(file, "set grid mxtics\n");
227     } else {
228         fprintf(file, "unset logscale x \n");
229         if (xlims)
230             fprintf(file, "set xrange [%e:%e]\n", xlims[0], xlims[1]);
231     }
232     if (ylog) {
233         fprintf(file, "set logscale y \n");
234         if (ylims)
235             fprintf(file, "set yrange [%1.0e:%1.0e]\n",
236                 pow(10, floor(log10(ylims[0]))), pow(10, ceil(log10(ylims[1]))));
237         fprintf(file, "set mytics 10\n");
238         fprintf(file, "set grid mytics\n");
239     } else {
240         fprintf(file, "unset logscale y \n");
241         if (ylims)
242             fprintf(file, "set yrange [%e:%e]\n", ylims[0] - extrange, ylims[1] + extrange);
243     }
244 
245     if (xdel > 0.)
246         fprintf(file, "set xtics %e\n", xdel);
247     else
248         fprintf(file, "#set xtics 1\n");
249     fprintf(file, "#set x2tics 1\n");
250     if (ydel > 0.)
251         fprintf(file, "set ytics %e\n", ydel);
252     else
253         fprintf(file, "#set ytics 1\n");
254     fprintf(file, "#set y2tics 1\n");
255 
256     if (gridlinewidth > 1)
257         fprintf(file, "set border lw %d\n", gridlinewidth);
258 
259     if(nolegend)
260         fprintf(file, "set key off\n");
261 
262     if (plottype == PLOT_COMB) {
263         strcpy(plotstyle, "boxes");
264     } else if (plottype == PLOT_POINT) {
265         if (markers) {
266             // fprintf(file, "Markers: True\n");
267         } else {
268             // fprintf(file, "LargePixels: True\n");
269         }
270         strcpy(plotstyle, "points");
271     } else {
272         strcpy(plotstyle, "lines");
273     }
274 
275     /* Open the output gnuplot data file. */
276     if ((file_data = fopen(filename_data, "w")) == NULL) {
277         perror(filename);
278         return;
279     }
280     fprintf(file, "set format y \"%%g\"\n");
281     fprintf(file, "set format x \"%%g\"\n");
282 
283     if ((terminal_type != 3) && (terminal_type != 5)) {
284         fprintf(file, "plot ");
285         i = 0;
286 
287         /* Write out the gnuplot command */
288         for (v = vecs; v; v = v->v_link2) {
289             scale = v->v_scale;
290             if (v->v_name) {
291                 i = i + 2;
292                 if (i > 2) fprintf(file, ",\\\n");
293                 fprintf(file, "\'%s\' using %d:%d with %s lw %d title ",
294                     filename_data, i - 1, i, plotstyle, linewidth);
295                 quote_gnuplot_string(file, v->v_name);
296             }
297         }
298         fprintf(file, "\n");
299     }
300 
301     /* terminal_type
302     1: do not print an eps or png file
303     2: print png file, keep command window open
304     3: print png file, quit command window
305     4: print eps file, keep command window open
306     5: print eps file, quit command window
307     */
308     if ((terminal_type == 2) || (terminal_type == 4))
309         fprintf(file, "set terminal push\n");
310     if ((terminal_type == 4) || (terminal_type == 5)) {
311         fprintf(file, "set terminal postscript eps color noenhanced\n");
312         fprintf(file, "set out \'%s.eps\'\n", filename);
313     }
314     if ((terminal_type == 2) || (terminal_type == 3)) {
315         fprintf(file, "set terminal png noenhanced\n");
316         fprintf(file, "set out \'%s.png\'\n", filename);
317     }
318     if ((terminal_type == 2) || (terminal_type == 4)) {
319         fprintf(file, "replot\n");
320         fprintf(file, "set term pop\n");
321         fprintf(file, "replot\n");
322     }
323 
324     if ((terminal_type == 3) || (terminal_type == 5)) {
325         fprintf(file, "plot ");
326         i = 0;
327 
328         /* Write out the gnuplot command */
329         for (v = vecs; v; v = v->v_link2) {
330             scale = v->v_scale;
331             if (v->v_name) {
332                 i = i + 2;
333                 if (i > 2) fprintf(file, ",\\\n");
334                 fprintf(file, "\'%s\' using %d:%d with %s lw %d title ",
335                     filename_data, i - 1, i, plotstyle, linewidth);
336                 quote_gnuplot_string(file, v->v_name);
337             }
338         }
339         fprintf(file, "\n");
340         fprintf(file, "exit\n");
341     }
342 
343 
344 
345     (void) fclose(file);
346 
347     /* Write out the data and setup arrays */
348     bool mono = (plottype != PLOT_RETLIN);
349     dir = 0;
350     prev_xval = NAN;
351     for (i = 0; i < scale->v_length; i++) {
352         for (v = vecs; v; v = v->v_link2) {
353             scale = v->v_scale;
354 
355             xval = isreal(scale) ?
356                    scale->v_realdata[i] : realpart(scale->v_compdata[i]);
357 
358             yval = isreal(v) ?
359                    v->v_realdata[i] : realpart(v->v_compdata[i]);
360 
361             if (i > 0 && (mono || (scale->v_plot && scale->v_plot->pl_scale == scale))) {
362                 if (dir * (xval - prev_xval) < 0) {
363                     /* direction reversal, start a new graph */
364                     fprintf(file_data, "\n");
365                     dir = 0;
366                 } else if (!dir && xval > prev_xval) {
367                     dir = 1;
368                 } else if (!dir && xval < prev_xval) {
369                     dir = -1;
370                 }
371             }
372 
373             fprintf(file_data, "%e %e ", xval, yval);
374 
375             prev_xval = xval;
376         }
377         fprintf(file_data, "\n");
378     }
379 
380     (void) fclose(file_data);
381 
382 #if defined(__MINGW32__) || defined(_MSC_VER)
383     /* for external fcn system() */
384     // (void) sprintf(buf, "start /B wgnuplot %s -" ,  filename_plt);
385     (void) sprintf(buf, "start /B wgnuplot -persist %s " ,  filename_plt);
386     _flushall();
387 #else
388     /* for external fcn system() from LINUX environment */
389     if (terminal_type == 3) {
390         fprintf(cp_out, "writing plot to file %s.png\n", filename);
391         (void) sprintf(buf, "gnuplot %s", filename_plt);
392     }
393     else if (terminal_type == 5) {
394         fprintf(cp_out, "writing plot to file %s.eps\n", filename);
395         (void) sprintf(buf, "gnuplot %s", filename_plt);
396     }
397     else if (terminal_type == 6) {
398         (void) sprintf(buf, "xterm -e gnuplot %s - &", filename_plt);
399     }
400     else {
401         (void) sprintf(buf, "gnuplot -persist %s &", filename_plt);
402     }
403 #endif
404     err = system(buf);
405 
406     /* delete the plt and data files */
407     if ((terminal_type == 3) || (terminal_type == 5)) {
408         /* wait for gnuplot generating eps or png file */
409 #if defined(__MINGW32__) || defined(_MSC_VER)
410         Sleep(200);
411 #else
412         usleep(200000);
413 #endif
414         if (remove(filename_data)) {
415             fprintf(stderr, "Could not remove file %s\n", filename_data);
416             perror(NULL);
417         }
418         if (remove(filename_plt)) {
419             fprintf(stderr, "Could not remove file %s\n", filename_plt);
420             perror(NULL);
421         }
422     }
423 #ifdef SHARED_MODULE
424     /* go back to what it was before */
425     setlocale(LC_NUMERIC, llocale);
426 #endif
427 }
428 
429 
430 /* simple printout of data into a file, similar to data table in ft_gnuplot
431    command: wrdata file vecs, vectors of different length (from different plots)
432    may be printed. Data are written in pairs: scale vector, value vector. If
433    data are complex, a triple is printed (scale, real, imag).
434    Setting 'singlescale' as variable, the scale vector will be printed once only,
435    if scale vectors are of same length (there is little risk here!).
436    Width of numbers printed is set by option 'numdgt'.
437  */
ft_writesimple(double * xlims,double * ylims,const char * filename,const char * title,const char * xlabel,const char * ylabel,GRIDTYPE gridtype,PLOTTYPE plottype,struct dvec * vecs)438 void ft_writesimple(double *xlims, double *ylims,
439         const char *filename, const char *title,
440         const char *xlabel, const char *ylabel,
441         GRIDTYPE gridtype, PLOTTYPE plottype,
442         struct dvec *vecs)
443 {
444     FILE *file_data;
445     struct dvec *v;
446     int i, numVecs, maxlen, preci;
447     bool appendwrite, singlescale, vecnames;
448 
449     NG_IGNORE(xlims);
450     NG_IGNORE(ylims);
451     NG_IGNORE(title);
452     NG_IGNORE(xlabel);
453     NG_IGNORE(ylabel);
454     NG_IGNORE(gridtype);
455     NG_IGNORE(plottype);
456 
457     appendwrite = cp_getvar("appendwrite", CP_BOOL, NULL, 0);
458     singlescale = cp_getvar("wr_singlescale", CP_BOOL, NULL, 0);
459     vecnames = cp_getvar("wr_vecnames", CP_BOOL, NULL, 0);
460 
461     /* Sanity checking. */
462     for (v = vecs, numVecs = 0; v; v = v->v_link2)
463         numVecs++;
464 
465     if (numVecs == 0)
466         return;
467 
468     /* print scale vector only once */
469     if (singlescale) {
470         /* check if all vectors have equal scale length */
471         maxlen = vecs->v_length; /* first length of vector read */
472         for (v = vecs; v; v = v->v_link2)
473             if (v->v_scale->v_length != maxlen) {
474                 fprintf(stderr,
475                         "Error: Option 'singlescale' not possible.\n"
476                         "       Vectors %s and %s have different lengths!\n"
477                         "       No data written to %s!\n\n",
478                         vecs->v_name, v->v_name, filename);
479                 return;
480             }
481     }
482     else {
483         /* find maximum scale length from all vectors */
484         maxlen = 0;
485         for (v = vecs; v; v = v->v_link2)
486             maxlen = MAX(v->v_scale->v_length, maxlen);
487     }
488 
489     /* Open the output data file. */
490     if ((file_data = fopen(filename, appendwrite ? "a" : "w")) == NULL) {
491         perror(filename);
492         return;
493     }
494 
495     /* If option numdgt is set, use it for printout precision. */
496     if (cp_numdgt > 0)
497         preci = cp_numdgt;
498     else
499         preci = 8;
500 
501     /* Print names of vectors to first line */
502     if (vecnames) {
503         bool prscale = TRUE;
504         for (v = vecs; v; v = v->v_link2) {
505             struct dvec *scale = v->v_scale;
506             /* If wr_singlescale is set, print scale name only in first column */
507             if (prscale)
508                 fprintf(file_data, " %-*s", preci + 7, scale->v_name);
509 
510             if (isreal(v))
511                 fprintf(file_data, " %-*s", preci + 7, v->v_name);
512             else
513                 fprintf(file_data, " %-*s %-*s", preci + 7, v->v_name, preci + 7, v->v_name);
514             if (singlescale)
515                 /* the following names are printed without scale vector names */
516                 prscale = FALSE;
517         }
518         fprintf(file_data, "\n");
519     }
520 
521     /* Write out the data as simple arrays */
522     for (i = 0; i < maxlen; i++) {
523         bool prscale = TRUE;
524         /* print scale from the first vector, then only if wr_singlescale is not set */
525         for (v = vecs; v; v = v->v_link2) {
526             struct dvec *scale = v->v_scale;
527             /* if no more scale and value data, just print spaces */
528             if (i >= scale->v_length) {
529                 if (prscale)
530                     fprintf(file_data, "%*s", preci + 8, "");
531 
532                 if (isreal(v))
533                     fprintf(file_data, "%*s", preci + 8, "");
534                 else
535                     fprintf(file_data, "%*s", 2 * (preci + 8), "");
536             }
537             else {
538                 if (prscale) {
539                     double xval = isreal(scale)
540                         ? scale->v_realdata[i]
541                         : realpart(scale->v_compdata[i]);
542                     fprintf(file_data, "% .*e ", preci, xval);
543                 }
544 
545                 if (isreal(v))
546                     fprintf(file_data, "% .*e ", preci, v->v_realdata[i]);
547                 else
548                     fprintf(file_data, "% .*e % .*e ", preci, realpart(v->v_compdata[i]), preci, imagpart(v->v_compdata[i]));
549             }
550             if (singlescale)
551                 /* the following vectors are printed without scale vector */
552                 prscale = FALSE;
553         }
554         fprintf(file_data, "\n");
555     }
556 
557     (void) fclose(file_data);
558 }
559