WINDEX
plot2d.h
1 #pragma once
2 
3 #include <iostream>
4 #include <sstream>
5 #include <iomanip>
6 #include <vector>
7 #include <algorithm>
8 #include <limits>
9 #include <cfloat>
10 
11 #include <wex.h>
12 
13 // minimum data range that will produce sensible plots
14 #define minDataRange 0.000001
15 
16 namespace wex
17 {
18  namespace plot
19  {
20  class plot;
21 
23 
30  class cCircularBuffer
31  {
32  int myCurrentID; // buffer index for current iteration
33  int myLastValid; // index of most recent data point
34  int mySize; // largest buffer index
35  bool myfWrapped; // true if buffer is full aand wrap around has occurred
36  bool myfCurrentLast; // true if current iteration is at most recent data point
37  bool myfIterating; // an iteration is in progress
38 
39  public:
40  cCircularBuffer()
41  : myCurrentID(-1), myLastValid(-1), mySize(-1),
42  myfWrapped(false), myfCurrentLast(false), myfIterating(false)
43  {
44  }
45 
49  void set(int s)
50  {
51  mySize = s;
52  }
53 
55  bool isValidData() const
56  {
57  return myLastValid >= 0;
58  }
59 
61  bool isFull()
62  {
63  return myfWrapped;
64  }
65 
69  int add()
70  {
71  if (mySize < 0)
72  throw std::logic_error(
73  "cCircularBuffer::add() called without size buffer");
74 
75  if (myfIterating)
76  throw std::logic_error(
77  "cCircularBuffer::add() called during iteration");
78 
79  myLastValid++;
80  if (myLastValid > mySize)
81  {
82  myLastValid = 0;
83  myfWrapped = true;
84  }
85  return myLastValid;
86  }
87 
91  int first()
92  {
93  if (myLastValid < 0)
94  return -1;
95  myfIterating = true;
96  if (!myfWrapped)
97  {
98  myCurrentID = 0;
99  myfCurrentLast = false;
100  if (myLastValid == 0)
101  myfCurrentLast = true;
102  }
103  else
104  {
105  myCurrentID = myLastValid + 1;
106  if (myCurrentID > mySize)
107  myCurrentID = 0;
108  myfCurrentLast = false;
109  }
110  return myCurrentID;
111  }
112 
115  int next()
116  {
117  // check for no data in buffer
118  if (myLastValid < 0)
119  return -1;
120 
121  // check for end of data
122  if (myfCurrentLast)
123  {
124  myfIterating = false;
125  return -1;
126  }
127 
128  myCurrentID++;
129 
130  if (myCurrentID > mySize)
131  myCurrentID = 0;
132 
133  if (myCurrentID == myLastValid)
134  myfCurrentLast = true;
135 
136  return myCurrentID;
137  }
138  };
139 
145  class scaleStateMachine
146  {
147  public:
148  enum class eState
149  {
150  none,
151  fit,
152  fix,
153  fitzoom,
154  fixzoom,
155  };
156  enum class eEvent
157  {
158  start,
159  zoom,
160  unzoom,
161  fix,
162  fit,
163  };
164 
165  eState myState;
166 
167  scaleStateMachine()
168  : myState(eState::fit)
169  {
170  }
171 
175  eState event(eEvent event)
176  {
177  switch (event)
178  {
179 
180  case eEvent::start:
181  // handled by constructor
182  return eState::fit;
183 
184  case eEvent::zoom:
185  switch (myState)
186  {
187  case eState::fit:
188  myState = eState::fitzoom;
189  break;
190  case eState::fix:
191  myState = eState::fixzoom;
192  break;
193  default:
194  return eState::none;
195  }
196  break;
197 
198  case eEvent::unzoom:
199  switch (myState)
200  {
201  case eState::fitzoom:
202  myState = eState::fit;
203  break;
204  case eState::fixzoom:
205  myState = eState::fix;
206  break;
207  default:
208  return eState::none;
209  }
210  break;
211 
212  case eEvent::fix:
213  switch (myState)
214  {
215  case eState::fit:
216  myState = eState::fix;
217  break;
218  default:
219  return eState::none;
220  }
221  break;
222 
223  case eEvent::fit:
224  switch (myState)
225  {
226  case eState::fit:
227  break;
228  case eState::fix:
229  myState = eState::fit;
230  break;
231  default:
232  return eState::none;
233  }
234  break;
235 
236  default:
237  throw std::runtime_error(
238  "plot scaleStateMachine unrecognized event");
239  }
240 
241  // std::cout << "scaleStateMachine state change " << (int)myState << "\n";
242 
243  return myState;
244  }
245  };
246 
257  class XScale
258  {
259  scaleStateMachine::eState &theState;
260 
261  int xpmin; // min pixel
262  int xpmax; // max pixel
263 
264  int ximin; // min data index
265  int ximax; // max data index
266 
267  double xumin; // min displayed x user value
268  double xuximin; // user x value for ximin
269  double xumax; // max user x value displayed
270  double xixumin; // data index for user x min
271 
272  double xuminfix;
273  double xumaxfix;
274  double xuminZoom;
275  double xumaxZoom;
276 
277  double sxi2xu; // scale from data index to x user
278  double sxi2xp; // scale from data index to pixel
279  double sxu2xp; // scale from x user to pixel
280 
281  public:
282  XScale(scaleStateMachine &machine)
283  : theState(machine.myState)
284  {
285  }
286  void xiSet(int min, int max)
287  {
288  ximin = min;
289  ximax = max;
290  }
291  void xpSet(int min, int max)
292  {
293  xpmin = min;
294  xpmax = max;
295  }
296 
300 
301  void xi2xuSet(double u0, double sc)
302  {
303  xuximin = u0;
304  sxi2xu = sc;
305  }
306  void fixSet(double min, double max)
307  {
308  xuminfix = min;
309  xumaxfix = max;
310  }
311 
315 
316  void zoom(double umin, double umax)
317  {
318  xuminZoom = umin;
319  xumaxZoom = umax;
320  }
321  void zoomExit()
322  {
323  }
324 
325  void calculate()
326  {
327  switch (theState)
328  {
329  case scaleStateMachine::eState::fit:
330  xumin = xuximin;
331  xumax = xumin + sxi2xu * ximax;
332  xixumin = 0;
333  sxi2xp = (double)(xpmax - xpmin) / (ximax - ximin);
334  sxu2xp = (xpmax - xpmin) / (xumax - xumin);
335  break;
336 
337  case scaleStateMachine::eState::fix:
338  {
339  xumin = xuminfix;
340  xumax = xumaxfix;
341  xixumin = (xumin - xuximin) / sxi2xu;
342  double xixumax = (xumax - xuximin) / sxi2xu;
343  sxi2xp = (xpmax - xpmin) / (xixumax - xixumin);
344  sxu2xp = (xpmax - xpmin) / (xumax - xumin);
345  }
346  break;
347 
348  case scaleStateMachine::eState::fitzoom:
349  case scaleStateMachine::eState::fixzoom:
350  {
351  xumin = xuminZoom;
352  xumax = xumaxZoom;
353  xixumin = (xumin - xuximin) / sxi2xu;
354  double xixumax = (xumax - xuximin) / sxi2xu;
355  sxi2xp = (xpmax - xpmin) / (xixumax - xixumin);
356  sxu2xp = (xpmax - xpmin) / (xumax - xumin);
357  }
358  break;
359  }
360  }
361 
362  int XI2XP(double xi) const
363  {
364  // std::cout << "XI2XP " << xi
365  // << " xpmin " << xpmin
366  // << " sxi2xp " << sxi2xp
367  // << " xixumin " << xixumin
368  // << "\n";
369 
370  return round(xpmin + sxi2xp * (xi - xixumin));
371  }
372  double XP2XU(int pixel) const
373  {
374  return xumin + (pixel - xpmin) / sxu2xp;
375  }
376  int XU2XP(double xu) const
377  {
378  return round(xpmin + sxu2xp * (xu - xumin));
379  }
380 
381  int XUmin() const
382  {
383  return xumin;
384  }
385  int XUmax() const
386  {
387  return xumax;
388  }
389  int XPmin() const
390  {
391  return xpmin;
392  }
393  int XPmax() const
394  {
395  return xpmax;
396  }
397 
398  void text() const
399  {
400  std::cout
401  << "state " << (int)theState
402  << " xpstart " << xpmin << " xpmax " << xpmax
403  << " xistart " << ximin << " ximax " << ximax
404  << " xustart " << xumin << " xumax " << xumax
405  << " sxi2xp " << sxi2xp
406  << " sxi2xu " << sxi2xu
407  << "\n";
408  }
409  };
410 
414  class YScale
415  {
416  scaleStateMachine::eState &theState;
417  double yvmin; // smallest value in data currently displayed
418  double yvmax; // largest value in data currently displayed
419  int ypmin; // y pixel showing smallest data value
420  int ypmax; // y pixel showing largest data value
421  double syv2yp; // scale from data value to y pixel
422  double yvminZoom; // smallest value in data when zoomed
423  double yvmaxZoom; // largest value in data when zoomed
424  double yvminFit; // smallest value in data when fitted
425  double yvmaxFit; // largest value in data when fitted
426  double yvminFix; // smallest value in data when fixed
427  double yvmaxFix; // largest value in data when fixed
428 
429  public:
430  YScale(scaleStateMachine &scaleMachine)
431  : theState(scaleMachine.myState)
432  {
433  }
434 
435  void YVrange(double min, double max)
436  {
437  yvminFit = min;
438  yvmaxFit = max;
439  }
440 
441  double YVrange() const
442  {
443  return yvmax - yvmin;
444  }
445 
450 
451  void YPrange(int min, int max)
452  {
453  ypmin = min;
454  ypmax = max;
455  calculate();
456  }
457 
458  void zoom(double min, double max)
459  {
460  yvminZoom = min;
461  yvmaxZoom = max;
462  }
463 
464  void fixSet(double min, double max)
465  {
466  yvminFix = min;
467  yvmaxFix = max;
468  }
469 
470  double YP2YV(int pixel) const
471  {
472  return yvmin - (ypmin - pixel) / syv2yp;
473  }
474  int YV2YP(double v) const
475  {
476  return ypmin + syv2yp * (v - yvmin);
477  }
478  int YPmin() const
479  {
480  return ypmin;
481  }
482  int YPmax() const
483  {
484  return ypmax;
485  }
486  void text() const
487  {
488  std::cout << "yv " << yvmin << " " << yvmax
489  << " xp " << ypmin << " " << ypmax
490  << " " << syv2yp
491  << "\n";
492  }
493 
494  void calculate()
495  {
496  switch (theState)
497  {
498  case scaleStateMachine::eState::fit:
499  yvmin = yvminFit;
500  yvmax = yvmaxFit;
501  break;
502 
503  case scaleStateMachine::eState::fix:
504  yvmin = yvminFix;
505  yvmax = yvmaxFix;
506  break;
507 
508  case scaleStateMachine::eState::fitzoom:
509  case scaleStateMachine::eState::fixzoom:
510  yvmin = yvminZoom;
511  yvmax = yvmaxZoom;
512  break;
513  }
514  double yvrange = yvmax - yvmin;
515  if (fabs(yvrange) < 0.00001)
516  {
517  // seems like there are no meaningful data
518  syv2yp = 1;
519  return;
520  }
521 
522  syv2yp = -(ypmin - ypmax) / yvrange;
523  }
524  };
525 
526  // @endcond
527 
561  class trace
562  {
563  public:
564  enum class eType
565  {
566  plot,
567  realtime,
568  scatter
569  } myType; // trace type
570 
578  void set(const std::vector<double> &y)
579  {
580  if ((myType != eType::plot) && (myType != eType::scatter))
581  throw std::runtime_error("plot2d error: plot data added to non plot/scatter trace");
582 
583  myY = y;
584  }
585  void setScatterX(const std::vector<double> &x)
586  {
587  if (myType != eType::scatter)
588  throw std::runtime_error("plot2d error: plot X added to non scatter trace");
589 
590  myX = x;
591  }
592  std::vector<double> get() const
593  {
594  return myY;
595  }
596 
603  void add(double y)
604  {
605  if (myType != eType::realtime)
606  throw std::runtime_error("plot2d error: realtime data added to non realtime trace");
607  myY[myCircular.add()] = y;
608  }
609 
618  void add(double x, double y)
619  {
620  if (myType != eType::scatter)
621  throw std::runtime_error("plot2d error: point data added to non scatter type trace");
622  myX.push_back(x);
623  myY.push_back(y);
624  }
625 
627  void clear()
628  {
629  myX.clear();
630  myY.clear();
631  }
632 
634  void color(int clr)
635  {
636  myColor = clr;
637  }
638  int color() const
639  {
640  return myColor;
641  }
642 
644  void thick(int t)
645  {
646  myThick = t;
647  }
648  int thick() const
649  {
650  return myThick;
651  }
652 
654  int size() const
655  {
656  return (int)myY.size();
657  }
658 
663  double value(double xfraction)
664  {
665  if (0 > xfraction || xfraction > 1)
666  return 0;
667  return myY[(int)(xfraction * myY.size())];
668  }
669 
670  const std::vector<double> &getY()
671  {
672  if (myType != eType::realtime)
673  return myY;
674 
675  static std::vector<double> ret;
676  ret.clear();
677  for (int yidx = myCircular.first();
678  yidx >= 0;
679  yidx = myCircular.next())
680  ret.push_back(myY[yidx]);
681  return ret;
682  }
683 
684  private:
685  friend plot;
686 
687  plot *myPlot; // plot where this trace is displayed
688  std::vector<double> myX; // X value of each data point
689  std::vector<double> myY; // Y value of each data point
690  cCircularBuffer myCircular; // maintain indices of circular buffer used by real time trace
691  int myColor; // trace color
692  int myThick; // trace thickness
693 
699  trace()
700  : myThick(1), myType(eType::plot)
701  {
702  }
703 
705  void Plot(plot *p)
706  {
707  myPlot = p;
708  }
709 
718  void realTime(int w)
719  {
720  myType = eType::realtime;
721  myY.clear();
722  myY.resize(w);
723  myCircular.set(w);
724  }
725 
727  void scatter()
728  {
729  myType = eType::scatter;
730  myY.clear();
731  myX.clear();
732  }
733 
735  void bounds(
736  int &txmin, int &txmax,
737  double &tymin, double &tymax)
738  {
739  if (myY.size())
740  {
741 
742  // find the x limits
743 
744  txmin = 0;
745  txmax = myY.size() - 1;
746 
747  // if (myType == eType::realtime || myX.size() == 0)
748  // {
749  // txmin = 0;
750  // txmax = myY.size();
751  // }
752  // else
753  // {
754  // // auto result = std::minmax_element(
755  // // myX.begin(),
756  // // myX.end());
757  // // txmin = *result.first;
758  // // txmax = *result.second;
759  // txmin =
760  // }
761 
762  // find the y limits
763 
764  if (myType == eType::realtime)
765  {
766  if (!myCircular.isValidData())
767  {
768  // no data is buffer
769  tymin = -5;
770  tymax = 5;
771  return;
772  }
773 
774  if (myCircular.isFull())
775  {
776  // buffer is full
777  auto result = std::minmax_element(
778  myY.begin(),
779  myY.end());
780  tymin = *result.first;
781  tymax = *result.second;
782  }
783 
784  // buffer is partially full
785  // set bounds using the data that has been received so far
786  tymin = myY[0];
787  tymax = myY[0];
788  for (int idx = myCircular.first();
789  idx >= 0;
790  idx = myCircular.next())
791  {
792  double y = myY[idx];
793  if (y < tymin)
794  tymin = y;
795  if (y > tymax)
796  tymax = y;
797  }
798  }
799  else
800  {
801  // scatter or static trace
802  auto result = std::minmax_element(
803  myY.begin(),
804  myY.end());
805  tymin = *result.first;
806  tymax = *result.second;
807  }
808  }
809  }
810  };
811 
892  class plot : public gui
893  {
894  public:
898  plot(gui *parent)
899  : gui(parent), myfDrag(false),
900  myXScale(myScaleStateMachine),
901  myYScale(myScaleStateMachine)
902  {
903  text("Plot");
904 
905  events().draw(
906  [this](PAINTSTRUCT &ps)
907  {
908  // calculate scaling factors
909  // so plot will fit
910  if (!CalcScale(ps.rcPaint.right,
911  ps.rcPaint.bottom))
912  {
913  wex::msgbox("Plot has no data");
914  return;
915  }
916 
917  wex::shapes S(ps);
918 
919  // draw axis
920  drawYAxis(S);
921  drawXAxis(
922  S,
923  ps.rcPaint.bottom - 20);
924 
925  // loop over traces
926  for (auto t : myTrace)
927  drawTrace(t, S);
928 
929  drawSelectedArea(ps);
930  });
931 
932  events().click(
933  [&]
934  {
935  // start dragging for selected area
936  auto m = getMouseStatus();
937  myStartDragX = m.x;
938  myStartDragY = m.y;
939  myStopDragX = -1;
940  myfDrag = true;
941  });
942  events().mouseMove(
943  [&](wex::sMouse &m)
944  {
945  // extend selected area as mouse is dragged
946  dragExtend(m);
947  });
948  events().mouseUp(
949  [&]
950  {
951  // check if user has completed a good drag operation
952  if (isGoodDrag())
953  {
954  // check for valid event
955  if (myScaleStateMachine.event(scaleStateMachine::eEvent::zoom) != scaleStateMachine::eState::none)
956  {
957  double myZoomXMin = myXScale.XP2XU(myStartDragX);
958  double myZoomXMax = myXScale.XP2XU(myStopDragX);
959  double myZoomYMax = myYScale.YP2YV(myStartDragY);
960  double myZoomYMin = myYScale.YP2YV(myStopDragY);
961 
962  myXScale.zoom(myZoomXMin, myZoomXMax);
963  myYScale.zoom(myZoomYMin, myZoomYMax);
964 
965  // myfZoom = true;
966 
967  // std::cout << myStartDragX <<" "<< myStopDragX <<" "<< myStartDragY <<" "<< myStopDragY << "\n";
968  // std::cout << myZoomXMin <<" "<< myZoomXMax <<" "<< myZoomYMin <<" "<< myZoomYMax << "\n";
969  }
970  }
971  myfDrag = false;
972  update();
973  });
974 
975  events().clickRight(
976  [&]
977  {
978  myScaleStateMachine.event(scaleStateMachine::eEvent::unzoom);
979  update();
980  });
981  }
982 
983  ~plot()
984  {
985  }
986 
996  {
997  trace *t = new trace();
998  t->Plot(this);
999  myTrace.push_back(t);
1000  return *t;
1001  }
1002 
1012  {
1013  trace *t = new trace();
1014  t->Plot(this);
1015  t->realTime(w);
1016  myTrace.push_back(t);
1017  return *t;
1018  }
1019 
1029  {
1030  trace *t = new trace();
1031  t->Plot(this);
1032  t->scatter();
1033  myTrace.push_back(t);
1034  return *t;
1035  }
1036 
1038  void grid(bool enable)
1039  {
1040  myfGrid = enable;
1041  }
1042 
1048 
1050  double minX, double maxX, double minY, double maxY)
1051  {
1052  if (maxX <= minX || maxY <= minY)
1053  throw std::runtime_error(
1054  "plot::setFixedScale bad params");
1055 
1056  // change scale state
1057  if (
1058  myScaleStateMachine.event(
1059  scaleStateMachine::eEvent::fix) == scaleStateMachine::eState::none)
1060  return;
1061 
1062  if (!myfXset)
1063  XUValues(0, 1);
1064 
1065  myXScale.fixSet(minX, maxX);
1066  myYScale.fixSet(minY, maxY);
1067 
1068  // myXScale.text();
1069  }
1070 
1071  void setFitScale()
1072  {
1073  if (
1074  myScaleStateMachine.event(
1075  scaleStateMachine::eEvent::fit) == scaleStateMachine::eState::none)
1076  throw std::runtime_error(
1077  "wex plot cannot return to fit scale");
1078  }
1079 
1080  int traceCount() const
1081  {
1082  return (int)myTrace.size();
1083  }
1084 
1086  void clear()
1087  {
1088  myTrace.clear();
1089  }
1090 
1095  // void fixYVminmax(double min, double max)
1096  // {
1097  // myfFit = false;
1098  // myfZoom = false;
1099  // myYScale.YVrange(min, max);
1100  // }
1101 
1103  // void autoFit()
1104  // {
1105  // myfFit = true;
1106  // myfDrag = false;
1107  // myfZoom = false;
1108  // myXScale.zoomExit();
1109  // update();
1110  // }
1111 
1113  {
1114  if (!myfDrag)
1115  return;
1116  myStopDragX = m.x;
1117  myStopDragY = m.y;
1118  update();
1119  }
1126  void XUValues(
1127  float start_xu,
1128  float scale_xi2xu)
1129  {
1130  myXScale.xi2xuSet(start_xu, scale_xi2xu);
1131  myfXset = true;
1132  }
1133 
1137  void XValues(
1138  float start_xu,
1139  float scale_xi2xu)
1140  {
1141  XUValues(start_xu, scale_xi2xu);
1142  }
1143 
1146  bool CalcScale(int w, int h)
1147  {
1148  // std::cout << "Plot::CalcScale " << w << " " << h << "\n";
1149 
1150  // If user has not called XValues(), set X-axis scale to 1
1151  if (!myfXset)
1152  XUValues(0, 1);
1153 
1154  // check there are traces that need to be drawn
1155  if (!myTrace.size())
1156  return false;
1157 
1158  // set pixel ranges for the axis
1159  myYScale.YPrange(h - 40, 10);
1160  myXScale.xpSet(50, w - 70);
1161 
1162  int ximin, ximax;
1163  double ymin, ymax;
1164  switch (myScaleStateMachine.myState)
1165  {
1166  case scaleStateMachine::eState::fit:
1167 
1168  calcDataBounds(ximin, ximax, ymin, ymax);
1169  myXScale.xiSet(ximin, ximax);
1170  myYScale.YVrange(ymin, ymax);
1171  break;
1172 
1173  case scaleStateMachine::eState::fix:
1174  calcDataBounds(ximin, ximax, ymin, ymax);
1175  myXScale.xiSet(ximin, ximax);
1176  break;
1177 
1178  case scaleStateMachine::eState::fitzoom:
1179  case scaleStateMachine::eState::fixzoom:
1180  break;
1181 
1182  default:
1183  return false;
1184  }
1185 
1186  myXScale.calculate();
1187  myYScale.calculate();
1188 
1189  // myXScale.text();
1190 
1191  return true;
1192  }
1193 
1194  std::vector<trace *> &traces()
1195  {
1196  return myTrace;
1197  }
1198 
1199  // bool isZoomed() const
1200  // {
1201  // return myfZoom;
1202  // }
1203 
1205  double pixel2Xuser(int xpixel) const
1206  {
1207  return myXScale.XP2XU(xpixel);
1208  }
1209  int xuser2pixel(double xu) const
1210  {
1211  return myXScale.XU2XP(xu);
1212  }
1214  double pixel2Yuser(int ypixel) const
1215  {
1216  return myYScale.YP2YV(ypixel);
1217  }
1218 
1219  private:
1221  std::vector<trace *> myTrace;
1222 
1223  // scales
1224  scaleStateMachine myScaleStateMachine;
1225  XScale myXScale;
1226  YScale myYScale;
1227 
1228  bool myfGrid; // true if tick and grid marks reuired
1229  bool myfXset; // true if the x user range has been set
1230  bool myfDrag; // drag in progress
1231 
1232  int myStartDragX;
1233  int myStartDragY;
1234  int myStopDragX;
1235  int myStopDragY;
1236 
1237  void calcDataBounds(
1238  int &xmin, int &xmax,
1239  double &ymin, double &ymax)
1240  {
1241  myTrace[0]->bounds(
1242  xmin, xmax,
1243  ymin, ymax);
1244  for (auto &t : myTrace)
1245  {
1246  int txmin, txmax;
1247  double tymin, tymax;
1248  txmin = txmax = tymax = 0;
1249  tymin = std::numeric_limits<double>::max();
1250  t->bounds(txmin, txmax, tymin, tymax);
1251  if (txmin < xmin)
1252  xmin = txmin;
1253  if (txmax > xmax)
1254  xmax = txmax;
1255  if (tymin < ymin)
1256  ymin = tymin;
1257  if (tymax > ymax)
1258  ymax = tymax;
1259  }
1260  }
1261  bool isGoodDrag()
1262  {
1263  return (myfDrag && myStopDragX > 0 && myStopDragX > myStartDragX && myStopDragY > myStartDragY);
1264  }
1265 
1266  std::vector<double> ytickValues()
1267  {
1268  std::vector<double> vl;
1269  double mn = myYScale.YPmax();
1270  double mx = myYScale.YPmin();
1271  double range = mx - mn;
1272  if (range < minDataRange)
1273  {
1274  // plot is single valued
1275  // display just one tick
1276  vl.push_back(mn);
1277  return vl;
1278  }
1279  double inc = myYScale.YVrange() / 4;
1280  double tickValue;
1281  if (inc > 1)
1282  {
1283  inc = (int)inc;
1284  tickValue = myYScale.YP2YV(myYScale.YPmin());
1285  }
1286  else
1287  {
1288  tickValue = mn;
1289  }
1290  // if (tick < 0)
1291  // return vl;
1292 
1293  while (true)
1294  {
1295  double v = tickValue;
1296  if (v > 100)
1297  v = ((int)v / 100) * 100;
1298  else if (v > 10)
1299  v = ((int)v / 10) * 10;
1300  vl.push_back(v);
1301  tickValue += inc;
1302  if (tickValue >= mx)
1303  break;
1304  }
1305  vl.push_back(mx);
1306  return vl;
1307  }
1311  std::string numberformat(double f)
1312  {
1313  if (f == 0)
1314  {
1315  return "0";
1316  }
1317  int n = 2; // number of significant digits
1318  int d = (int)::floor(::log10(f < 0 ? -f : f)) + 1; /*digits before decimal point*/
1319  double order = ::pow(10., n - d);
1320  std::stringstream ss;
1321  ss << std::fixed << std::setprecision(std::max(n - d, 0)) << round(f * order) / order;
1322  return ss.str();
1323  }
1324  void drawYAxis(wex::shapes &S)
1325  {
1326  S.color(0xFFFFFF - bgcolor());
1327  S.textHeight(15);
1328 
1329  S.line({50, myYScale.YPmin(),
1330  50, myYScale.YPmax()});
1331 
1332  for (double y : ytickValues())
1333  {
1334  int yp = myYScale.YV2YP(y);
1335  S.text(numberformat(y),
1336  {0, yp - 8, 50, 15});
1337  S.line({50, yp,
1338  60, yp});
1339  if (myfGrid)
1340  {
1341  auto kpmax = myXScale.XPmax();
1342  for (int kp = 65;
1343  kp < kpmax;
1344  kp += 25)
1345  {
1346  S.pixel(kp, yp);
1347  S.pixel(kp + 1, yp);
1348  }
1349  }
1350  }
1351  // int yp = scale::get().Y2Pixel(mx);
1352  // S.text(myMaxYLabel,
1353  // {0, yp + 10, 50, 15});
1354  }
1355  void drawXAxis(wex::shapes &S, int ypos)
1356  {
1357  S.color(0xFFFFFF - bgcolor());
1358  S.textHeight(15);
1359  S.line({myXScale.XPmin(), ypos, myXScale.XPmax(), ypos});
1360  if (!myfGrid)
1361  {
1362  // there is no grid
1363  // so just label the minimum, maximum x points
1364  float xmin_label_value = 0;
1365  float xmax_label_value = 100;
1366  if (myfXset)
1367  {
1368  xmin_label_value = myXScale.XUmin();
1369  xmax_label_value = myXScale.XUmax();
1370  }
1371  S.text(std::to_string((int)xmin_label_value), {myXScale.XPmin(), ypos + 3, 50, 15});
1372  S.text(std::to_string((int)xmax_label_value), {myXScale.XPmax() - 25, ypos + 3, 50, 15});
1373  return;
1374  }
1375  // there is a grid
1376 
1377  int tickCount = 8;
1378  float xutickinc = (myXScale.XUmax() - myXScale.XUmin()) / tickCount;
1379 
1380  // std::cout << "X ticks ";
1381  // myXScale.text();
1382  // std::cout
1383  // << " xutickinc " << xutickinc
1384  // << "\n";
1385 
1386  // if possible, place tick marks at integer values of x index
1387  if (xutickinc > 1)
1388  xutickinc = floor(xutickinc);
1389 
1390  for (int kxtick = 0; kxtick <= tickCount; kxtick++)
1391  {
1392  float tickXU = myXScale.XUmin() + kxtick * xutickinc;
1393  // float tick_label_value = myXScale.XI2XU(tickXU);
1394  int xPixel = myXScale.XU2XP(tickXU);
1395 
1396  // std::cout << "tick " << kxtick << " xu " << tickXU
1397  // << " " << xPixel << "\n";
1398 
1399  S.text(
1400  std::to_string(tickXU).substr(0, 4),
1401  {xPixel, ypos + 1, 50, 15});
1402 
1403  for (
1404  int k = myYScale.YPmax();
1405  k < myYScale.YPmin();
1406  k = k + 25)
1407  {
1408  S.pixel(xPixel, k);
1409  S.pixel(xPixel, k + 1);
1410  }
1411  }
1412  }
1413  void drawTrace(trace *t, shapes &S)
1414  {
1415  S.penThick(t->thick());
1416  S.color(t->color());
1417 
1418  bool first = true;
1419  int xi = 0;
1420  double prevX, prev;
1421 
1422  switch (t->myType)
1423  {
1424  case trace::eType::plot:
1425  {
1426  POINT p;
1427  std::vector<POINT> vp;
1428  for (auto y : t->getY())
1429  {
1430  // scale
1431  p.x = myXScale.XI2XP(xi++);
1432  p.y = myYScale.YV2YP(y);
1433  vp.push_back(p);
1434  }
1435  S.polyLine(vp.data(), t->size());
1436  }
1437  break;
1438 
1439  case trace::eType::scatter:
1440 
1441  for (auto y : t->getY())
1442  {
1443  S.rectangle(
1444  {myXScale.XI2XP(xi++) - 5, myYScale.YV2YP(y) - 5,
1445  5, 5});
1446  }
1447  break;
1448 
1449  case trace::eType::realtime:
1450  {
1451 
1452  for (auto y : t->getY())
1453  {
1454 
1455  // scale data point to pixels
1456  double x = myXScale.XI2XP(xi++);
1457  double yp = myYScale.YV2YP(y);
1458 
1459  if (first)
1460  {
1461  first = false;
1462  }
1463  else
1464  {
1465  // draw line from previous to this data point
1466  S.line(
1467  {(int)prevX, (int)prev, (int)x, (int)yp});
1468  }
1469 
1470  prevX = x;
1471  prev = yp;
1472  }
1473  }
1474  break;
1475 
1476  default:
1477  throw std::runtime_error(
1478  "Trace type NYI");
1479  }
1480  }
1481  void drawSelectedArea(PAINTSTRUCT &ps)
1482  {
1483  if (!isGoodDrag())
1484  return;
1485 
1486  // display selected area by drawing a box around it
1487  wex::shapes S(ps);
1488 
1489  // contrast to background color
1490  S.color(0xFFFFFF ^ bgcolor());
1491 
1492  S.line({myStartDragX, myStartDragY, myStopDragX, myStartDragY});
1493  S.line({myStopDragX, myStartDragY, myStopDragX, myStopDragY});
1494  S.line({myStopDragX, myStopDragY, myStartDragX, myStopDragY});
1495  S.line({myStartDragX, myStopDragY, myStartDragX, myStartDragY});
1496  }
1497  };
1498  }
1499 }
void click(std::function< void(void)> f, bool propogate=false)
register click event handler
Definition: wex.h:276
The base class for all windex gui elements.
Definition: wex.h:824
void update()
force widget to redraw completely
Definition: wex.h:1575
eventhandler & events()
Get event handler.
Definition: wex.h:1649
void enable(bool f=true)
Enable/Disable, default enable.
Definition: wex.h:963
sMouse getMouseStatus()
Get mouse status.
Definition: wex.h:1171
void bgcolor(int color)
Change background color.
Definition: wex.h:949
Draw a 2D plot.
Definition: plot2d.h:893
double pixel2Yuser(int ypixel) const
get Y user value from y pixel
Definition: plot2d.h:1214
void dragExtend(sMouse &m)
Disable auto-fit scaling and set Y minumum, maximum.
Definition: plot2d.h:1112
trace & AddScatterTrace()
Add scatter trace.
Definition: plot2d.h:1028
bool CalcScale(int w, int h)
calculate scaling factors so plot will fit in window client area
Definition: plot2d.h:1146
double pixel2Xuser(int xpixel) const
get X user value from x pixel
Definition: plot2d.h:1205
void XUValues(float start_xu, float scale_xi2xu)
Set conversion from index of x value buffer to x user units.
Definition: plot2d.h:1126
void grid(bool enable)
Enable display of grid markings.
Definition: plot2d.h:1038
trace & AddRealTimeTrace(int w)
Add real time trace.
Definition: plot2d.h:1011
void clear()
Remove all traces from plot.
Definition: plot2d.h:1086
plot(gui *parent)
CTOR.
Definition: plot2d.h:898
void XValues(float start_xu, float scale_xi2xu)
for backward compatability
Definition: plot2d.h:1137
void setFixedScale(double minX, double maxX, double minY, double maxY)
Set fixed scale.
Definition: plot2d.h:1049
trace & AddStaticTrace()
Add static trace.
Definition: plot2d.h:995
Single trace to be plotted.
Definition: plot2d.h:562
void color(int clr)
set color
Definition: plot2d.h:634
double value(double xfraction)
y value at fractional position along x-axis
Definition: plot2d.h:663
void set(const std::vector< double > &y)
set plot data
Definition: plot2d.h:578
void thick(int t)
set trace thickness in pixels
Definition: plot2d.h:644
void add(double x, double y)
add point to scatter trace
Definition: plot2d.h:618
int size() const
get number of points
Definition: plot2d.h:654
void clear()
clear data from trace
Definition: plot2d.h:627
void add(double y)
add new value to real time data
Definition: plot2d.h:603
A class that offers application code methods to draw on a window.
Definition: wex.h:525
void textHeight(int h)
Set text height.
Definition: wex.h:786
void text(const std::string &t, const std::vector< int > &v)
Draw text.
Definition: wex.h:727
void line(const std::vector< int > &v)
Draw line between two points.
Definition: wex.h:616
void pixel(int x, int y)
Color a pixel.
Definition: wex.h:609
void color(int r, int g, int b)
Set color for drawings.
Definition: wex.h:559
A structure containing the mouse status for event handlers.
Definition: wex.h:28