Fixed text file permissions
[snark14.git] / tools / Display / plot_t.cpp
1 /** @file plot_t.cpp
2     @author Deniz
3     @description functions in class plot_t is implemented here
4     licensed under (open-source) QPL v1.0
5     which accompanies this distribution in the file QPL
6  */
7 #include <qcolor.h>
8 #include <cmath>
9 #include <list>
10 #include <exception>
11 #include <stdexcept>
12 #include <iostream>
13 #include <qpainter.h>
14 #include <qstring.h>
15
16 #include "verbosity.hpp"
17 #include "plot_t.hpp"
18 #include "line_window_onevar_t.hpp"
19 #include "line_real_set_t.hpp"
20 #include "line_real_t.hpp"
21
22 /** ultimate window-coord class: holds SEVERAL vars' info and can plot them in different colors */
23
24
25 typedef std::list<line_window_onevar_t> data_manyexecs_t;
26
27 plot_t::plot_t(line_real_set_t& LRS, int WIDTH, int HEIGHT) {
28     // can also take padding arguments maybe?
29     // we are a friend of line_real_set_t
30     plotName = "garden variety plot";
31     XAxisName = "x-units";
32     YAxisName = "y-units";
33     rMinX = LRS.getMinX();
34     rMaxX = LRS.getMaxX();
35     rMinY = LRS.getMinY();
36     rMaxY = LRS.getMaxY();
37     // calculate instance variables, mainly to make the right label notches on axes
38     sd_line_t::WIDTH = WIDTH;
39     (this->myWidth) = WIDTH;
40     sd_line_t::HEIGHT = HEIGHT;
41     (this->myHeight) = HEIGHT; // insurance
42     // perform fudging defensively
43     if (0 < sd_line_t::X_HALF_FUDGE_INCREMENT) {
44         rMinX -= sd_line_t::X_HALF_FUDGE_INCREMENT;
45         rMaxX += sd_line_t::X_HALF_FUDGE_INCREMENT;
46     }
47     xr_range = rMaxX - rMinX; // underscore between r's for emphasis
48     yr_range = rMaxY - rMinY;
49     if (0 < sd_line_t::Y_HALF_FUDGE_FACTOR) {
50         double y_half_fudge = yr_range * sd_line_t::Y_HALF_FUDGE_FACTOR;
51         rMinY -= y_half_fudge;
52         rMaxY += y_half_fudge;
53         yr_range = rMaxY - rMinY; // calculate post-fudge yr_range
54     }
55     ywmin = sd_line_t::YWINPAD;
56     ywmax = (this->myHeight) - 1 - sd_line_t::YWINPAD;
57     ywrange = ywmax - ywmin;
58     xwmin = sd_line_t::XWINPAD;
59     xwrange = (this->myWidth) - 1 - 2 * sd_line_t::XWINPAD;
60     ywmin = sd_line_t::YWINPAD;
61     ywmax = (this->myHeight) - 1 - sd_line_t::YWINPAD;
62     xscalar = static_cast<double> (xwrange) / static_cast<double> (xr_range);
63     yscalar = static_cast<double> (ywrange) / yr_range;
64     data.clear(); // JUST in case
65     // LRS has a bunch of line_real_t's inside
66     line_real_set_t::line_real_list_t::const_iterator it = LRS.data.begin();
67     for (; it != LRS.data.end(); it++) {
68         line_real_t LR = (*it);
69         line_window_onevar_t LWO(LR);
70         if (std::verbose > 2) {
71             LWO.show();
72         }
73         data.push_back(LWO);
74     }
75 } // --plot_t ctor/3
76
77 /** give a name to this plot_t object */
78 void plot_t::setName(QString& inName) {
79     plotName = inName;
80 } // --plot_t::setName()
81
82 void plot_t::setXAxisName(QString& inNameX) {
83     XAxisName = inNameX;
84 } // --plot_t::setXAxisName()
85
86 void plot_t::setYAxisName(QString& inNameY) {
87     YAxisName = inNameY;
88 } // --plot_t::setYAxisName()
89
90 /** calculate and make appropriate and human-oriented range increment for Y Axis:
91  *  1, 2, or 5 times a power of 10
92  *  such there are usually about 10 to 20 marks on the axis
93  *  this calibration can be fudged to about halve or double the number of notches
94  */
95 void plot_t::makeNotchesOnYAxis(QPainter& P) {
96     double rmin = this->rMinY; // minimum as a real
97     double rmax = this->rMaxY; // maximum as a real
98     double range = rmax - rmin; // range: real
99     int xPosOfYAxis = xwmin;
100     // this code is for calculating the values at which we make notches on the axes
101     double logbase10ofrange = log(range) / log(10);
102     int characteristic = static_cast<int> (floor(logbase10ofrange));
103     double mantissa = logbase10ofrange - characteristic;
104     double log2 = log(2) / log(10);
105     double log5 = log(5) / log(10);
106     int multiplierTimes10;
107     /* the whole idea is to partition numbers like a partition of frequencies \in |R+
108      *     by musical note-based half-open intervals, e.g.: { [A,C#) , [C#,F) , [F,A) }
109      * we partition all ranges \in |R+ into three representatives: {1,2,5}.
110      * so we work on the mantissa of the range's log base 10, by definition \in [0,1).
111      * unlike the base 12-partitioned base 2 musical octaves, we throw away and add back in powers of 10,
112      *     which the snark14Display user tribe feels comfortable with.
113      *          ( we really tripartition each interval [r, 10r) where r \in |R+ )
114      * since our representatives are {1, 2, 5}.10^n (does not form a perfect geometric sequence when sorted),
115      *     we partition [0,1) into { [0,log2) , [log2,log5) , [log5, 1) } => represented as {1,2,5}
116      * rather than using the perfectly even tri-partition of notes shown above.
117      * say range is 763, then the mantissa will be log(7.63), which falls in the third partition,
118      * so all intervals are 50 (5 times the appropriate power of 10 remembered by characteristic)
119      * and range/notchinterval = 763/50 is indeed in [10,20).  get either 15 or 16 notches, depending on extrema.
120      * in the case of something like range = 4.9, we have 0.2 be the representative: 24 or 25 notches.
121      *   (that is the absolute maximum of the number of notches.)
122      */
123     if (mantissa < log2) {
124         multiplierTimes10 = 1;
125     } else if (mantissa < log5) {
126         multiplierTimes10 = 2;
127     } else {
128         multiplierTimes10 = 5;
129     }
130     //  double range_inc = multiplier * pow(10, characteristic);
131     double range_inc = multiplierTimes10 * pow(10, characteristic - 1);
132     //    double from = range_inc * ceil(rmin/range_inc);
133     // handle "weird zero" (bug #114) this way...
134     int from_idx = static_cast<int> (ceil(rmin / range_inc));
135     // not idiomatic 'C' but it is easier for me to think about inclusive bounds
136     //    double to   = range_inc * floor(rmax/range_inc);
137     int to_idx = static_cast<int> (floor(rmax / range_inc));
138     //    int num_intervals = static_cast<int>(floor((to-from)/range_inc));
139     P.setPen(Qt::black);
140     QFont f("times", 12);
141     P.setFont(f);
142     //    for(int notch = 0; notch <= num_intervals; notch++) {
143     for (int idx = from_idx; idx <= to_idx; idx++) {
144         double markreal = idx * range_inc; // if idx==0, markreal will be 0.0
145         int markwinY = static_cast<int> (floor(ywmax - (markreal - rMinY) * yscalar)); // mark position in window
146         P.drawLine(xPosOfYAxis - 6, markwinY, xPosOfYAxis + 1, markwinY);
147         QString labelToDraw;
148         labelToDraw.setNum(markreal);
149         P.drawText(xPosOfYAxis - sd_line_t::YAXISNOTCHLABELOFFSET, markwinY, labelToDraw);
150     }
151 } // --plot_t::makeNotchesOnYAxis()
152
153 /** just like the above, slightly trickier since we only want integers
154  */
155 void plot_t::makeNotchesOnXAxis(QPainter& P) {
156     int& imin = (this->rMinX);
157     int& imax = (this->rMaxX);
158     int yPosOfXAxis = ywmax;
159     double rmin = static_cast<double> (imin); // minimum as a real
160     double rmax = static_cast<double> (imax); // maximum as a real
161     double range = rmax - rmin; // range: real
162     // this code is for calculating the values at which we make notches on the axes
163     double logbase10ofrange = log(range) / log(10);
164     int characteristic = static_cast<int> (floor(logbase10ofrange));
165     double mantissa = logbase10ofrange - characteristic;
166     double log2 = log(2) / log(10);
167     double log5 = log(5) / log(10);
168     int multiplierTimes10; // i like using integral types.
169     // the logic for this whole operation is the same as above,
170     // save some measures taken to ensure that the interval as well as notches are always integers.
171     if (mantissa < log2) {
172         multiplierTimes10 = 1;
173     } else if (mantissa < log5) {
174         multiplierTimes10 = 2;
175     } else {
176         multiplierTimes10 = 5;
177     }
178     // the following line really means: // double range_inc = multiplier * pow(10, characteristic);
179     double range_inc = multiplierTimes10 * pow(10, characteristic - 1);
180     if (range_inc <= 1) range_inc = 1.0; // x-axis should not have fractional labels (>1 guarantees it isn't)
181     int range_inc_int = static_cast<int> (floor(range_inc));
182     if (range_inc_int <= 0) range_inc_int = 1; // paranoia: don't want loop increment to be 0
183     int from_int = static_cast<int> (floor(range_inc * ceil(rmin / range_inc)));
184     // not idiomatic 'C' but it is easier for me to think about inclusive bounds
185     int to = static_cast<int> (floor(range_inc * floor(rmax / range_inc)));
186     P.setPen(Qt::black); // darkish blue notches and axis labels [as was in Bruno's code]
187     QFont f("times", 12);
188     P.setFont(f);
189     for (int notch = from_int; notch <= to; notch += range_inc_int) {
190         // safe to increment by integer in for loop
191         int markreal = notch; // really!
192         int markwinX = xwmin + static_cast<int> (floor((markreal - rMinX) * xscalar));
193         P.drawLine(markwinX, yPosOfXAxis - 1, markwinX, yPosOfXAxis + 6);
194         QString labelToDraw;
195         labelToDraw.setNum(markreal);
196         P.drawText(markwinX - 5, yPosOfXAxis + 19, labelToDraw);
197     }
198 } // --plot_t::makeNotchesOnXAxis()
199
200 //    void plot_t::labelTheXAxis() {
201 //    } // --plot_t::labelTheXAxis()
202 //    void plot_t::labelTheYAxis() {
203 //    } // --plot_t::labelTheYAxis()
204
205 /** plot the plot_t object */
206 void plot_t::plot(QPainter& P) {
207   static const int numCanned = 15;
208   static QColor cannedColors[numCanned] = {
209     Qt::red,          //  0
210     Qt::green,
211     Qt::blue,
212     Qt::cyan,
213     Qt::magenta,
214     Qt::yellow,       //  5
215     Qt::gray,
216     Qt::darkRed,
217     Qt::darkGreen,
218     Qt::darkBlue,
219     Qt::darkCyan,     // 10
220     Qt::darkMagenta,
221     Qt::darkYellow,
222     Qt::black,
223     Qt::lightGray     // 14
224   };
225   static const int numPenStyles = 5;
226   static Qt::PenStyle  cannedPenStyles[numPenStyles] = {
227     Qt::SolidLine,
228     Qt::SolidLine,
229     Qt::SolidLine,
230     Qt::SolidLine,
231     Qt::SolidLine
232   };
233   int RectWidth = 16;  // swatch rectangles' (for algo color/name association) dimensions
234   int RectHeight = 16;
235   int xPadForRectCtrs = RectWidth;
236   int yPadForRectCtrs = RectHeight;
237   int xCtrDistance = myWidth/3;
238   int yCtrDistance = ywmin/4; // so it has to be that ywmin > 4*RectHeight
239   // for drawing legend-swatches: 2x3 on top, 3x3 at bottom, total of 15
240   static point_window_t RectCtrPositions[numCanned] = {
241     // swatches above the plot
242     {xPadForRectCtrs + 0 * xCtrDistance,  yPadForRectCtrs + 0 * yCtrDistance},
243     {xPadForRectCtrs + 1 * xCtrDistance,  yPadForRectCtrs + 0 * yCtrDistance},
244     {xPadForRectCtrs + 2 * xCtrDistance,  yPadForRectCtrs + 0 * yCtrDistance},
245     {xPadForRectCtrs + 0 * xCtrDistance,  yPadForRectCtrs + 1 * yCtrDistance},
246     {xPadForRectCtrs + 1 * xCtrDistance,  yPadForRectCtrs + 1 * yCtrDistance},
247     {xPadForRectCtrs + 2 * xCtrDistance,  yPadForRectCtrs + 1 * yCtrDistance},
248     // leave room above the plot for plot name... and swatches below the plot
249     {xPadForRectCtrs + 0 * xCtrDistance, myHeight - yPadForRectCtrs - 0 * yCtrDistance},
250     {xPadForRectCtrs + 1 * xCtrDistance, myHeight - yPadForRectCtrs - 0 * yCtrDistance},
251     {xPadForRectCtrs + 2 * xCtrDistance, myHeight - yPadForRectCtrs - 0 * yCtrDistance},
252     {xPadForRectCtrs + 0 * xCtrDistance, myHeight - yPadForRectCtrs - 1 * yCtrDistance},
253     {xPadForRectCtrs + 1 * xCtrDistance, myHeight - yPadForRectCtrs - 1 * yCtrDistance},
254     {xPadForRectCtrs + 2 * xCtrDistance, myHeight - yPadForRectCtrs - 1 * yCtrDistance},
255     // really shouldn't plot this many... these could clash a bit with x axis label.
256     {xPadForRectCtrs + 0 * xCtrDistance, myHeight - yPadForRectCtrs - 2 * yCtrDistance},
257     {xPadForRectCtrs + 1 * xCtrDistance, myHeight - yPadForRectCtrs - 2 * yCtrDistance},
258     {xPadForRectCtrs + 2 * xCtrDistance, myHeight - yPadForRectCtrs - 2 * yCtrDistance}
259   };
260   int colorInd = 0; // don't want this to be static!
261   int penStyleInd = 0;
262   for(data_manyexecs_t::iterator it = data.begin(); it != data.end(); it++) {
263     QColor& myColor = cannedColors[ (colorInd % numCanned) ];
264     Qt::PenStyle myPenStyle = cannedPenStyles[ penStyleInd % numPenStyles ];
265     QPen pen = P.pen();
266     //pen.setStyle(myPenStyle);
267     pen.setWidth(5);
268     P.setPen(pen);
269
270     it->plot(P, myColor, myPenStyle);
271     if(colorInd < numCanned) {
272       int RectCtrX = RectCtrPositions[colorInd].x;
273       int RectCtrY = RectCtrPositions[colorInd].y;
274       // fill in rectangle with corresponding color (same as the line just plotted)
275       P.fillRect(RectCtrX - RectWidth/2, RectCtrY - RectHeight/2, RectWidth, RectHeight, myColor);
276       P.setPen(Qt::black); // then draw the boundary
277       P.drawRect(RectCtrX - RectWidth/2, RectCtrY - RectHeight/2, RectWidth, RectHeight);
278       QString algName = (it->getName());
279       int nameLengthInChars = 40; // max
280       QFont savedFont = P.font();
281       QFont f("Times", 11);
282       P.setFont(f);
283       P.drawText(RectCtrX + RectWidth*3/4, RectCtrY+5, algName, nameLengthInChars);
284       P.setFont(savedFont);
285     } else {
286       // perhaps generate some kind of warning about too many plots,
287       // not enough colors being used / not enough space for swatches etc
288     }
289     // no one in their right mind would run 16 different execs (?), but just covering our base (no segfault).
290     // here could also be where label associating color with variable name is plotted...
291     colorInd++;
292     penStyleInd++;
293   }
294   sd_line_t::drawBoundary(P); // by doing this after, we make sure the frame is not painted over
295   makeNotchesOnXAxis(P);
296   makeNotchesOnYAxis(P);
297   // now label the plot just above the plot window
298   QFont F = P.font();
299   QFont FBold = F;
300   FBold.setBold(true);
301   P.setFont(FBold);
302   P.setPen(Qt::black);
303   P.drawText(xwmin, ywmin-15, plotName);
304   P.setFont(F); // restore original font to QPainter
305   // label the X axis
306   P.drawText( (sd_line_t::WIDTH - sd_line_t::XWINPAD)/2, ywmax+45, XAxisName);
307   // label the Y axis (sideways)
308   P.rotate(-90);
309   P.drawText( -(sd_line_t::HEIGHT)/2, sd_line_t::XWINPAD - sd_line_t::YAXISLABELOFFSET, YAxisName);
310   P.rotate(90); // restore orientation of painter
311 } // --plot_t::plot()
312
313 /** plot the plot_t object in Grayscale*/
314 // jk 1/13/2009 adding grayScale option for graphs
315 void plot_t::plotGray(QPainter& P) {
316
317     static const int numCanned = 9;
318     static QColor cannedColors[numCanned] = {
319         Qt::gray,
320         Qt::black,
321         Qt::lightGray,
322         Qt::gray,
323         Qt::black,
324         Qt::lightGray,
325         Qt::gray,
326         Qt::black,
327         Qt::lightGray,
328     };
329     static const int numPenStyles = 9;
330     static Qt::PenStyle cannedPenStyles[numPenStyles] = {
331         Qt::SolidLine,
332         Qt::SolidLine,
333         Qt::SolidLine,
334         Qt::DotLine,
335         Qt::DotLine,
336         Qt::DotLine,
337         Qt::DashLine,
338         Qt::DashLine,
339         Qt::DashLine
340         //Qt::DashDotLine,
341         //Qt::DashDotDotLine,
342     };
343     int RectWidth = 16; // swatch rectangles' (for algo color/name association) dimensions
344     int RectHeight = 16;
345     int xPadForRectCtrs = RectWidth;
346     int yPadForRectCtrs = RectHeight;
347     int xCtrDistance = myWidth / 3;
348     int yCtrDistance = ywmin / 4; // so it has to be that ywmin > 4*RectHeight
349     // for drawing legend-swatches: 2x3 on top, 3x3 at bottom, total of 15
350     static point_window_t RectCtrPositions[numCanned] = {
351         // swatches above the plot
352         {xPadForRectCtrs + 0 * xCtrDistance, yPadForRectCtrs + 0 * yCtrDistance},
353         {xPadForRectCtrs + 1 * xCtrDistance, yPadForRectCtrs + 0 * yCtrDistance},
354         {xPadForRectCtrs + 2 * xCtrDistance, yPadForRectCtrs + 0 * yCtrDistance},
355         {xPadForRectCtrs + 0 * xCtrDistance, yPadForRectCtrs + 1 * yCtrDistance},
356         {xPadForRectCtrs + 1 * xCtrDistance, yPadForRectCtrs + 1 * yCtrDistance},
357         {xPadForRectCtrs + 2 * xCtrDistance, yPadForRectCtrs + 1 * yCtrDistance},
358         // leave room above the plot for plot name... and swatches below the plot
359         {xPadForRectCtrs + 0 * xCtrDistance, myHeight - yPadForRectCtrs - 0 * yCtrDistance},
360         {xPadForRectCtrs + 1 * xCtrDistance, myHeight - yPadForRectCtrs - 0 * yCtrDistance},
361         {xPadForRectCtrs + 2 * xCtrDistance, myHeight - yPadForRectCtrs - 0 * yCtrDistance}
362         //    {xPadForRectCtrs + 0 * xCtrDistance, myHeight - yPadForRectCtrs - 1 * yCtrDistance},
363         //    {xPadForRectCtrs + 1 * xCtrDistance, myHeight - yPadForRectCtrs - 1 * yCtrDistance},
364         //    {xPadForRectCtrs + 2 * xCtrDistance, myHeight - yPadForRectCtrs - 1 * yCtrDistance},
365         //    // really shouldn't plot this many... these could clash a bit with x axis label.
366         //    {xPadForRectCtrs + 0 * xCtrDistance, myHeight - yPadForRectCtrs - 2 * yCtrDistance},
367         //    {xPadForRectCtrs + 1 * xCtrDistance, myHeight - yPadForRectCtrs - 2 * yCtrDistance},
368         //    {xPadForRectCtrs + 2 * xCtrDistance, myHeight - yPadForRectCtrs - 2 * yCtrDistance}
369     };
370     int colorInd = 0; // don't want this to be static!
371     int penStyleInd = 0;
372     for (data_manyexecs_t::iterator it = data.begin(); it != data.end(); it++) {
373         QColor& myColor = cannedColors[ (colorInd % numCanned) ];
374         Qt::PenStyle myPenStyle = cannedPenStyles[ penStyleInd % numPenStyles ];
375
376         it->plot(P, myColor, myPenStyle);
377         if (colorInd < numCanned) {
378             int RectCtrX = RectCtrPositions[colorInd].x;
379             int RectCtrY = RectCtrPositions[colorInd].y;
380             // fill in rectangle with corresponding color (same as the line just plotted)
381             //P.fillRect(RectCtrX - RectWidth/2, RectCtrY - RectHeight/2, RectWidth, RectHeight, myColor);
382             QPen savedPen = P.pen();
383             QPen pen; // = P.pen();
384             pen.setStyle(myPenStyle);
385             pen.setWidth(2);
386             pen.setColor(myColor);
387             P.setPen(pen);
388
389             //P.setPen(myColor); // then draw the boundary
390             //P.setPen(myPenStyle);
391             //P.drawRect(RectCtrX - RectWidth/2, RectCtrY - RectHeight/2, RectWidth, RectHeight);
392             P.drawLine(RectCtrX - RectWidth / 2, RectCtrY, RectCtrX + RectWidth / 2, RectCtrY);
393             //change pen color and style back to black solid for printing the text
394             //P.setPen( savedPen );
395             pen.setStyle(Qt::SolidLine);
396             pen.setWidth(1);
397             pen.setColor(Qt::black);
398             P.setPen(pen);
399
400             QString algName = (it->getName());
401             int nameLengthInChars = 40; // max
402             QFont savedFont = P.font();
403             QFont f("Times", 11);
404             P.setFont(f);
405             P.drawText(RectCtrX + RectWidth * 3 / 4, RectCtrY + 5, algName, nameLengthInChars);
406             P.setFont(savedFont);
407
408         } else {
409             // perhaps generate some kind of warning about too many plots,
410             // not enough colors being used / not enough space for swatches etc
411         }
412         // no one in their right mind would run 16 different execs (?), but just covering our base (no segfault).
413         // here could also be where label associating color with variable name is plotted...
414         colorInd++;
415         penStyleInd++;
416     }
417     sd_line_t::drawBoundary(P); // by doing this after, we make sure the frame is not painted over
418     makeNotchesOnXAxis(P);
419     makeNotchesOnYAxis(P);
420     // now label the plot just above the plot window
421     QFont F = P.font();
422     QFont FBold = F;
423     FBold.setBold(true);
424     P.setFont(FBold);
425     P.setPen(Qt::black);
426     P.drawText(xwmin, ywmin - 15, plotName);
427     P.setFont(F); // restore original font to QPainter
428     // label the X axis
429     P.drawText((sd_line_t::WIDTH - sd_line_t::XWINPAD) / 2, ywmax + 45, XAxisName);
430     // label the Y axis (sideways)
431     P.rotate(-90);
432     P.drawText(-(sd_line_t::HEIGHT) / 2, sd_line_t::XWINPAD - sd_line_t::YAXISLABELOFFSET, YAxisName);
433     P.rotate(90); // restore orientation of painter
434 } // --plot_t::plot()
435