My Project  0.0.16
QUCS Mapping
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
diagram.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  diagram.cpp
3  -------------
4  begin : Thu Oct 2 2003
5  copyright : (C) 2003, 2004, 2005 by Michael Margraf
6  email : michael.margraf@alumni.tu-berlin.de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #if HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 
22 #include <stdlib.h>
23 #include <math.h>
24 #include <float.h>
25 #if HAVE_IEEEFP_H
26 # include <ieeefp.h>
27 #endif
28 #include <locale.h>
29 
30 #include <qtextstream.h>
31 #include <qmessagebox.h>
32 #include <qregexp.h>
33 #include <qdatetime.h>
34 
35 #include "diagram.h"
36 #include "qucs.h"
37 #include "main.h"
38 #include "mnemo.h"
39 
40 #include "rect3ddiagram.h"
41 
42 #ifdef __MINGW32__
43 # define finite(x) _finite(x)
44 #endif
45 
46 #if defined (__MINGW32__)
47 // strtod() appears to be so sloooooooo under Win32...
48 static double strtod_faster (char * pPos, char ** pEnd) {
49  *pEnd = pPos;
50  while ((**pEnd) && (**pEnd > ' ') && (**pEnd != 'j') && (**pEnd != 'i'))
51  (*pEnd)++;
52  if ((**pEnd == 'j') || (**pEnd == 'i'))
53  (*pEnd)--;
54  QCString str = QCString (pPos, *pEnd - pPos + 1);
55  bool ok;
56  double x = str.toDouble (&ok);
57  if (!ok) *pEnd = pPos;
58  return x;
59 }
60 #define strtod(a,b) strtod_faster(a,b)
61 #endif /* __MINGW32__ */
62 
63 using namespace std;
64 
65 Diagram::Diagram(int _cx, int _cy)
66 {
67  cx = _cx; cy = _cy;
68 
69  // x1, x2, y1, y2 are the selectable boundings of the diagram, but these
70  // are the real boundings. They are set in "createAxisLabels()".
71  Bounding_x1 = Bounding_x2 = Bounding_y1 = Bounding_y2 = 0;
72 
73  xAxis.numGraphs = yAxis.numGraphs = zAxis.numGraphs = 0;
74  xAxis.min = xAxis.low =
75  yAxis.min = yAxis.low = zAxis.min = zAxis.low = 0.0;
76  xAxis.max = xAxis.up =
77  yAxis.max = yAxis.up = zAxis.max = zAxis.up = 1.0;
78  xAxis.GridOn = yAxis.GridOn = true;
79  zAxis.GridOn = false;
80  xAxis.log = yAxis.log = zAxis.log = false;
81 
82  xAxis.limit_min = yAxis.limit_min = zAxis.limit_min = 0.0;
83  xAxis.limit_max = yAxis.limit_max = zAxis.limit_max = 1.0;
84  xAxis.step = yAxis.step = zAxis.step = 1.0;
85  xAxis.autoScale = yAxis.autoScale = zAxis.autoScale = true;
86 
87  rotX = 315; // for 3D diagram
88  rotY = 0;
89  rotZ = 225;
90  hideLines = true; // hide invisible lines
91 
92  Type = isDiagram;
93  isSelected = false;
94  GridPen = QPen(QPen::lightGray,0);
95  Graphs.setAutoDelete(true);
96  Arcs.setAutoDelete(true);
97  Lines.setAutoDelete(true);
98  Texts.setAutoDelete(true);
99 }
100 
102 {
103 }
104 
105 // ------------------------------------------------------------
106 // Paint function for most diagrams (cartesian, smith, polar, ...)
108 {
109  // paint all lines
110  for(Line *pl = Lines.first(); pl != 0; pl = Lines.next()) {
111  p->Painter->setPen(pl->style);
112  p->drawLine(cx+pl->x1, cy-pl->y1, cx+pl->x2, cy-pl->y2);
113  }
114 
115  // paint all arcs (1 pixel larger to compensate for strange circle method)
116  for(struct Arc *pa = Arcs.first(); pa != 0; pa = Arcs.next()) {
117  p->Painter->setPen(pa->style);
118  p->drawArc(cx+pa->x, cy-pa->y, pa->w, pa->h, pa->angle, pa->arclen);
119  }
120 
121  Graph *pg;
122  // draw all graphs
123  for(pg = Graphs.first(); pg != 0; pg = Graphs.next())
124  pg->paint(p, cx, cy);
125 
126 
127  // write whole text (axis label inclusively)
128  QWMatrix wm = p->Painter->worldMatrix();
129  for(Text *pt = Texts.first(); pt != 0; pt = Texts.next()) {
130  p->Painter->setWorldMatrix(
131  QWMatrix(pt->mCos, -pt->mSin, pt->mSin, pt->mCos,
132  p->DX + float(cx+pt->x) * p->Scale,
133  p->DY + float(cy-pt->y) * p->Scale));
134 
135  p->Painter->setPen(pt->Color);
136  p->Painter->drawText(0, 0, pt->s);
137  }
138  p->Painter->setWorldMatrix(wm);
139  p->Painter->setWorldXForm(false);
140 
141  // draw markers last, so they are at the top of painting layers
142  for(pg = Graphs.first(); pg != 0; pg = Graphs.next())
143  for(Marker *pm = pg->Markers.first(); pm != 0; pm = pg->Markers.next())
144  pm->paint(p, cx, cy);
145 
146 
147  if(isSelected) {
148  int x_, y_;
149  float fx_, fy_;
150  p->map(cx, cy-y2, x_, y_);
151  fx_ = float(x2)*p->Scale + 10;
152  fy_ = float(y2)*p->Scale + 10;
153 
154  p->Painter->setPen(QPen(QPen::darkGray,3));
155  p->Painter->drawRect(x_-5, y_-5, TO_INT(fx_), TO_INT(fy_));
156  p->Painter->setPen(QPen(QPen::darkRed,2));
157  p->drawResizeRect(cx, cy-y2); // markers for changing the size
158  p->drawResizeRect(cx, cy);
159  p->drawResizeRect(cx+x2, cy-y2);
160  p->drawResizeRect(cx+x2, cy);
161  }
162 }
163 
164 // ------------------------------------------------------------
165 void Diagram::paintScheme(QPainter *p)
166 {
167  p->drawRect(cx, cy-y2, x2, y2);
168 }
169 
170 // ------------------------------------------------------------
171 // Put axis labels into the text list.
173 {
174  Graph *pg;
175  int x, y, w, wmax = 0;
176  QString Str;
177  QFontMetrics metrics(QucsSettings.font);
178  int LineSpacing = metrics.lineSpacing();
179 
180 
181  x = (x2>>1);
182  y = -y1;
183  if(xAxis.Label.isEmpty()) {
184  // write all x labels ----------------------------------------
185  for(pg = Graphs.first(); pg != 0; pg = Graphs.next()) {
186  DataX *pD = pg->cPointsX.getFirst();
187  if(!pD) continue;
188  y -= LineSpacing;
189  if(Name[0] != 'C') { // locus curve ?
190  w = metrics.width(pD->Var) >> 1;
191  if(w > wmax) wmax = w;
192  Texts.append(new Text(x-w, y, pD->Var, pg->Color, 12.0));
193  }
194  else {
195  w = metrics.width("real("+pg->Var+")") >> 1;
196  if(w > wmax) wmax = w;
197  Texts.append(new Text(x-w, y, "real("+pg->Var+")",
198  pg->Color, 12.0));
199  }
200  }
201  }
202  else {
203  y -= LineSpacing;
204  encode_String(xAxis.Label, Str);
205  w = metrics.width(Str) >> 1;
206  if(w > wmax) wmax = w;
207  Texts.append(new Text(x-w, y, Str, Qt::black, 12.0));
208  }
209  Bounding_y2 = 0;
210  Bounding_y1 = y - LineSpacing;
211  Bounding_x2 = wmax - (x2 >> 1);
212  if(Bounding_x2 < 0) Bounding_x2 = 0;
213  Bounding_x1 = Bounding_x2;
214 
215 
216  wmax = 0;
217  x = -x1;
218  y = y2>>1;
219  if(yAxis.Label.isEmpty()) {
220  // draw left y-label for all graphs ------------------------------
221  for(pg = Graphs.first(); pg != 0; pg = Graphs.next()) {
222  if(pg->yAxisNo != 0) continue;
223  if(pg->cPointsY) {
224  if(Name[0] != 'C') { // location curve ?
225  w = metrics.width(pg->Var) >> 1;
226  if(w > wmax) wmax = w;
227  Texts.append(new Text(x, y-w, pg->Var, pg->Color, 12.0, 0.0, 1.0));
228  }
229  else {
230  w = metrics.width("imag("+pg->Var+")") >> 1;
231  if(w > wmax) wmax = w;
232  Texts.append(new Text(x, y-w, "imag("+pg->Var+")",
233  pg->Color, 12.0, 0.0, 1.0));
234  }
235  }
236  else { // if no data => <invalid>
237  w = metrics.width(pg->Var+INVALID_STR) >> 1;
238  if(w > wmax) wmax = w;
239  Texts.append(new Text(x, y-w, pg->Var+INVALID_STR,
240  pg->Color, 12.0, 0.0, 1.0));
241  }
242  x -= LineSpacing;
243  }
244  }
245  else {
246  encode_String(yAxis.Label, Str);
247  w = metrics.width(Str) >> 1;
248  if(w > wmax) wmax = w;
249  Texts.append(new Text(x, y-w, Str, Qt::black, 12.0, 0.0, 1.0));
250  x -= LineSpacing;
251  }
252  if(Bounding_x1 < -x) Bounding_x1 = -x;
253 
254 
255  x = x3;
256  y = y2>>1;
257  if(zAxis.Label.isEmpty()) {
258  // draw right y-label for all graphs ------------------------------
259  for(pg = Graphs.first(); pg != 0; pg = Graphs.next()) {
260  if(pg->yAxisNo != 1) continue;
261  if(pg->cPointsY) {
262  if(Name[0] != 'C') { // location curve ?
263  w = metrics.width(pg->Var) >> 1;
264  if(w > wmax) wmax = w;
265  Texts.append(new Text(x, y+w, pg->Var,
266  pg->Color, 12.0, 0.0, -1.0));
267  }
268  else {
269  w = metrics.width("imag("+pg->Var+")") >> 1;
270  if(w > wmax) wmax = w;
271  Texts.append(new Text(x, y+w, "imag("+pg->Var+")",
272  pg->Color, 12.0, 0.0, -1.0));
273  }
274  }
275  else { // if no data => <invalid>
276  w = metrics.width(pg->Var+INVALID_STR) >> 1;
277  if(w > wmax) wmax = w;
278  Texts.append(new Text(x, y+w, pg->Var+INVALID_STR,
279  pg->Color, 12.0, 0.0, -1.0));
280  }
281  x += LineSpacing;
282  }
283  }
284  else {
285  encode_String(zAxis.Label, Str);
286  w = metrics.width(Str) >> 1;
287  if(w > wmax) wmax = w;
288  Texts.append(new Text(x, y+w, Str, Qt::black, 12.0, 0.0, -1.0));
289  }
290  x -= x2;
291  if(Bounding_x2 < x) Bounding_x2 = x;
292 
293  wmax -= y2 >> 1;
294  if(wmax > 0) {
295  Bounding_y2 = wmax;
296  wmax *= -1;
297  if(wmax < Bounding_y1) Bounding_y1 = wmax;
298  }
299 }
300 
301 // ------------------------------------------------------------
302 int Diagram::regionCode(float x, float y)
303 {
304  int code=0; // code for clipping
305  if(x < 0.0)
306  code |= 1;
307  else if(x > float(x2)) // compare as float to avoid integer overflow
308  code |= 2;
309 
310  if(y < 0.0)
311  code |= 4;
312  else if(y > float(y2)) // compare as float to avoid integer overflow
313  code |= 8;
314 
315  return code;
316 }
317 
318 // ------------------------------------------------------------
319 // Is virtual. This one is for round diagrams only.
320 bool Diagram::insideDiagram(float x, float y)
321 {
322  float R = float(x2)/2.0 + 1.0; // +1 seems better (graph sometimes little outside)
323  x -= R;
324  y -= R;
325  return ((x*x + y*y) <= R*R);
326 }
327 
328 // ------------------------------------------------------------
329 // Cohen-Sutherland clipping algorithm
330 void Diagram::rectClip(float* &p)
331 {
332  int code, z=0;
333  float x=0, y=0, dx, dy;
334  float x_1 = *(p-4), y_1 = *(p-3);
335  float x_2 = *(p-2), y_2 = *(p-1);
336 
337  int code1 = regionCode(x_1, y_1);
338  int code2 = regionCode(x_2, y_2);
339  if((code1 | code2) == 0) return; // line completly inside ?
340 
341  if(code1 != 0) if(*(p-5) >= 0) { // is there already a line end flag ?
342  p++;
343  *(p-5) = STROKEEND;
344  }
345  if(code1 & code2) // line not visible at all ?
346  goto endWithHidden;
347 
348  if(code2 != 0) {
349  *p = STROKEEND;
350  *(p+1) = x_2;
351  *(p+2) = y_2;
352  z += 3;
353  }
354 
355 
356  for(;;) {
357  if((code1 | code2) == 0) break; // line completly inside ?
358 
359  if(code1) code = code1;
360  else code = code2;
361 
362  dx = x_2 - x_1; // dx and dy never equals zero !
363  dy = y_2 - y_1;
364  if(code & 1) {
365  y = y_1 - dy * x_1 / dx;
366  x = 0.0;
367  }
368  else if(code & 2) {
369  y = y_1 + dy * (x2-x_1) / dx;
370  x = float(x2);
371  }
372  else if(code & 4) {
373  x = x_1 - dx * y_1 / dy;
374  y = 0.0;
375  }
376  else if(code & 8) {
377  x = x_1 + dx * (y2-y_1) / dy;
378  y = float(y2);
379  }
380 
381  if(code == code1) {
382  x_1 = x;
383  y_1 = y;
384  code1 = regionCode(x, y);
385  }
386  else {
387  x_2 = x;
388  y_2 = y;
389  code2 = regionCode(x, y);
390  }
391  if(code1 & code2)
392  goto endWithHidden; // line not visible at all ?
393  }
394 
395  *(p-4) = x_1;
396  *(p-3) = y_1;
397  *(p-2) = x_2;
398  *(p-1) = y_2;
399  p += z;
400  return;
401 
402 endWithHidden:
403  *(p-4) = x_2;
404  *(p-3) = y_2;
405  p -= 2;
406 }
407 
408 // ------------------------------------------------------------
409 // Clipping for round diagrams (smith, polar, ...)
410 void Diagram::clip(float* &p)
411 {
412  float R = float(x2) / 2.0;
413  float x_1 = *(p-4) - R, y_1 = *(p-3) - R;
414  float x_2 = *(p-2) - R, y_2 = *(p-1) - R;
415 
416  float dt1 = R*R; // square of radius
417  float dt2 = dt1 - x_2*x_2 - y_2*y_2;
418  dt1 -= x_1*x_1 + y_1*y_1;
419 
420  if(dt1 >= 0.0) if(dt2 >= 0.0) return; // line completly inside ?
421 
422  if(dt1 < 0.0) if(*(p-5) >= 0.0) { // is there already a line end flag ?
423  p++;
424  *(p-5) = STROKEEND;
425  }
426 
427  float x = x_1-x_2;
428  float y = y_1-y_2;
429  float C = x_1*x + y_1*y;
430  float D = x*x + y*y;
431  float F = C*C + dt1*D;
432 
433  x_1 += R;
434  y_1 += R;
435  x_2 += R;
436  y_2 += R;
437  if(F <= 0.0) { // line not visible at all ?
438  *(p-4) = x_2;
439  *(p-3) = y_2;
440  p -= 2;
441  return;
442  }
443 
444  int code = 0;
445  R = sqrt(F);
446  dt1 = C - R;
447  if((dt1 > 0.0) && (dt1 < D)) { // intersection outside start/end point ?
448  *(p-4) = x_1 - x*dt1 / D;
449  *(p-3) = y_1 - y*dt1 / D;
450  code |= 1;
451  }
452  else {
453  *(p-4) = x_1;
454  *(p-3) = y_1;
455  }
456 
457  dt2 = C + R;
458  if((dt2 > 0.0) && (dt2 < D)) { // intersection outside start/end point ?
459  *(p-2) = x_1 - x*dt2 / D;
460  *(p-1) = y_1 - y*dt2 / D;
461  *p = STROKEEND;
462  p += 3;
463  code |= 2;
464  }
465  *(p-2) = x_2;
466  *(p-1) = y_2;
467 
468  if(code == 0) { // intersections both lie outside ?
469  *(p-4) = x_2;
470  *(p-3) = y_2;
471  p -= 2;
472  }
473 
474 }
475 
476 
477 // ------------------------------------------------------------
478 // g->Points must already be empty!!!
480 {
481  double *px;
482  double *pz = g->cPointsY;
483  if(!pz) return;
484  if(g->cPointsX.count() < 1) return;
485 
486  int i, z, tmp, Counter=2;
487  float dx, dy, xtmp, ytmp;
488  int Size = ((2*(g->cPointsX.getFirst()->count) + 1) * g->countY) + 10;
489 
490  if(xAxis.autoScale) if(yAxis.autoScale) if(zAxis.autoScale)
491  Counter = -50000;
492 
493  double Dummy = 0.0; // not used
494  double *py = &Dummy;
495 
496  float *p = (float*)malloc( Size*sizeof(float) ); // create memory for points
497  float *p_end;
498  g->ScrPoints = p_end = p;
499  p_end += Size - 9; // limit of buffer
500  *(p++) = STROKEEND;
501 
502  Axis *pa;
503  if(g->yAxisNo == 0) pa = &yAxis;
504  else pa = &zAxis;
505 
506  double Stroke=10.0, Space=10.0; // length of strokes and spaces in pixel
507  switch(g->Style) {
508  case GRAPHSTYLE_SOLID: // ***** solid line ****************************
509  for(i=g->countY; i>0; i--) { // every branch of curves
510  px = g->cPointsX.getFirst()->Points;
511  calcCoordinate(px, pz, py, p, p+1, pa);
512  p += 2;
513  for(z=g->cPointsX.getFirst()->count-1; z>0; z--) { // every point
514  FIT_MEMORY_SIZE; // need to enlarge memory block ?
515  calcCoordinate(px, pz, py, p, p+1, pa);
516  p += 2;
517  if(Counter >= 2) // clipping only if an axis is manual
518  clip(p);
519  }
520  if(*(p-3) == STROKEEND)
521  p -= 3; // no single point after "no stroke"
522  else if(*(p-3) == BRANCHEND) {
523  if((*(p-2) < 0) || (*(p-1) < 0))
524  p -= 2; // erase last hidden point
525  }
526  *(p++) = BRANCHEND;
527  }
528 
529 
530  *p = GRAPHEND;
531 /*z = p-g->Points+1;
532 p = g->Points;
533 qDebug("\n****** p=%p", p);
534 for(int zz=0; zz<z; zz+=2)
535  qDebug("c: %d/%d", *(p+zz), *(p+zz+1));*/
536  return;
537 
538  case GRAPHSTYLE_DASH:
539  Stroke = 10.0; Space = 6.0;
540  break;
541  case GRAPHSTYLE_DOT:
542  Stroke = 2.0; Space = 4.0;
543  break;
544  case GRAPHSTYLE_LONGDASH:
545  Stroke = 24.0; Space = 8.0;
546  break;
547 
548  default: // symbol (e.g. star) at each point **********************
549  for(i=g->countY; i>0; i--) { // every branch of curves
550  px = g->cPointsX.getFirst()->Points;
551  for(z=g->cPointsX.getFirst()->count; z>0; z--) { // every point
552  calcCoordinate(px, pz, py, p, p+1, pa);
553  if(insideDiagram(*p, *(p+1))) // within diagram ?
554  p += 2;
555  }
556  *(p++) = BRANCHEND;
557  }
558  *p = GRAPHEND;
559 /*qDebug("\n******");
560 for(int zz=0; zz<60; zz+=2)
561  qDebug("c: %d/%d", *(g->Points+zz), *(g->Points+zz+1));*/
562  return;
563  }
564 
565  double alpha, dist;
566  int Flag; // current state: 1=stroke, 0=space
567  for(i=g->countY; i>0; i--) { // every branch of curves
568  Flag = 1;
569  dist = -Stroke;
570  px = g->cPointsX.getFirst()->Points;
571  calcCoordinate(px, pz, py, &xtmp, &ytmp, pa);
572  *(p++) = xtmp;
573  *(p++) = ytmp;
574  Counter = 1;
575  for(z=g->cPointsX.getFirst()->count-1; z>0; z--) {
576  dx = xtmp;
577  dy = ytmp;
578  calcCoordinate(px, pz, py, &xtmp, &ytmp, pa);
579  dx = xtmp - dx;
580  dy = ytmp - dy;
581  dist += sqrt(double(dx*dx + dy*dy)); // distance between points
582  if(Flag == 1) if(dist <= 0.0) {
583  FIT_MEMORY_SIZE; // need to enlarge memory block ?
584 
585  *(p++) = xtmp; // if stroke then save points
586  *(p++) = ytmp;
587  if((++Counter) >= 2) clip(p);
588  continue;
589  }
590  alpha = atan2(double(dy), double(dx)); // slope for interpolation
591  while(dist > 0) { // stroke or space finished ?
592  FIT_MEMORY_SIZE; // need to enlarge memory block ?
593 
594  *(p++) = xtmp - float(dist*cos(alpha)); // linearly interpolate
595  *(p++) = ytmp - float(dist*sin(alpha));
596  if((++Counter) >= 2) clip(p);
597 
598  if(Flag == 0) {
599  dist -= Stroke;
600  if(dist <= 0) {
601  *(p++) = xtmp; // don't forget point after ...
602  *(p++) = ytmp; // ... interpolated point
603  if((++Counter) >= 2) clip(p);
604  }
605  }
606  else {
607  dist -= Space;
608  if(*(p-3) < 0) p -= 2;
609  else *(p++) = STROKEEND;
610  if(Counter < 0) Counter = -50000; // if auto-scale
611  else Counter = 0;
612  }
613  Flag ^= 1; // toggle between stroke and space
614  }
615 
616  } // of x loop
617 
618  if(*(p-3) == STROKEEND)
619  p -= 3; // no single point after "no stroke"
620  else if(*(p-3) == BRANCHEND) {
621  if((*(p-2) < 0) || (*(p-1) < 0))
622  p -= 2; // erase last hidden point
623  }
624  *(p++) = BRANCHEND;
625  } // of y loop
626 
627 
628  *p = GRAPHEND;
629 /*z = p-g->Points+1;
630 p = g->Points;
631 qDebug("\n****** p=%p", p);
632 for(int zz=0; zz<z; zz+=2)
633  qDebug("c: %d/%d", *(p+zz), *(p+zz+1));*/
634 }
635 
636 // -------------------------------------------------------
637 void Diagram::Bounding(int& _x1, int& _y1, int& _x2, int& _y2)
638 {
639  _x1 = cx - Bounding_x1;
640  _y1 = cy - y2 - Bounding_y2;
641  _x2 = cx + x2 + Bounding_x2;
642  _y2 = cy - Bounding_y1;
643 }
644 
645 // -------------------------------------------------------
646 bool Diagram::getSelected(int x_, int y_)
647 {
648  if(x_ >= cx-x1) if(x_ <= cx+x3) if(y_ >= cy-y2) if(y_ <= cy+y1)
649  return true;
650 
651  return false;
652 }
653 
654 // ------------------------------------------------------------
655 // Checks if the resize area was clicked. If so return "true" and sets
656 // x1/y1 and x2/y2 to the border coordinates to draw a rectangle.
657 bool Diagram::resizeTouched(float fX, float fY, float len)
658 {
659  float fCX = float(cx), fCY = float(cy);
660  float fX2 = float(cx+x2), fY2 = float(cy-y2);
661  if(fX < fCX-len) return false;
662  if(fX > fX2+len) return false;
663  if(fY < fY2-len) return false;
664  if(fY > fCY+len) return false;
665 
666  State = 0;
667  if(fX < fCX+len) State = 1;
668  else if(fX <= fX2-len) return false;
669  if(fY > fCY-len) State |= 2;
670  else if(fY >= fY2+len) return false;
671 
672  return true;
673 }
674 
675 // --------------------------------------------------------------------------
677 {
678  int z;
679  double x, y, *p;
680  DataX *pD = pg->cPointsX.first();
681  if(pD == 0) return;
682 
683  if(Name[0] != 'C') { // not for location curves
684  p = pD->Points;
685  for(z=pD->count; z>0; z--) { // check x coordinates (1. dimension)
686  x = *(p++);
687  if(finite(x)) {
688  if(x > xAxis.max) xAxis.max = x;
689  if(x < xAxis.min) xAxis.min = x;
690  }
691  }
692  }
693 
694  if(Name == "Rect3D") {
695  DataX *pDy = pg->cPointsX.next();
696  if(pDy) {
697  p = pDy->Points;
698  for(z=pDy->count; z>0; z--) { // check y coordinates (2. dimension)
699  y = *(p++);
700  if(finite(y)) {
701  if(y > yAxis.max) yAxis.max = y;
702  if(y < yAxis.min) yAxis.min = y;
703  }
704  }
705  }
706  }
707 
708  Axis *pa;
709  if(pg->yAxisNo == 0) pa = &yAxis;
710  else pa = &zAxis;
711  (pa->numGraphs)++; // count graphs
712  p = pg->cPointsY;
713  if(p == 0) return; // if no data => invalid
714  for(z=pg->countY*pD->count; z>0; z--) { // check every y coordinate
715  x = *(p++);
716  y = *(p++);
717 
718  if(Name[0] != 'C') {
719  if(fabs(y) >= 1e-250) x = sqrt(x*x+y*y);
720  if(finite(x)) {
721  if(x > pa->max) pa->max = x;
722  if(x < pa->min) pa->min = x;
723  }
724  }
725  else { // location curve needs different treatment
726  if(finite(x)) {
727  if(x > xAxis.max) xAxis.max = x;
728  if(x < xAxis.min) xAxis.min = x;
729  }
730  if(finite(y)) {
731  if(y > pa->max) pa->max = y;
732  if(y < pa->min) pa->min = y;
733  }
734  }
735  }
736 }
737 
738 // --------------------------------------------------------------------------
739 void Diagram::loadGraphData(const QString& defaultDataSet)
740 {
741  int yNum = yAxis.numGraphs;
742  int zNum = zAxis.numGraphs;
743  yAxis.numGraphs = zAxis.numGraphs = 0;
744 
745  double xmin = xAxis.min, ymin = yAxis.min, zmin = zAxis.min;
746  double xmax = xAxis.max, ymax = yAxis.max, zmax = zAxis.max;
747  yAxis.min = zAxis.min = xAxis.min = DBL_MAX;
748  yAxis.max = zAxis.max = xAxis.max = -DBL_MAX;
749 
750  int No=0;
751  for(Graph *pg = Graphs.first(); pg != 0; pg = Graphs.next()) {
752  if(loadVarData(defaultDataSet, pg) != 1) // load data, determine max/min values
753  No++;
754  else
755  getAxisLimits(pg);
756  pg->lastLoaded = QDateTime::currentDateTime();
757  }
758 
759  if(No <= 0) { // All dataset files unchanged ?
760  yAxis.numGraphs = yNum; // rebuild scrollbar position
761  zAxis.numGraphs = zNum;
762 
763  xAxis.min = xmin; yAxis.min = ymin; zAxis.min = zmin;
764  xAxis.max = xmax; yAxis.max = ymax; zAxis.max = zmax;
765  return; // -> no update neccessary
766  }
767 
768  if(xAxis.min > xAxis.max)
769  xAxis.min = xAxis.max = 0.0;
770  if(yAxis.min > yAxis.max)
771  yAxis.min = yAxis.max = 0.0;
772  if(zAxis.min > zAxis.max)
773  zAxis.min = zAxis.max = 0.0;
774 
775 /* if((Name == "Polar") || (Name == "Smith")) { // one axis only
776  if(yAxis.min > zAxis.min) yAxis.min = zAxis.min;
777  if(yAxis.max < zAxis.max) yAxis.max = zAxis.max;
778  }*/
779  updateGraphData();
780 }
781 
782 // ------------------------------------------------------------------------
783 // Calculate diagram again without reading dataset from file.
785 {
786  yAxis.min = zAxis.min = xAxis.min = DBL_MAX;
787  yAxis.max = zAxis.max = xAxis.max = -DBL_MAX;
788  yAxis.numGraphs = zAxis.numGraphs = 0;
789 
790  // get maximum and minimum values
791  for(Graph *pg = Graphs.first(); pg != 0; pg = Graphs.next())
792  getAxisLimits(pg);
793 
794  if(xAxis.min > xAxis.max) {
795  xAxis.min = 0.0;
796  xAxis.max = 1.0;
797  }
798  if(yAxis.min > yAxis.max) {
799  yAxis.min = 0.0;
800  yAxis.max = 1.0;
801  }
802  if(zAxis.min > zAxis.max) {
803  zAxis.min = 0.0;
804  zAxis.max = 1.0;
805  }
806  if((Name == "Polar") || (Name == "Smith")) { // one axis only
807  if(yAxis.min > zAxis.min) yAxis.min = zAxis.min;
808  if(yAxis.max < zAxis.max) yAxis.max = zAxis.max;
809  }
810 
811  updateGraphData();
812 }
813 
814 // ------------------------------------------------------------------------
816 {
817  int valid = calcDiagram(); // do not calculate graph data if invalid
818 
819  Graph *pg;
820  for(pg = Graphs.first(); pg != 0; pg = Graphs.next()) {
821  if(pg->ScrPoints != 0) {
822  free(pg->ScrPoints);
823  pg->ScrPoints = 0;
824  }
825  if((valid & (pg->yAxisNo+1)) != 0)
826  calcData(pg); // calculate screen coordinates
827  else if(pg->cPointsY) {
828  delete[] pg->cPointsY;
829  pg->cPointsY = 0;
830  }
831  }
832 
833  createAxisLabels(); // virtual function
834 
835  // Setting markers must be done last, because in 3D diagram "Mem"
836  // is released in "createAxisLabels()".
837  for(pg = Graphs.first(); pg != 0; pg = Graphs.next())
838  for(Marker *pm = pg->Markers.first(); pm != 0; pm = pg->Markers.next())
839  pm->createText();
840 }
841 
842 // --------------------------------------------------------------------------
843 int Diagram::loadVarData(const QString& fileName, Graph *g)
844 {
845  QFile file;
846  QString Variable;
847  QFileInfo Info(fileName);
848 
849  int pos = g->Var.find(':');
850 // if(g->Var.right(3) == "].X") // e.g. stdl[8:0].X
851 // if(pos > g->Var.find('['))
852 // pos = -1;
853 
854  /* WORK-AROUND: A bug in SCIM (libscim) which Qt is linked to causes
855  to change the locale to the default. */
856  setlocale (LC_NUMERIC, "C");
857 
858  if(pos <= 0) {
859  file.setName(fileName);
860  Variable = g->Var;
861  }
862  else {
863  file.setName(Info.dirPath()+QDir::separator() + g->Var.left(pos)+".dat");
864  Variable = g->Var.mid(pos+1);
865  }
866 
867  Info.setFile(file);
868  if(g->lastLoaded.isValid())
869  if(g->lastLoaded > Info.lastModified())
870  return 1; // dataset unchanged -> no update neccessary
871 
872  g->countY = 0;
873  g->cPointsX.clear();
874  if(g->cPointsY) { delete[] g->cPointsY; g->cPointsY = 0; }
875  if(Variable.isEmpty()) return 0;
876 
877  if(Variable.right(2) == ".X")
878  if(Name.at(0) != 'T')
879  return 0; // digital variables only for tabulars and ziming diagram
880 
881 
882  if(!file.open(IO_ReadOnly)) return 0;
883 
884  // *****************************************************************
885  // To strongly speed up the file read operation the whole file is
886  // read into the memory in one piece.
887  QByteArray FileContent;
888  FileContent = file.readAll();
889  file.close();
890  char *FileString = FileContent.data();
891  if(!FileString) return 0;
892  char *pPos = FileString+FileContent.size()-1;
893  if(*pPos > ' ') if(*pPos != '>') return 0;
894  *pPos = 0;
895 
896 
897  // *****************************************************************
898  // look for variable name in data file ****************************
899  bool isIndep = false;
900  Variable = "dep "+Variable+" ";
901  // "pFile" is used through-out the whole function and must NOT used
902  // for other purposes!
903  char *pFile = strstr(FileString, Variable.latin1());
904  while(pFile) {
905  if(*(pFile-1) == '<') // is dependent variable ?
906  break;
907  else if(strncmp(pFile-3, "<in", 3) == 0) { // is independent variable ?
908  isIndep = true;
909  break;
910  }
911  pFile = strstr(pFile+4, Variable.latin1());
912  }
913 
914  if(!pFile) return 0; // data not found
915 
916  QString Line, tmp;
917  pFile += Variable.length();
918  pPos = strchr(pFile, '>');
919  if(!pPos) return 0; // file corrupt
920  *pPos = 0;
921  Line = QString(pFile);
922  *pPos = '>';
923  pFile = pPos+1;
924  if(!isIndep) {
925  pos = 0;
926  tmp = Line.section(' ', pos, pos);
927  while(!tmp.isEmpty()) {
928  g->cPointsX.append(new DataX(tmp)); // name of independet variable
929  pos++;
930  tmp = Line.section(' ', pos, pos);
931  }
932  }
933 
934  Axis *pa;
935  // *****************************************************************
936  // get independent variable ****************************************
937  bool ok=true;
938  double *p;
939  int counting = 0;
940  if(isIndep) { // create independent variable by myself ?
941  counting = Line.toInt(&ok); // get number of values
942  g->cPointsX.append(new DataX("number", 0, counting));
943  if(!ok) return 0;
944 
945  p = new double[counting]; // memory of new independent variable
946  g->countY = 1;
947  g->cPointsX.current()->Points = p;
948  for(int z=1; z<=counting; z++) *(p++) = double(z);
949  if(xAxis.min > 1.0) xAxis.min = 1.0;
950  if(xAxis.max < double(counting)) xAxis.max = double(counting);
951  }
952  else { // ...................................
953  // get independent variables from data file
954  g->countY = 1;
955  DataX *bLast = 0;
956  if(Name == "Rect3D") bLast = g->cPointsX.at(1); // y axis for Rect3D
957 
958  double min_tmp = xAxis.min, max_tmp = xAxis.max;
959  for(DataX *pD = g->cPointsX.last(); pD!=0; pD = g->cPointsX.prev()) {
960  pa = &xAxis;
961  if(pD == g->cPointsX.getFirst()) {
962  xAxis.min = min_tmp; // only count first independent variable
963  xAxis.max = max_tmp;
964  }
965  else if(pD == bLast) pa = &yAxis; // y axis for Rect3D
966  counting = loadIndepVarData(pD->Var, FileString, pa, g);
967  if(counting <= 0) return 0;
968 
969  g->countY *= counting;
970  }
971  g->countY /= counting;
972  }
973 
974 
975  // *****************************************************************
976  // get dependent variables *****************************************
977  counting *= g->countY;
978  p = new double[2*counting]; // memory for dependent variables
979  g->cPointsY = p;
980  if(g->yAxisNo == 0) pa = &yAxis; // for which axis
981  else pa = &zAxis;
982  (pa->numGraphs)++; // count graphs
983 
984  char *pEnd;
985  double x, y;
986  pPos = pFile;
987 
988 if(Variable.right(3) != ".X ")
989 
990  for(int z=counting; z>0; z--) {
991  pEnd = 0;
992  while((*pPos) && (*pPos <= ' ')) pPos++; // find start of next number
993  x = strtod(pPos, &pEnd); // real part
994  pPos = pEnd + 1;
995  if(*pEnd < ' ') // is there an imaginary part ?
996  y = 0.0;
997  else {
998  if(((*pEnd != '+') && (*pEnd != '-')) || (*pPos != 'j')) {
999  delete[] g->cPointsY; g->cPointsY = 0;
1000  return 0;
1001  }
1002  *pPos = *pEnd; // overwrite 'j' with sign
1003  pEnd = 0;
1004  y = strtod(pPos, &pEnd); // imaginary part
1005  *pPos = 'j'; // write back old character
1006  pPos = pEnd;
1007  }
1008  *(p++) = x;
1009  *(p++) = y;
1010  if(Name[0] != 'C') {
1011  if(fabs(y) >= 1e-250) x = sqrt(x*x+y*y);
1012  if(finite(x)) {
1013  if(x > pa->max) pa->max = x;
1014  if(x < pa->min) pa->min = x;
1015  }
1016  }
1017  else { // location curve needs different treatment
1018  if(finite(x)) {
1019  if(x > xAxis.max) xAxis.max = x;
1020  if(x < xAxis.min) xAxis.min = x;
1021  }
1022  if(finite(y)) {
1023  if(y > pa->max) pa->max = y;
1024  if(y < pa->min) pa->min = y;
1025  }
1026  }
1027  }
1028 
1029 
1030 else { // of "if not digital"
1031 
1032  char *pc = (char*)p;
1033  pEnd = pc + 2*(counting-1)*sizeof(double);
1034  // for digital variables (e.g. 100ZX0):
1035  for(int z=counting; z>0; z--) {
1036 
1037  while((*pPos) && (*pPos <= ' ')) pPos++; // find start of next bit vector
1038  if(*pPos == 0) {
1039  delete[] g->cPointsY; g->cPointsY = 0;
1040  return 0;
1041  }
1042 
1043  while(*pPos > ' ') { // copy bit vector
1044  *(pc++) = *(pPos++);
1045  if(pEnd <= pc) {
1046  counting = pc - (char*)g->cPointsY;
1047  pc = (char*)realloc(g->cPointsY, counting+1024);
1048  pEnd = pc;
1049  g->cPointsY = (double*)pEnd;
1050  pc += counting;
1051  pEnd += counting+1020;
1052  }
1053  }
1054  *(pc++) = 0; // terminate each vector with NULL
1055  }
1056 
1057 } // of "if not digital"
1058 
1059  return 2;
1060 }
1061 
1062 // --------------------------------------------------------------------------
1063 // Reads the data of an independent variable. Returns the number of points.
1064 int Diagram::loadIndepVarData(const QString& Variable,
1065  char *FileString, Axis *pa, Graph *pg)
1066 {
1067  bool isIndep = false;
1068  QString Line, tmp;
1069 
1070  /* WORK-AROUND: A bug in SCIM (libscim) which Qt is linked to causes
1071  to change the locale to the default. */
1072  setlocale (LC_NUMERIC, "C");
1073 
1074  Line = "dep "+Variable+" ";
1075  // "pFile" is used through-out the whole function and must NOT used
1076  // for other purposes!
1077  char *pFile = strstr(FileString, Line.latin1());
1078  while(pFile) {
1079  if(*(pFile-1) == '<') // is dependent variable ?
1080  break;
1081  else if(strncmp(pFile-3, "<in", 3) == 0) { // is independent variable ?
1082  isIndep = true;
1083  break;
1084  }
1085  pFile = strstr(pFile+4, Line.latin1());
1086  }
1087 
1088  if(!pFile) return -1; // data not found
1089 
1090  pFile += Line.length();
1091  char *pPos = strchr(pFile, '>');
1092  if(!pPos) return -1; // file corrupt
1093  *pPos = 0;
1094  Line = QString(pFile);
1095  *pPos = '>';
1096  pFile = pPos+1;
1097  char *pEnd;
1098  if(!isIndep) { // dependent variable can also be used...
1099  if(Line.find(' ') >= 0) return -1; // ...if only one dependency
1100  Line = "<indep "+Line+" ";
1101  pPos = strstr(FileString, Line.latin1());
1102  if(!pPos) return -1;
1103  pPos += Line.length();
1104  pEnd = strchr(pPos, '>');
1105  if(!pEnd) return -1; // file corrupt
1106  *pEnd = 0;
1107  Line = QString(pPos);
1108  *pEnd = '>';
1109  }
1110 
1111 
1112  bool ok;
1113  int n = Line.toInt(&ok); // number of values
1114  if(!ok) return -1;
1115 
1116  double *p = new double[n]; // memory for new independent variable
1117  DataX *pD = pg->cPointsX.current();
1118  pD->Points = p;
1119  pD->count = n;
1120 
1121 
1122  double x;
1123  pPos = pFile;
1124  // find first position containing no whitespace
1125  while((*pPos) && (*pPos <= ' ')) pPos++;
1126 
1127  for(int z=0; z<n; z++) {
1128  pEnd = 0;
1129  x = strtod(pPos, &pEnd); // real part
1130  if(pPos == pEnd) {
1131  delete[] pD->Points; pD->Points = 0;
1132  return -1;
1133  }
1134 
1135  *(p++) = x;
1136  if(Name[0] != 'C') // not for location curves
1137  if(finite(x)) {
1138  if(x > pa->max) pa->max = x;
1139  if(x < pa->min) pa->min = x;
1140  }
1141 
1142  pPos = pEnd;
1143  while((*pPos) && (*pPos <= ' ')) pPos++; // find start of next number
1144  }
1145 
1146  return n; // return number of independent data
1147 }
1148 
1149 // ------------------------------------------------------------
1150 // Checks if the two graphs have the same independent variables.
1152 {
1153  if(g1 == g2) return true;
1154 
1155  DataX *g1Data = g1->cPointsX.first();
1156  DataX *g2Data = g2->cPointsX.first();
1157  while(g1Data && g2Data) {
1158  if(g1Data->Var != g2Data->Var) return false;
1159  g1Data = g1->cPointsX.next();
1160  g2Data = g2->cPointsX.next();
1161  }
1162 
1163  if(g1Data) return false; // Is there more data ?
1164  if(g2Data) return false; // Is there more data ?
1165  return true;
1166 }
1167 
1168 // ------------------------------------------------------------
1169 int Diagram::checkColumnWidth(const QString& Str,
1170  const QFontMetrics& metrics, int colWidth, int x, int y)
1171 {
1172  int w = metrics.width(Str); // width of text
1173  if(w > colWidth) {
1174  colWidth = w;
1175  if((x+colWidth) >= x2) { // enough space for text ?
1176  // mark lack of space with a small arrow
1177  Lines.append(new Line(x2-6, y-4, x2+7, y-4, QPen(QPen::red,2)));
1178  Lines.append(new Line(x2, y-7, x2+6, y-4, QPen(QPen::red,2)));
1179  Lines.append(new Line(x2, y-1, x2+6, y-4, QPen(QPen::red,2)));
1180  return -1;
1181  }
1182  }
1183  return colWidth;
1184 }
1185 
1186 // ------------------------------------------------------------
1187 void Diagram::setCenter(int x, int y, bool relative)
1188 {
1189  if(relative) {
1190  cx += x; cy += y;
1191  }
1192  else {
1193  cx = x; cy = y;
1194  }
1195 }
1196 
1197 // -------------------------------------------------------
1198 void Diagram::getCenter(int& x, int& y)
1199 {
1200  x = cx + (x2 >> 1);
1201  y = cy - (y2 >> 1);
1202 }
1203 
1204 // ------------------------------------------------------------
1206 {
1207  return new Diagram();
1208 }
1209 
1210 // ------------------------------------------------------------
1211 QString Diagram::save()
1212 {
1213  QString s = "<"+Name+" "+QString::number(cx)+" "+QString::number(cy)+" ";
1214  s += QString::number(x2)+" "+QString::number(y2)+" ";
1215  char c = '0';
1216  if(xAxis.GridOn) c |= 1;
1217  if(hideLines) c |= 2;
1218  s += c;
1219  s += " " + GridPen.color().name() + " " + QString::number(GridPen.style());
1220 
1221  if(xAxis.log) s+= " 1"; else s += " 0";
1222  c = '0';
1223  if(yAxis.log) c |= 1;
1224  if(zAxis.log) c |= 2;
1225  s += c;
1226 
1227  if(xAxis.autoScale) s+= " 1 ";
1228  else s+= " 0 ";
1229  s += QString::number(xAxis.limit_min) + " ";
1230  s += QString::number(xAxis.step) + " ";
1231  s += QString::number(xAxis.limit_max);
1232  if(yAxis.autoScale) s+= " 1 ";
1233  else s+= " 0 ";
1234  s += QString::number(yAxis.limit_min) + " ";
1235  s += QString::number(yAxis.step) + " ";
1236  s += QString::number(yAxis.limit_max);
1237  if(zAxis.autoScale) s+= " 1 ";
1238  else s+= " 0 ";
1239  s += QString::number(zAxis.limit_min) + " ";
1240  s += QString::number(zAxis.step) + " ";
1241  s += QString::number(zAxis.limit_max) + " ";
1242 
1243  s += QString::number(rotX)+" "+QString::number(rotY)+" "+
1244  QString::number(rotZ);
1245 
1246  // labels can contain spaces -> must be last items in the line
1247  s += " \""+xAxis.Label+"\" \""+yAxis.Label+"\" \""+zAxis.Label+"\">\n";
1248 
1249  for(Graph *pg=Graphs.first(); pg != 0; pg=Graphs.next())
1250  s += pg->save()+"\n";
1251 
1252  s += " </"+Name+">";
1253  return s;
1254 }
1255 
1256 // ------------------------------------------------------------
1257 bool Diagram::load(const QString& Line, QTextStream *stream)
1258 {
1259  bool ok;
1260  QString s = Line;
1261 
1262  if(s.at(0) != '<') return false;
1263  if(s.at(s.length()-1) != '>') return false;
1264  s = s.mid(1, s.length()-2); // cut off start and end character
1265 
1266  QString n;
1267  n = s.section(' ',1,1); // cx
1268  cx = n.toInt(&ok);
1269  if(!ok) return false;
1270 
1271  n = s.section(' ',2,2); // cy
1272  cy = n.toInt(&ok);
1273  if(!ok) return false;
1274 
1275  n = s.section(' ',3,3); // x2
1276  x2 = n.toInt(&ok);
1277  if(!ok) return false;
1278 
1279  n = s.section(' ',4,4); // y2
1280  y2 = n.toInt(&ok);
1281  if(!ok) return false;
1282 
1283  char c;
1284  n = s.section(' ',5,5); // GridOn
1285  c = n.at(0).latin1() - '0';
1286  xAxis.GridOn = yAxis.GridOn = (c & 1) != 0;
1287  hideLines = (c & 2) != 0;
1288 
1289  n = s.section(' ',6,6); // color for GridPen
1290  QColor co;
1291  co.setNamedColor(n);
1292  GridPen.setColor(co);
1293  if(!GridPen.color().isValid()) return false;
1294 
1295  n = s.section(' ',7,7); // line style
1296  GridPen.setStyle((Qt::PenStyle)n.toInt(&ok));
1297  if(!ok) return false;
1298 
1299  n = s.section(' ',8,8); // xlog, ylog
1300  xAxis.log = n.at(0) != '0';
1301  c = n.at(1).latin1();
1302  yAxis.log = ((c - '0') & 1) == 1;
1303  zAxis.log = ((c - '0') & 2) == 2;
1304 
1305  n = s.section(' ',9,9); // xAxis.autoScale
1306  if(n.at(0) != '"') { // backward compatible
1307  if(n == "1") xAxis.autoScale = true;
1308  else xAxis.autoScale = false;
1309 
1310  n = s.section(' ',10,10); // xAxis.limit_min
1311  xAxis.limit_min = n.toDouble(&ok);
1312  if(!ok) return false;
1313 
1314  n = s.section(' ',11,11); // xAxis.step
1315  xAxis.step = n.toDouble(&ok);
1316  if(!ok) return false;
1317 
1318  n = s.section(' ',12,12); // xAxis.limit_max
1319  xAxis.limit_max = n.toDouble(&ok);
1320  if(!ok) return false;
1321 
1322  n = s.section(' ',13,13); // yAxis.autoScale
1323  if(n == "1") yAxis.autoScale = true;
1324  else yAxis.autoScale = false;
1325 
1326  n = s.section(' ',14,14); // yAxis.limit_min
1327  yAxis.limit_min = n.toDouble(&ok);
1328  if(!ok) return false;
1329 
1330  n = s.section(' ',15,15); // yAxis.step
1331  yAxis.step = n.toDouble(&ok);
1332  if(!ok) return false;
1333 
1334  n = s.section(' ',16,16); // yAxis.limit_max
1335  yAxis.limit_max = n.toDouble(&ok);
1336  if(!ok) return false;
1337 
1338  n = s.section(' ',17,17); // zAxis.autoScale
1339  if(n == "1") zAxis.autoScale = true;
1340  else zAxis.autoScale = false;
1341 
1342  n = s.section(' ',18,18); // zAxis.limit_min
1343  zAxis.limit_min = n.toDouble(&ok);
1344  if(!ok) return false;
1345 
1346  n = s.section(' ',19,19); // zAxis.step
1347  zAxis.step = n.toDouble(&ok);
1348  if(!ok) return false;
1349 
1350  n = s.section(' ',20,20); // zAxis.limit_max
1351  zAxis.limit_max = n.toDouble(&ok);
1352  if(!ok) return false;
1353 
1354  n = s.section(' ',21,21); // rotX
1355  if(n.at(0) != '"') { // backward compatible
1356  rotX = n.toInt(&ok);
1357  if(!ok) return false;
1358 
1359  n = s.section(' ',22,22); // rotY
1360  rotY = n.toInt(&ok);
1361  if(!ok) return false;
1362 
1363  n = s.section(' ',23,23); // rotZ
1364  rotZ = n.toInt(&ok);
1365  if(!ok) return false;
1366  }
1367  }
1368 
1369  xAxis.Label = s.section('"',1,1); // xLabel
1370  yAxis.Label = s.section('"',3,3); // yLabel left
1371  zAxis.Label = s.section('"',5,5); // yLabel right
1372 
1373  Graph *pg;
1374  // .......................................................
1375  // load graphs of the diagram
1376  while(!stream->atEnd()) {
1377  s = stream->readLine();
1378  s = s.stripWhiteSpace();
1379  if(s.isEmpty()) continue;
1380 
1381  if(s == ("</"+Name+">")) return true; // found end tag ?
1382  if(s.section(' ', 0,0) == "<Mkr") {
1383 
1384  // .......................................................
1385  // load markers of the diagram
1386  pg = Graphs.current();
1387  if(!pg) return false;
1388  Marker *pm = new Marker(this, pg);
1389  if(!pm->load(s)) {
1390  delete pm;
1391  return false;
1392  }
1393  pg->Markers.append(pm);
1394  continue;
1395  }
1396 
1397  pg = new Graph();
1398  if(!pg->load(s)) {
1399  delete pg;
1400  return false;
1401  }
1402  Graphs.append(pg);
1403  }
1404 
1405  return false; // end tag missing
1406 }
1407 
1408 // --------------------------------------------------------------
1409 void Diagram::calcSmithAxisScale(Axis *Axis, int& GridX, int& GridY)
1410 {
1411  xAxis.low = xAxis.min;
1412  xAxis.up = xAxis.max;
1413 
1414  Axis->low = 0.0;
1415  if(fabs(Axis->min) > Axis->max)
1416  Axis->max = fabs(Axis->min); // also fit negative values
1417  if(Axis->autoScale) {
1418  if(Axis->max > 1.01) Axis->up = 1.05*Axis->max;
1419  else Axis->up = 1.0;
1420  GridX = GridY = 4;
1421  }
1422  else {
1423  Axis->up = Axis->limit_max = fabs(Axis->limit_max);
1424  GridX = GridY = int(Axis->step);
1425  }
1426 }
1427 
1428 // ------------------------------------------------------------
1430 {
1431  int GridX; // number of arcs with re(z)=const
1432  int GridY; // number of arcs with im(z)=const
1433  calcSmithAxisScale(Axis, GridX, GridY);
1434 
1435 
1436  if(!xAxis.GridOn) return;
1437 
1438  bool Zplane = ((Mode & 1) == 1); // impedance or admittance chart ?
1439  bool Above = ((Mode & 2) == 2); // paint upper half ?
1440  bool Below = ((Mode & 4) == 4); // paint lower half ?
1441 
1442  int dx2 = x2>>1;
1443 
1444  double im, n_cos, n_sin, real, real1, real2, root;
1445  double rMAXq = Axis->up*Axis->up;
1446  int theta, beta, phi, len, m, x, y;
1447 
1448  int R1 = int(x2/Axis->up + 0.5);
1449  // ....................................................
1450  // draw arcs with im(z)=const
1451  for(m=1; m<GridY; m++) {
1452  n_sin = M_PI*double(m)/double(GridY);
1453  n_cos = cos(n_sin);
1454  n_sin = sin(n_sin);
1455  im = (1.0-n_cos)/n_sin * pow(Axis->up,0.7); // up^0.7 is beauty correction
1456  y = int(im/Axis->up*x2 + 0.5); // diameter
1457 
1458  if(Axis->up <= 1.0) { // Smith chart with |r|=1
1459  beta = int(16.0*180.0*atan2(n_sin-im,n_cos-1.0)/M_PI - 0.5);
1460  if(beta<0) beta += 16*360;
1461  theta = 16*270-beta;
1462  }
1463  else { // Smith chart with |r|>1
1464  im = 1.0/im;
1465  real = (rMAXq+1.0)/(rMAXq-1.0);
1466  root = real*real - im*im - 1.0;
1467  if(root < 0.0) { // circle lies completely within the Smith chart ?
1468  beta = 0; // yes, ...
1469  theta = 16*360; // ... draw whole circle
1470  }
1471  else {
1472  // calculate both intersections with most outer circle
1473  real1 = sqrt(root)-real;
1474  real2 = -sqrt(root)-real;
1475 
1476  root = (real1+1.0)*(real1+1.0) + im*im;
1477  n_cos = (real1*real1 + im*im - 1.0) / root;
1478  n_sin = 2.0*im / root;
1479  beta = int(16.0*180.0*atan2(n_sin-1.0/im,n_cos-1.0)/M_PI);
1480  if(beta<0) beta += 16*360;
1481 
1482  root = (real2+1.0)*(real2+1.0) + im*im;
1483  n_cos = (real2*real2 + im*im - 1.0) / root;
1484  n_sin = 2.0*im / root;
1485  theta = int(16.0*180.0*atan2(n_sin-1/im,n_cos-1)/M_PI);
1486  if(theta<0) theta += 16*360;
1487  theta = theta - beta; // arc length
1488  if(theta < 0) theta = 16*360+theta;
1489  }
1490  }
1491 
1492  if(Zplane)
1493  x = (x2 + R1 - y) >> 1;
1494  else {
1495  x = (x2 - R1 - y) >> 1;
1496  beta = 16*180 - beta - theta; // mirror
1497  if(beta < 0) beta += 16*360; // angle has to be > 0
1498  }
1499 
1500  if(Above)
1501  Arcs.append(new struct Arc(x, dx2+y, y, y, beta, theta, GridPen));
1502  if(Below)
1503  Arcs.append(new struct Arc(x, dx2, y, y, 16*360-beta-theta, theta, GridPen));
1504  }
1505 
1506  // ....................................................
1507  // draw arcs with Re(z)=const
1508  theta = 0; // arc length
1509  beta = 16*180; // start angle
1510  if(Above) { beta = 0; theta = 16*180; }
1511  if(Below) theta += 16*180;
1512 
1513  for(m=1; m<GridX; m++) {
1514  im = m*(Axis->up+1.0)/GridX - Axis->up;
1515  y = int((1.0-im)/Axis->up*double(dx2) + 0.5); // diameter
1516 
1517  if(Zplane)
1518  x = ((x2+R1)>>1) - y;
1519  else
1520  x = (x2-R1)>>1;
1521  if(fabs(fabs(im)-1.0) > 0.2) // if too near to |r|=1, it looks ugly
1522  Arcs.append(new struct Arc(x, (x2+y)>>1, y, y, beta, theta, GridPen));
1523 
1524  if(Axis->up > 1.0) { // draw arcs on the rigth-handed side ?
1525  im = 1.0-im;
1526  im = (rMAXq-1.0)/(im*(im/2.0+1.0)) - 1.0;
1527  if(Zplane) x += y;
1528  else x -= y;
1529  if(im >= 1.0)
1530  Arcs.append(new struct Arc(x, (x2+y)>>1, y, y, beta, theta, GridPen));
1531  else {
1532  phi = int(16.0*180.0/M_PI*acos(im));
1533  len = 16*180-phi;
1534  if(Above && Below) len += len;
1535  else if(Below) phi = 16*180;
1536  if(!Zplane) phi += 16*180;
1537  Arcs.append(new struct Arc(x, (x2+y)>>1, y, y, phi, len, GridPen));
1538  }
1539  }
1540  }
1541 
1542 
1543  // ....................................................
1544  if(Axis->up > 1.0) { // draw circle with |r|=1 ?
1545  x = (x2-R1) >> 1;
1546  y = (x2+R1) >> 1;
1547  Arcs.append(new struct Arc(x, y, R1, R1, beta, theta, QPen(QPen::black,0)));
1548 
1549  // vertical line Re(r)=1 (visible only if |r|>1)
1550  if(Zplane) x = y;
1551  y = int(sqrt(rMAXq-1)/Axis->up*dx2 + 0.5);
1552  if(Above) m = y;
1553  else m = 0;
1554  if(!Below) y = 0;
1555  Lines.append(new Line(x, dx2+m, x, dx2-y, GridPen));
1556 
1557  if(Below) y = 4;
1558  else y = y2-4-QucsSettings.font.pointSize();
1559  Texts.append(new Text(0, y, StringNum(Axis->up)));
1560  }
1561 
1562 }
1563 
1564 
1565 // --------------------------------------------------------------
1566 void Diagram::calcPolarAxisScale(Axis *Axis, double& numGrids,
1567  double& GridStep, double& zD)
1568 {
1569  if(Axis->autoScale) { // auto-scale or user defined limits ?
1570  double Expo, Base;
1571  numGrids = floor(double(x2)/80.0); // minimal grid is 40 pixel
1572  Expo = floor(log10(Axis->max/numGrids));
1573  Base = Axis->max/numGrids/pow(10.0,Expo);// get first significant digit
1574  if(Base < 3.5) { // use only 1, 2 and 5, which ever is best fitted
1575  if(Base < 1.5) Base = 1.0;
1576  else Base = 2.0;
1577  }
1578  else {
1579  if(Base < 7.5) Base = 5.0;
1580  else { Base = 1.0; Expo++; }
1581  }
1582  GridStep = Base * pow(10.0,Expo); // grid distance in real values
1583  numGrids -= floor(numGrids - Axis->max/GridStep); // correct num errors
1584  Axis->up = GridStep*numGrids;
1585 
1586  zD = double(x2) / numGrids; // grid distance in pixel
1587  }
1588  else { // no auto-scale
1589  Axis->up = Axis->limit_max = fabs(Axis->limit_max);
1590  GridStep = Axis->step;
1591  zD = double(x2) / Axis->limit_max * Axis->step; // grid distance in pixel
1592 
1593  if(fabs(zD) < 2.0) { // if grid too small, then no grid
1594  zD = double(x2);
1595  GridStep = Axis->step = Axis->up;
1596  numGrids = 1.0;
1597  }
1598  else numGrids = Axis->limit_max / Axis->step;
1599  }
1600 }
1601 
1602 // ------------------------------------------------------------
1604 {
1605  xAxis.low = xAxis.min;
1606  xAxis.up = xAxis.max;
1607  Axis->low = 0.0;
1608  if(fabs(Axis->min) > Axis->max)
1609  Axis->max = fabs(Axis->min); // also fit negative values
1610 
1611 
1612  bool Above = ((Mode & 1) == 1); // paint upper half ?
1613  bool Below = ((Mode & 2) == 2); // paint lower half ?
1614 
1615  int i, z, tmp;
1616  if(Above) i = y2; else i = y2>>1;
1617  if(Below) z = 0; else z = y2>>1;
1618  // y line
1619  Lines.append(new Line(x2>>1, i, x2>>1, z, GridPen));
1620 
1621  int len = 0; // arc length
1622  int beta = 16*180; // start angle
1623  if(Above) { beta = 0; len = 16*180; }
1624  if(Below) len += 16*180;
1625 
1626  int phi, tPos;
1627  int tHeight = QucsSettings.font.pointSize() + 5;
1628  if(!Below) tPos = (y2>>1) + 3;
1629  else tPos = (y2>>1) - tHeight + 3;
1630 
1631  double Expo, Base, numGrids, GridStep, zD;
1632  if(xAxis.GridOn) {
1633  calcPolarAxisScale(Axis, numGrids, GridStep, zD);
1634 
1635  double zDstep = zD;
1636  double GridNum = 0.0;
1637  for(i=int(numGrids); i>1; i--) { // create all grid circles
1638  z = int(zD);
1639  GridNum += GridStep;
1640  Texts.append(new Text(((x2+z)>>1)-10, tPos, StringNiceNum(GridNum)));
1641 
1642  phi = int(16.0*180.0/M_PI*atan(double(2*tHeight)/zD));
1643  if(!Below) tmp = beta + phi;
1644  else tmp = beta;
1645  Arcs.append(new struct Arc((x2-z)>>1, (y2+z)>>1, z, z, tmp, len-phi,
1646  GridPen));
1647  zD += zDstep;
1648  }
1649  }
1650  else { // of "if(GridOn)"
1651  Expo = floor(log10(Axis->max));
1652  Base = ceil(Axis->max/pow(10.0,Expo) - 0.01);
1653  Axis->up = Base * pow(10.0,Expo); // separate Base * 10^Expo
1654  }
1655 
1656  // create outer circle
1657  Texts.append(new Text(x2-8, tPos, StringNiceNum(Axis->up)));
1658  phi = int(16.0*180.0/M_PI*atan(double(2*tHeight)/double(x2)));
1659  if(!Below) tmp = phi;
1660  else tmp = 0;
1661  Arcs.append(new struct Arc(0, y2, x2, y2, tmp, 16*360-phi, QPen(QPen::black,0)));
1662 
1663  QFontMetrics metrics(QucsSettings.font);
1664  QSize r = metrics.size(0, Texts.current()->s); // width of text
1665  len = x2+r.width()-4; // more space at the right
1666  if(len > x3) x3 = len;
1667 }
1668 
1669 // --------------------------------------------------------------
1670 // Calculations for Cartesian diagrams (RectDiagram and Rect3DDiagram).
1671 // parameters: Axis - pointer to the axis to scale
1672 // Dist - length of axis in pixel on the screen
1673 // return value: "true" if axis runs from largest to smallest value
1674 //
1675 // GridNum - number where the first numbered grid is placed
1676 // GridStep - distance from one grid to the next
1677 // zD - screen coordinate where the first grid is placed
1678 // zDstep - distance on screen from one grid to the next
1679 bool Diagram::calcAxisScale(Axis *Axis, double& GridNum, double& zD,
1680  double& zDstep, double& GridStep, double Dist)
1681 {
1682  bool back=false;
1683  double numGrids, Base, Expo, corr;
1684 if(Axis->autoScale) {
1685 
1686  if(fabs(Axis->max-Axis->min) < 1e-200) {
1687  if((Axis->max == 0.0) && (Axis->min == 0.0)) {
1688  Axis->up = 1.0;
1689  Axis->low = -1.0;
1690  }
1691  else { // if max = min, double difference
1692  Axis->up = Axis->max + fabs(Axis->max);
1693  Axis->low = Axis->min - fabs(Axis->min);
1694  }
1695  }
1696  else if(Axis != &xAxis) {
1697  // keep a small bounding between graph and diagram limit
1698  Axis->up = Axis->max + 0.1*(Axis->max-Axis->min);
1699  Axis->low = Axis->min - 0.1*(Axis->max-Axis->min);
1700  }
1701  else {
1702  Axis->up = Axis->max; // normal case for x axis
1703  Axis->low = Axis->min;
1704  }
1705 
1706 
1707  numGrids = floor(Dist/60.0); // minimal grid is 60 pixel
1708  if(numGrids < 1.0) Base = Axis->up-Axis->low;
1709  else Base = (Axis->up-Axis->low)/numGrids;
1710  Expo = floor(log10(Base));
1711  Base = Base/pow(10.0,Expo); // separate first significant digit
1712  if(Base < 3.5) { // use only 1, 2 and 5, which ever is best fitted
1713  if(Base < 1.5) Base = 1.0;
1714  else Base = 2.0;
1715  }
1716  else {
1717  if(Base < 7.5) Base = 5.0;
1718  else { Base = 1.0; Expo++; }
1719  }
1720  GridStep = Base * pow(10.0,Expo); // grid distance in real coordinates
1721  corr = floor((Axis->up-Axis->low)/GridStep - numGrids);
1722  if(corr < 0.0) corr++;
1723  numGrids += corr; // correct rounding faults
1724 
1725 
1726  // upper y boundery ...........................
1727  zD = fabs(fmod(Axis->up, GridStep));// expand grid to upper diagram edge ?
1728  GridNum = zD/GridStep;
1729  if((1.0-GridNum) < 1e-10) GridNum = 0.0; // fix rounding errors
1730  if(Axis->up <= 0.0) {
1731  if(GridNum < 0.3) { Axis->up += zD; zD = 0.0; }
1732  }
1733  else if(GridNum > 0.7) Axis->up += GridStep-zD;
1734  else if(GridNum < 0.1)
1735  if(GridNum*Dist >= 1.0)// more than 1 pixel above ?
1736  Axis->up += 0.3*GridStep; // beauty correction
1737 
1738 
1739  // lower y boundery ...........................
1740  zD = fabs(fmod(Axis->low, GridStep));// expand grid to lower diagram edge ?
1741  GridNum = zD/GridStep;
1742  if((1.0-GridNum) < 1e-10) zD = GridNum = 0.0; // fix rounding errors
1743  if(Axis->low <= 0.0) {
1744  if(GridNum > 0.7) { Axis->low -= GridStep-zD; zD = 0.0; }
1745  else if(GridNum < 0.1)
1746  if(GridNum*Dist >= 1.0) { // more than 1 pixel above ?
1747  Axis->low -= 0.3*GridStep; // beauty correction
1748  zD += 0.3*GridStep;
1749  }
1750  }
1751  else {
1752  if(GridNum > 0.3) {
1753  zD = GridStep-zD;
1754  if(GridNum > 0.9) {
1755  if((1.0-GridNum)*Dist >= 1.0) { // more than 1 pixel above ?
1756  Axis->low -= 0.3*GridStep; // beauty correction
1757  zD += 0.3*GridStep;
1758  }
1759  }
1760  }
1761  else { Axis->low -= zD; zD = 0.0; }
1762  }
1763 
1764  GridNum = Axis->low + zD;
1765  zD /= (Axis->up-Axis->low)/Dist;
1766 }
1767 else { // user defined limits
1768  zD = 0.0;
1769  Axis->low = GridNum = Axis->limit_min;
1770  Axis->up = Axis->limit_max;
1771  if(Axis->limit_max < Axis->limit_min)
1772  back = true;
1773  GridStep = Axis->step;
1774 }
1775 
1776  zDstep = GridStep/(Axis->up-Axis->low)*Dist; // grid in pixel
1777 
1778  if(fabs(zDstep) < 2.0) { // if grid too small, then no grid
1779  zDstep = Dist;
1780  GridStep = Axis->step = Axis->up-Axis->low;
1781  }
1782 
1783  return back;
1784 }
1785 
1786 // --------------------------------------------------------------
1787 // Calculations for logarithmical Cartesian diagrams (RectDiagram and
1788 // Rect3DDiagram).
1789 // parameters: Axis - pointer to the axis to scale
1790 // Dist - length of axis in pixel on the screen
1791 // return value: "true" if axis runs from largest to smallest value
1792 //
1793 // z - screen coordinate where the first grid is placed
1794 // zD - number where the first grid is placed
1795 // zDstep - number increment from one grid to the next
1796 // coor - scale factor for calculate screen coordinate
1797 bool Diagram::calcAxisLogScale(Axis *Axis, int& z, double& zD,
1798  double& zDstep, double& corr, int len)
1799 {
1800  if(fabs(Axis->max-Axis->min) < 1e-200) { // if max = min, double difference
1801  Axis->max *= 10.0;
1802  Axis->min /= 10.0;
1803  }
1804  Axis->low = Axis->min; Axis->up = Axis->max;
1805 
1806  if(!Axis->autoScale) {
1807  Axis->low = Axis->limit_min;
1808  Axis->up = Axis->limit_max;
1809  }
1810 
1811 
1812  bool mirror=false, mirror2=false;
1813  double tmp;
1814  if(Axis->up < 0.0) { // for negative values
1815  tmp = Axis->low;
1816  Axis->low = -Axis->up;
1817  Axis->up = -tmp;
1818  mirror = true;
1819  }
1820 
1821  double Base, Expo;
1822  if(Axis->autoScale) {
1823  if(mirror) { // set back values ?
1824  tmp = Axis->min;
1825  Axis->min = -Axis->max;
1826  Axis->max = -tmp;
1827  }
1828 
1829  Expo = floor(log10(Axis->max));
1830  Base = Axis->max/pow(10.0,Expo);
1831  if(Base > 3.0001) Axis->up = pow(10.0,Expo+1.0);
1832  else if(Base < 1.0001) Axis->up = pow(10.0,Expo);
1833  else Axis->up = 3.0 * pow(10.0,Expo);
1834 
1835  Expo = floor(log10(Axis->min));
1836  Base = Axis->min/pow(10.0,Expo);
1837  if(Base < 2.999) Axis->low = pow(10.0,Expo);
1838  else if(Base > 9.999) Axis->low = pow(10.0,Expo+1.0);
1839  else Axis->low = 3.0 * pow(10.0,Expo);
1840 
1841  corr = double(len) / log10(Axis->up / Axis->low);
1842 
1843  z = 0;
1844  zD = Axis->low;
1845  zDstep = pow(10.0,Expo);
1846 
1847  if(mirror) { // set back values ?
1848  tmp = Axis->min;
1849  Axis->min = -Axis->max;
1850  Axis->max = -tmp;
1851  }
1852  }
1853  else { // user defined limits
1854  if(Axis->up < Axis->low) {
1855  tmp = Axis->low;
1856  Axis->low = Axis->up;
1857  Axis->up = tmp;
1858  mirror2 = true;
1859  }
1860 
1861  Expo = floor(log10(Axis->low));
1862  Base = ceil(Axis->low/pow(10.0,Expo));
1863  zD = Base * pow(10.0, Expo);
1864  zDstep = pow(10.0,Expo);
1865  if(zD > 9.5*zDstep) zDstep *= 10.0;
1866 
1867  corr = double(len) / log10(Axis->up / Axis->low);
1868  z = int(corr*log10(zD / Axis->low) + 0.5); // int(..) implies floor(..)
1869 
1870  if(mirror2) { // set back values ?
1871  tmp = Axis->low;
1872  Axis->low = Axis->up;
1873  Axis->up = tmp;
1874  }
1875  }
1876 
1877  if(mirror) { // set back values ?
1878  tmp = Axis->low;
1879  Axis->low = -Axis->up;
1880  Axis->up = -tmp;
1881  }
1882 
1883  if(mirror == mirror2) return false;
1884  else return true;
1885 }
1886 
1887 // --------------------------------------------------------------
1889 {
1890  int z, w;
1891  double GridStep, corr, zD, zDstep, GridNum;
1892 
1893  QString tmp;
1894  QFontMetrics metrics(QucsSettings.font);
1895  int maxWidth = 0;
1896 
1897  bool back = false;
1898 if(Axis->log) {
1899  if(Axis->autoScale) {
1900  if(Axis->max*Axis->min <= 0.0) return false; // invalid
1901  }
1902  else if(Axis->limit_min*Axis->limit_max <= 0.0) return false; // invalid
1903 
1904  back = calcAxisLogScale(Axis, z, zD, zDstep, corr, y2);
1905 
1906  if(back) z = y2;
1907  while((z <= y2) && (z >= 0)) { // create all grid lines
1908  if(Axis->GridOn) if(z < y2) if(z > 0)
1909  Lines.prepend(new Line(0, z, x2, z, GridPen)); // y grid
1910 
1911  if((zD < 1.5*zDstep) || (z == 0)) {
1912  tmp = StringNiceNum(zD);
1913  if(Axis->up < 0.0) tmp = '-'+tmp;
1914 
1915  w = metrics.width(tmp); // width of text
1916  if(maxWidth < w) maxWidth = w;
1917  if(x0 > 0)
1918  Texts.append(new Text(x0+7, z-6, tmp)); // text aligned left
1919  else
1920  Texts.append(new Text(-w-7, z-6, tmp)); // text aligned right
1921 
1922  // y marks
1923  Lines.append(new Line(x0-5, z, x0+5, z, QPen(QPen::black,0)));
1924  }
1925 
1926  zD += zDstep;
1927  if(zD > 9.5*zDstep) zDstep *= 10.0;
1928  if(back) {
1929  z = int(corr*log10(zD / fabs(Axis->up)) + 0.5); // int() implies floor()
1930  z = y2 - z;
1931  }
1932  else
1933  z = int(corr*log10(zD / fabs(Axis->low)) + 0.5);// int() implies floor()
1934  }
1935 }
1936 else { // not logarithmical
1937  back = calcAxisScale(Axis, GridNum, zD, zDstep, GridStep, double(y2));
1938 
1939  double Expo;
1940  if(Axis->up == 0.0) Expo = log10(fabs(Axis->up-Axis->low));
1941  else Expo = log10(fabs(Axis->up));
1942 
1943  zD += 0.5; // perform rounding
1944  z = int(zD); // "int(...)" implies "floor(...)"
1945  while((z <= y2) && (z >= 0)) { // create all grid lines
1946  if(fabs(GridNum) < 0.01*pow(10.0, Expo)) GridNum = 0.0;// make 0 really 0
1947  tmp = StringNiceNum(GridNum);
1948 
1949  w = metrics.width(tmp); // width of text
1950  if(maxWidth < w) maxWidth = w;
1951  if(x0 > 0)
1952  Texts.append(new Text(x0+8, z-6, tmp)); // text aligned left
1953  else
1954  Texts.append(new Text(-w-7, z-6, tmp)); // text aligned right
1955  GridNum += GridStep;
1956 
1957  if(Axis->GridOn) if(z < y2) if(z > 0)
1958  Lines.prepend(new Line(0, z, x2, z, GridPen)); // y grid
1959  Lines.append(new Line(x0-5, z, x0+5, z, QPen(QPen::black,0))); // y marks
1960  zD += zDstep;
1961  z = int(zD);
1962  }
1963 } // of "if(ylog) ... else ..."
1964  if(x0 == 0) x1 = maxWidth+14;
1965  else x3 = x2+maxWidth+14;
1966  return true;
1967 }