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  double XUmin() const
382  {
383  return xumin;
384  }
385  double 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  switch (theState)
438  {
439  case scaleStateMachine::eState::fit:
440  yvminFit = min;
441  yvmaxFit = max;
442  break;
443  case scaleStateMachine::eState::fix:
444  yvminFix = min;
445  yvminFix = max;
446  break;
447  }
448  }
449 
450  double YVrange() const
451  {
452  return yvmax - yvmin;
453  }
454 
459 
460  void YPrange(int min, int max)
461  {
462  ypmin = min;
463  ypmax = max;
464  calculate();
465  }
466 
467  void zoom(double min, double max)
468  {
469  yvminZoom = min;
470  yvmaxZoom = max;
471  }
472 
473  void fixSet(double min, double max)
474  {
475  yvminFix = min;
476  yvmaxFix = max;
477  }
478 
479  double YP2YV(int pixel) const
480  {
481  return yvmin - (ypmin - pixel) / syv2yp;
482  }
483 
484  int YV2YP(double v) const
485  {
486  return ypmin + syv2yp * (v - yvmin);
487  }
488  double YVmin() const
489  {
490  return yvmin;
491  }
492  double YVmax() const
493  {
494  return yvmax;
495  }
496  int YPmin() const
497  {
498  return ypmin;
499  }
500  int YPmax() const
501  {
502  return ypmax;
503  }
504  void text() const
505  {
506  std::cout << "yv " << yvmin << " " << yvmax
507  << " xp " << ypmin << " " << ypmax
508  << " " << syv2yp
509  << "\n";
510  }
511 
512  void calculate()
513  {
514  switch (theState)
515  {
516  case scaleStateMachine::eState::fit:
517  yvmin = yvminFit;
518  yvmax = yvmaxFit;
519  break;
520 
521  case scaleStateMachine::eState::fix:
522  yvmin = yvminFix;
523  yvmax = yvmaxFix;
524  break;
525 
526  case scaleStateMachine::eState::fitzoom:
527  case scaleStateMachine::eState::fixzoom:
528  yvmin = yvminZoom;
529  yvmax = yvmaxZoom;
530  break;
531  }
532  double yvrange = yvmax - yvmin;
533  if (fabs(yvrange) < 0.00001)
534  {
535  // seems like there are no meaningful data
536  syv2yp = 1;
537  return;
538  }
539 
540  syv2yp = -(ypmin - ypmax) / yvrange;
541  }
542 
545 
546  std::vector<double> tickValues() const
547  {
548  std::vector<double> vl;
549  double rangeV = fabs(yvmax - yvmin);
550  if (rangeV < minDataRange)
551  {
552  // plot is single valued
553  // display just one tick
554  vl.push_back(yvmin);
555  return vl;
556  }
557  double incV = rangeV / 4;
558  double tickValue;
559  if (incV > 1)
560  incV = (int)incV;
561 
562  tickValue = yvmin;
563 
564  while (true)
565  {
566  double v = tickValue;
567  if (v > 100)
568  v = ((int)v / 100) * 100;
569  else if (v > 10)
570  v = ((int)v / 10) * 10;
571  vl.push_back(v);
572  tickValue += incV;
573  if (tickValue >= yvmax)
574  break;
575  }
576  // vl.push_back(mx);
577  return vl;
578  }
579  };
580 
581  // @endcond
582 
616  class trace
617  {
618  public:
619  enum class eType
620  {
621  plot,
622  realtime,
623  scatter
624  } myType; // trace type
625 
633  void set(const std::vector<double> &y)
634  {
635  if ((myType != eType::plot) && (myType != eType::scatter))
636  throw std::runtime_error("plot2d error: plot data added to non plot/scatter trace");
637 
638  myY = y;
639  }
648  void set(double *begin, double *end)
649  {
650  if ((myType != eType::plot) && (myType != eType::scatter))
651  throw std::runtime_error("plot2d error: plot data added to non plot/scatter trace");
652 
653  myY = std::vector(begin, end);
654  }
655  void setScatterX(const std::vector<double> &x)
656  {
657  if (myType != eType::scatter)
658  throw std::runtime_error("plot2d error: plot X added to non scatter trace");
659 
660  myX = x;
661  }
662  std::vector<double> get() const
663  {
664  return myY;
665  }
666 
673  void add(double y)
674  {
675  if (myType != eType::realtime)
676  throw std::runtime_error("plot2d error: realtime data added to non realtime trace");
677  myY[myCircular.add()] = y;
678  }
679 
688  void add(double x, double y)
689  {
690  if (myType != eType::scatter)
691  throw std::runtime_error("plot2d error: point data added to non scatter type trace");
692  myX.push_back(x);
693  myY.push_back(y);
694  }
695 
697  void clear()
698  {
699  myX.clear();
700  myY.clear();
701  }
702 
704  void color(int clr)
705  {
706  myColor = clr;
707  }
708  int color() const
709  {
710  return myColor;
711  }
712 
714  void thick(int t)
715  {
716  myThick = t;
717  }
718  int thick() const
719  {
720  return myThick;
721  }
722 
724  int size() const
725  {
726  return (int)myY.size();
727  }
728 
733  double value(double xfraction)
734  {
735  if (0 > xfraction || xfraction > 1)
736  return 0;
737  return myY[(int)(xfraction * myY.size())];
738  }
739 
740  const std::vector<double> &getX() const
741  {
742  return myX;
743  }
744  const std::vector<double> &getY()
745  {
746  if (myType != eType::realtime)
747  return myY;
748 
749  static std::vector<double> ret;
750  ret.clear();
751  for (int yidx = myCircular.first();
752  yidx >= 0;
753  yidx = myCircular.next())
754  ret.push_back(myY[yidx]);
755  return ret;
756  }
757 
758  private:
759  friend plot;
760 
761  plot *myPlot; // plot where this trace is displayed
762  std::vector<double> myX; // X value of each data point
763  std::vector<double> myY; // Y value of each data point
764  cCircularBuffer myCircular; // maintain indices of circular buffer used by real time trace
765  int myColor; // trace color
766  int myThick; // trace thickness
767 
773  trace()
774  : myThick(1), myType(eType::plot)
775  {
776  }
777 
779  void Plot(plot *p)
780  {
781  myPlot = p;
782  }
783 
792  void realTime(int w)
793  {
794  myType = eType::realtime;
795  myY.clear();
796  myY.resize(w);
797  myCircular.set(w);
798  }
799 
801  void scatter()
802  {
803  myType = eType::scatter;
804  myY.clear();
805  myX.clear();
806  }
807 
809  void bounds(
810  int &txmin, int &txmax,
811  double &tymin, double &tymax)
812  {
813  if (myY.size())
814  {
815 
816  // find the x limits
817 
818  txmin = 0;
819  txmax = myY.size() - 1;
820 
821  // if (myType == eType::realtime || myX.size() == 0)
822  // {
823  // txmin = 0;
824  // txmax = myY.size();
825  // }
826  // else
827  // {
828  // // auto result = std::minmax_element(
829  // // myX.begin(),
830  // // myX.end());
831  // // txmin = *result.first;
832  // // txmax = *result.second;
833  // txmin =
834  // }
835 
836  // find the y limits
837 
838  if (myType == eType::realtime)
839  {
840  if (!myCircular.isValidData())
841  {
842  // no data is buffer
843  tymin = -5;
844  tymax = 5;
845  return;
846  }
847 
848  if (myCircular.isFull())
849  {
850  // buffer is full
851  auto result = std::minmax_element(
852  myY.begin(),
853  myY.end());
854  tymin = *result.first;
855  tymax = *result.second;
856  }
857 
858  // buffer is partially full
859  // set bounds using the data that has been received so far
860  tymin = myY[0];
861  tymax = myY[0];
862  for (int idx = myCircular.first();
863  idx >= 0;
864  idx = myCircular.next())
865  {
866  double y = myY[idx];
867  if (y < tymin)
868  tymin = y;
869  if (y > tymax)
870  tymax = y;
871  }
872  }
873  else
874  {
875  // scatter or static trace
876  auto result = std::minmax_element(
877  myY.begin(),
878  myY.end());
879  tymin = *result.first;
880  tymax = *result.second;
881  }
882  }
883  }
884  };
885 
886  class axis
887  {
888  public:
889  enum class eOrient
890  {
891  none,
892  horz,
893  vert,
894  };
895 
896  axis()
897  : myfEnable(true),
898  myfGrid(false)
899  {
900  }
901 
902  void set(
903  eOrient o)
904  {
905  myOrient = o;
906  }
907 
908  void enable(bool f = true)
909  {
910  myfEnable = f;
911  }
912 
913  void setValueRange(
914  double min,
915  double max)
916  {
917  myvmin = min;
918  myvmax = max;
919  }
920 
921  void setGrid(bool f = true)
922  {
923  myfGrid = f;
924  }
925 
926  void draw(
927  wex::shapes &S,
928  const XScale &xs,
929  const YScale &ys)
930  {
931  if (!myfEnable)
932  return;
933 
934  double scale;
935  int tickCount = 8;
936  int paxis;
937  int tickPixel;
938  if (myOrient == eOrient::horz)
939  {
940  paxis = ys.YPmin();
941  S.line({xs.XPmin(), paxis,
942  xs.XPmax(), paxis});
943  scale = (xs.XPmax() - xs.XPmin()) / (xs.XUmax() - xs.XUmin());
944  }
945  else
946  {
947  // // y pixels are indexed from top of screen
948  paxis = xs.XPmin();
949  tickCount = 4;
950 
951  S.line({paxis, ys.YPmin(),
952  paxis, ys.YPmax()});
953 
954  scale = (ys.YPmax() - ys.YPmin()) / ys.YVrange();
955  }
956 
957  for (double tickValue : tickValues(tickCount, myvmin, myvmax))
958  {
959  switch (myOrient)
960  {
961  case eOrient::horz:
962 
963  tickPixel = xs.XPmin() + scale * (tickValue - xs.XUmin());
964  S.line(
965  {tickPixel, paxis - 5,
966  tickPixel, paxis + 5});
967  S.text(
968  numberformat(tickValue),
969  {tickPixel, paxis + 5,
970  tickPixel + 50, paxis + 15});
971  if (myfGrid)
972  {
973  for (int kp = paxis;
974  kp >= ys.YPmax();
975  kp -= 25)
976  {
977  S.pixel(tickPixel, kp);
978  S.pixel(tickPixel, kp + 1);
979  }
980  }
981  break;
982 
983  case eOrient::vert:
984 
985  tickPixel = ys.YPmin() + scale * (tickValue - ys.YVmin());
986  S.line({paxis - 5, tickPixel,
987  paxis + 5, tickPixel});
988  S.text(
989  numberformat(tickValue),
990  {paxis - 50, tickPixel, paxis - 5, tickPixel + 15});
991  if (myfGrid)
992  {
993  for (int kp = paxis;
994  kp < xs.XPmax();
995  kp += 25)
996  {
997  S.pixel(kp, tickPixel);
998  S.pixel(kp + 1, tickPixel);
999  }
1000  }
1001  break;
1002  }
1003  }
1004  }
1005 
1006  private:
1007  eOrient myOrient;
1008  double myvmin;
1009  double myvmax;
1010  bool myfEnable;
1011  bool myfGrid;
1012 
1013  std::vector<double> tickValues(
1014  int count,
1015  double min,
1016  double max)
1017  {
1018  std::vector<double> ret;
1019  double tickinc = (max - min) / count;
1020  if (tickinc > 1)
1021  tickinc = floor(tickinc);
1022  for (
1023  double v = min;
1024  v < max;
1025  v += tickinc)
1026  {
1027  ret.push_back(v);
1028  }
1029  return ret;
1030  }
1031 
1035  std::string numberformat(double f)
1036  {
1037  if (f == 0)
1038  {
1039  return "0";
1040  }
1041  int n = 3; // number of significant digits
1042  int d = (int)::floor(::log10(f < 0 ? -f : f)) + 1; /*digits before decimal point*/
1043  double order = ::pow(10., n - d);
1044  std::stringstream ss;
1045  ss << std::fixed << std::setprecision(std::max(n - d, 0)) << round(f * order) / order;
1046  return ss.str();
1047  }
1048  };
1049 
1050  // class rightAxis : public axis
1051  // {
1052  // bool myfEnable;
1053 
1054  // public:
1055  // rightAxis()
1056  // : myfEnable(false)
1057  // {
1058  // }
1059  // void enable()
1060  // {
1061  // myfEnable = true;
1062  // set( eOrient::vert );
1063  // }
1064  // };
1065 
1146  class plot : public gui
1147  {
1148  public:
1152  plot(gui *parent)
1153  : gui(parent), myfDrag(false),
1154  myXScale(myScaleStateMachine),
1155  myYScale(myScaleStateMachine),
1156  mypBottomMarginWidth(50),
1157  mypLeftMarginWidth(70)
1158  {
1159  text("Plot");
1160  myRightAxis.enable(false);
1161  myRightAxis.set(axis::eOrient::vert);
1162  myLeftAxis.set(axis::eOrient::vert);
1163  myBottomAxis.set(axis::eOrient::horz);
1164 
1165  events().draw(
1166  [this](PAINTSTRUCT &ps)
1167  {
1168  // calculate scaling factors
1169  // so plot will fit
1170  if (!CalcScale(ps.rcPaint.right,
1171  ps.rcPaint.bottom))
1172  {
1173  wex::msgbox("Plot has no data");
1174  return;
1175  }
1176 
1177  wex::shapes S(ps);
1178 
1179  // draw axis
1180  drawAxis(S);
1181 
1182  // loop over traces
1183  for (auto t : myTrace)
1184  drawTrace(t, S);
1185 
1186  drawSelectedArea(ps);
1187  });
1188 
1189  events().click(
1190  [&]
1191  {
1192  // start dragging for selected area
1193  auto m = getMouseStatus();
1194  myStartDragX = m.x;
1195  myStartDragY = m.y;
1196  myStopDragX = -1;
1197  myfDrag = true;
1198  });
1199  events().mouseMove(
1200  [&](wex::sMouse &m)
1201  {
1202  // extend selected area as mouse is dragged
1203  dragExtend(m);
1204  });
1205  events().mouseUp(
1206  [&]
1207  {
1208  zoomHandler();
1209  myfDrag = false;
1210  update();
1211  });
1212 
1213  events().clickRight(
1214  [&]
1215  {
1216  myScaleStateMachine.event(scaleStateMachine::eEvent::unzoom);
1217  update();
1218  });
1219  }
1220 
1221  ~plot()
1222  {
1223  }
1224 
1234  {
1235  trace *t = new trace();
1236  t->Plot(this);
1237  myTrace.push_back(t);
1238  return *t;
1239  }
1240 
1250  {
1251  trace *t = new trace();
1252  t->Plot(this);
1253  t->realTime(w);
1254  myTrace.push_back(t);
1255  return *t;
1256  }
1257 
1267  {
1268  trace *t = new trace();
1269  t->Plot(this);
1270  t->scatter();
1271  myTrace.push_back(t);
1272  return *t;
1273  }
1274 
1276  void grid(bool enable)
1277  {
1278  myfGrid = enable;
1279  myLeftAxis.setGrid(enable);
1280  myBottomAxis.setGrid(enable);
1281  }
1282 
1288 
1290  double minX, double maxX, double minY, double maxY)
1291  {
1292  if (maxX <= minX || maxY <= minY)
1293  throw std::runtime_error(
1294  "plot::setFixedScale bad params");
1295 
1296  // change scale state
1297  if (
1298  myScaleStateMachine.event(
1299  scaleStateMachine::eEvent::fix) == scaleStateMachine::eState::none)
1300  return;
1301 
1302  if (!myfXset)
1303  XUValues(0, 1);
1304 
1305  myXScale.fixSet(minX, maxX);
1306  myYScale.fixSet(minY, maxY);
1307 
1308  // myXScale.text();
1309  }
1310 
1311  void setFitScale()
1312  {
1313  if (
1314  myScaleStateMachine.event(
1315  scaleStateMachine::eEvent::fit) == scaleStateMachine::eState::none)
1316  throw std::runtime_error(
1317  "wex plot cannot return to fit scale");
1318  }
1319 
1324 
1325  void setMarginWidths(int pBottomMarginWidth, int pLeftMarginWidth)
1326  {
1327  mypBottomMarginWidth = pBottomMarginWidth;
1328  mypLeftMarginWidth = pLeftMarginWidth;
1329  }
1330 
1331  void setXAxisLabel(const std::string &label)
1332  {
1333  myXAxisLabel = label;
1334  }
1335  void setYAxisLabel(const std::string &label)
1336  {
1337  myYAxisLabel = label;
1338  }
1339 
1343 
1345  double minValue,
1346  double maxValue)
1347  {
1348  myRightAxis.enable();
1349  myRightAxis.setValueRange(
1350  minValue,
1351  maxValue);
1352  }
1353 
1354  int traceCount() const
1355  {
1356  return (int)myTrace.size();
1357  }
1358 
1360  void clear()
1361  {
1362  myTrace.clear();
1363  }
1364 
1369  // void fixYVminmax(double min, double max)
1370  // {
1371  // myfFit = false;
1372  // myfZoom = false;
1373  // myYScale.YVrange(min, max);
1374  // }
1375 
1377  // void autoFit()
1378  // {
1379  // myfFit = true;
1380  // myfDrag = false;
1381  // myfZoom = false;
1382  // myXScale.zoomExit();
1383  // update();
1384  // }
1385 
1387  {
1388  if (!myfDrag)
1389  return;
1390  myStopDragX = m.x;
1391  myStopDragY = m.y;
1392  update();
1393  }
1400  void XUValues(
1401  float start_xu,
1402  float scale_xi2xu)
1403  {
1404  myXScale.xi2xuSet(start_xu, scale_xi2xu);
1405  myfXset = true;
1406  }
1407 
1411  void XValues(
1412  float start_xu,
1413  float scale_xi2xu)
1414  {
1415  XUValues(start_xu, scale_xi2xu);
1416  }
1417 
1418  std::vector<trace *> &traces()
1419  {
1420  return myTrace;
1421  }
1422 
1423  const YScale &yscale() const
1424  {
1425  return myYScale;
1426  }
1427 
1429  double pixel2Xuser(int xpixel) const
1430  {
1431  return myXScale.XP2XU(xpixel);
1432  }
1433 
1435  int xuser2pixel(double xu) const
1436  {
1437  return myXScale.XU2XP(xu);
1438  }
1439 
1441  double pixel2Yuser(int ypixel) const
1442  {
1443  return myYScale.YP2YV(ypixel);
1444  }
1445 
1447  int yuser2pixel(double yu) const
1448  {
1449  return myYScale.YV2YP(yu);
1450  }
1451 
1452 // methods that need to be unit tested, and therefore need to be public
1453 #ifndef UNIT_TEST
1454  private:
1455 #endif
1456 
1459  bool CalcScale(int w, int h)
1460  {
1461  // std::cout << "Plot::CalcScale " << w << " " << h << "\n";
1462 
1463  // If user has not called XValues(), set X-axis scale to 1
1464  if (!myfXset)
1465  XUValues(0, 1);
1466 
1467  // check there are traces that need to be drawn
1468  if (!myTrace.size())
1469  return false;
1470 
1471  // set pixel ranges for the axis
1472  myYScale.YPrange(h - mypBottomMarginWidth, 10);
1473  myXScale.xpSet(mypLeftMarginWidth, w - 50);
1474 
1475  int ximin, ximax;
1476  double ymin, ymax;
1477  switch (myScaleStateMachine.myState)
1478  {
1479  case scaleStateMachine::eState::fit:
1480 
1481  calcDataBounds(ximin, ximax, ymin, ymax);
1482  myXScale.xiSet(ximin, ximax);
1483  myYScale.YVrange(ymin, ymax);
1484  myLeftAxis.setValueRange(ymin, ymax);
1485 
1486  break;
1487 
1488  case scaleStateMachine::eState::fix:
1489  break;
1490 
1491  case scaleStateMachine::eState::fitzoom:
1492  case scaleStateMachine::eState::fixzoom:
1493  break;
1494 
1495  default:
1496  return false;
1497  }
1498 
1499  myXScale.calculate();
1500  myYScale.calculate();
1501 
1502  myBottomAxis.setValueRange(myXScale.XUmin(), myXScale.XUmax());
1503  myLeftAxis.setValueRange(myYScale.YVmin(), myYScale.YVmax());
1504 
1505  // myXScale.text();
1506 
1507  return true;
1508  }
1509 
1510  private:
1512  std::vector<trace *> myTrace;
1513 
1514  // scales
1515  scaleStateMachine myScaleStateMachine;
1516  XScale myXScale;
1517  YScale myYScale;
1518 
1519  axis myLeftAxis;
1520  axis myRightAxis;
1521  axis myBottomAxis;
1522 
1523  int mypBottomMarginWidth, mypLeftMarginWidth;
1524  std::string myXAxisLabel, myYAxisLabel;
1525 
1526  bool myfGrid; // true if tick and grid marks reuired
1527  bool myfXset; // true if the x user range has been set
1528  bool myfDrag; // drag in progress
1529 
1530  int myStartDragX;
1531  int myStartDragY;
1532  int myStopDragX;
1533  int myStopDragY;
1534 
1535  void calcDataBounds(
1536  int &xmin, int &xmax,
1537  double &ymin, double &ymax)
1538  {
1539  myTrace[0]->bounds(
1540  xmin, xmax,
1541  ymin, ymax);
1542  for (auto &t : myTrace)
1543  {
1544  int txmin, txmax;
1545  double tymin, tymax;
1546  txmin = txmax = tymax = 0;
1547  tymin = std::numeric_limits<double>::max();
1548  t->bounds(txmin, txmax, tymin, tymax);
1549  if (txmin < xmin)
1550  xmin = txmin;
1551  if (txmax > xmax)
1552  xmax = txmax;
1553  if (tymin < ymin)
1554  ymin = tymin;
1555  if (tymax > ymax)
1556  ymax = tymax;
1557  }
1558  }
1559  void zoomHandler()
1560  {
1561  // check if user has completed a good drag operation
1562  if (!isGoodDrag())
1563  return;
1564 
1565  // change scale state
1566  if (myScaleStateMachine.event(
1567  scaleStateMachine::eEvent::zoom) ==
1568  scaleStateMachine::eState::none)
1569  {
1570  // scale state change failed
1571  // probably zoom attempt on already zoomed plot
1572  return;
1573  }
1574 
1575  /* set the zoom scale values
1576 
1577  The scale calculation, and plot redrawing will be done in the next update() call
1578  */
1579 
1580  myXScale.zoom(myXScale.XP2XU(myStartDragX), myXScale.XP2XU(myStopDragX));
1581  myYScale.zoom(myYScale.YP2YV(myStopDragY), myYScale.YP2YV(myStartDragY));
1582  }
1583  bool isGoodDrag()
1584  {
1585  return (myfDrag && myStopDragX > 0 && myStopDragX > myStartDragX && myStopDragY > myStartDragY);
1586  }
1587 
1588  void drawAxis(wex::shapes &S)
1589  {
1590  S.color(0xFFFFFF - bgcolor());
1591  S.textHeight(15);
1592 
1593  myLeftAxis.draw(
1594  S,
1595  myXScale,
1596  myYScale);
1597 
1598  if (myYAxisLabel.length())
1599  {
1600  S.textVertical();
1601  S.text(myYAxisLabel,
1602  {mypLeftMarginWidth - 50, myYScale.YPmax() + 30});
1603  S.textVertical(false);
1604  }
1605  if( myXAxisLabel.length())
1606  {
1607  S.text(myXAxisLabel,
1608  { myXScale.XPmax() -30 , myYScale.YPmin() + 30});
1609  }
1610 
1611  // myRightAxis.draw(
1612  // S,
1613  // myXScale.XPmax(),
1614  // myXScale.XPmax(),
1615  // myYScale.YPmax(),
1616  // myYScale.YPmin());
1617 
1618  myBottomAxis.draw(
1619  S,
1620  myXScale,
1621  myYScale);
1622  }
1623 
1624  void drawTrace(trace *t, shapes &S)
1625  {
1626  S.penThick(t->thick());
1627  S.color(t->color());
1628 
1629  bool first = true;
1630  int xi = 0;
1631  double prevX, prev;
1632 
1633  switch (t->myType)
1634  {
1635  case trace::eType::plot:
1636  {
1637  POINT p;
1638  std::vector<POINT> vp;
1639  for (auto y : t->getY())
1640  {
1641  // scale
1642  p.x = myXScale.XI2XP(xi++);
1643  p.y = myYScale.YV2YP(y);
1644  vp.push_back(p);
1645  }
1646  S.polyLine(vp.data(), t->size());
1647  }
1648  break;
1649 
1650  case trace::eType::scatter:
1651  {
1652  S.fill();
1653  int lnght = 10;
1654  const double *px = t->getX().data();
1655  const double *py = t->getY().data();
1656  for (int k = 0; k < t->getY().size(); k++)
1657  {
1658 
1659  int x = myXScale.XI2XP(*(px + k));
1660  int y = myYScale.YV2YP(*(py + k));
1661  S.rectangle(
1662  {x - lnght/2, y - lnght/2,
1663  lnght, lnght});
1664  }
1665  }
1666  break;
1667 
1668  case trace::eType::realtime:
1669  {
1670 
1671  for (auto y : t->getY())
1672  {
1673 
1674  // scale data point to pixels
1675  double x = myXScale.XI2XP(xi++);
1676  double yp = myYScale.YV2YP(y);
1677 
1678  if (first)
1679  {
1680  first = false;
1681  }
1682  else
1683  {
1684  // draw line from previous to this data point
1685  S.line(
1686  {(int)prevX, (int)prev, (int)x, (int)yp});
1687  }
1688 
1689  prevX = x;
1690  prev = yp;
1691  }
1692  }
1693  break;
1694 
1695  default:
1696  throw std::runtime_error(
1697  "Trace type NYI");
1698  }
1699  }
1700  void drawSelectedArea(PAINTSTRUCT &ps)
1701  {
1702  if (!isGoodDrag())
1703  return;
1704 
1705  // display selected area by drawing a box around it
1706  wex::shapes S(ps);
1707 
1708  // contrast to background color
1709  S.color(0xFFFFFF ^ bgcolor());
1710 
1711  S.line({myStartDragX, myStartDragY, myStopDragX, myStartDragY});
1712  S.line({myStopDragX, myStartDragY, myStopDragX, myStopDragY});
1713  S.line({myStopDragX, myStopDragY, myStartDragX, myStopDragY});
1714  S.line({myStartDragX, myStopDragY, myStartDragX, myStartDragY});
1715  }
1716  };
1717  }
1718 }
void click(std::function< void(void)> f, bool propogate=false)
register click event handler
Definition: wex.h:280
The base class for all windex gui elements.
Definition: wex.h:900
void update()
force widget to redraw completely
Definition: wex.h:1668
std::vector< int > size()
Size of window client area.
Definition: wex.h:1719
eventhandler & events()
Get event handler.
Definition: wex.h:1742
void enable(bool f=true)
Enable/Disable, default enable.
Definition: wex.h:1039
sMouse getMouseStatus()
Get mouse status.
Definition: wex.h:1247
void bgcolor(int color)
Change background color.
Definition: wex.h:1025
A widget that displays a string.
Definition: wex.h:2676
Definition: plot2d.h:887
Draw a 2D plot.
Definition: plot2d.h:1147
void setMarginWidths(int pBottomMarginWidth, int pLeftMarginWidth)
Set margin widths in pixels.
Definition: plot2d.h:1325
double pixel2Yuser(int ypixel) const
get Y user value from y pixel
Definition: plot2d.h:1441
void dragExtend(sMouse &m)
Disable auto-fit scaling and set Y minumum, maximum.
Definition: plot2d.h:1386
trace & AddScatterTrace()
Add scatter trace.
Definition: plot2d.h:1266
double pixel2Xuser(int xpixel) const
get X user value from x pixel
Definition: plot2d.h:1429
void setRightAxis(double minValue, double maxValue)
Enable drawing a right Y-axis with its own scaling.
Definition: plot2d.h:1344
void XUValues(float start_xu, float scale_xi2xu)
Set conversion from index of x value buffer to x user units.
Definition: plot2d.h:1400
void grid(bool enable)
Enable display of grid markings.
Definition: plot2d.h:1276
trace & AddRealTimeTrace(int w)
Add real time trace.
Definition: plot2d.h:1249
void clear()
Remove all traces from plot.
Definition: plot2d.h:1360
plot(gui *parent)
CTOR.
Definition: plot2d.h:1152
int yuser2pixel(double yu) const
get y pixel value from y user
Definition: plot2d.h:1447
int xuser2pixel(double xu) const
get x pixel value from y user
Definition: plot2d.h:1435
void XValues(float start_xu, float scale_xi2xu)
for backward compatability
Definition: plot2d.h:1411
void setFixedScale(double minX, double maxX, double minY, double maxY)
Set fixed scale.
Definition: plot2d.h:1289
trace & AddStaticTrace()
Add static trace.
Definition: plot2d.h:1233
Single trace to be plotted.
Definition: plot2d.h:617
void color(int clr)
set color
Definition: plot2d.h:704
double value(double xfraction)
y value at fractional position along x-axis
Definition: plot2d.h:733
void set(double *begin, double *end)
set plot data from raw buffer of doubles
Definition: plot2d.h:648
void set(const std::vector< double > &y)
set plot data
Definition: plot2d.h:633
void thick(int t)
set trace thickness in pixels
Definition: plot2d.h:714
void add(double x, double y)
add point to scatter trace
Definition: plot2d.h:688
int size() const
get number of points
Definition: plot2d.h:724
void clear()
clear data from trace
Definition: plot2d.h:697
void add(double y)
add new value to real time data
Definition: plot2d.h:673
A class that offers application code methods to draw on a window.
Definition: wex.h:529
void textHeight(int h)
Set text height.
Definition: wex.h:862
void text(const std::string &t, const std::vector< int > &v)
Draw text.
Definition: wex.h:763
void textVertical(bool f=true)
Enable / disable drawing text in vertical orientation Note: rotated text will NOT be clipped.
Definition: wex.h:847
void line(const std::vector< int > &v)
Draw line between two points.
Definition: wex.h:620
void pixel(int x, int y)
Color a pixel.
Definition: wex.h:613
void color(int r, int g, int b)
Set color for drawings.
Definition: wex.h:563
A structure containing the mouse status for event handlers.
Definition: wex.h:29