1 // $Id: PixelatedGraph.cc 5748 2014-10-11 19:38:53Z flaterco $
2 
3 /*  PixelatedGraph  Graphs that do not use scalable vectors.
4 
5     Copyright (C) 2010  David Flater.
6 
7     This program is free software: you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation, either version 3 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include "libxtide.hh"
22 #include "Graph.hh"
23 #include "PixelatedGraph.hh"
24 
25 namespace libxtide {
26 
27 
28 // In drawing of line graphs, slope at which to abandon the thick line
29 // drawing algorithm.
30 static const double slopeLimit (5.0);
31 
32 
PixelatedGraph(unsigned xSize,unsigned ySize,GraphStyle style)33 PixelatedGraph::PixelatedGraph (unsigned xSize, unsigned ySize,
34 				GraphStyle style):
35   Graph (xSize, ySize, style) {
36 }
37 
38 
setPixel(int x,int y,Colors::Colorchoice c,double opacity)39 void PixelatedGraph::setPixel (int x, int y,
40 			       Colors::Colorchoice c, double opacity) {
41   assert (c < (int)Colors::numColors);
42   if (opacity >= 0.5)
43     setPixel (x, y, c);
44 }
45 
46 
drawVerticalLineP(int x,int y1,int y2,Colors::Colorchoice c,double opacity)47 void PixelatedGraph::drawVerticalLineP (int x, int y1, int y2,
48 					Colors::Colorchoice c,
49 					double opacity) {
50   int ylo, yhi;
51   if (y1 < y2) {
52     ylo = y1; yhi = y2;
53   } else {
54     ylo = y2; yhi = y1;
55   }
56   if (opacity == 1)
57     for (int i=ylo; i<=yhi; ++i)
58       setPixel (x, i, c);
59   else
60     for (int i=ylo; i<=yhi; ++i)
61       setPixel (x, i, c, opacity);
62 }
63 
64 
drawVerticalLinePxSy(int x,double y1,double y2,Colors::Colorchoice c,double opacity)65 void PixelatedGraph::drawVerticalLinePxSy (int x, double y1, double y2,
66 					   Colors::Colorchoice c,
67 					   double opacity) {
68   double ylo, yhi;
69   if (y1 < y2) {
70     ylo = y1; yhi = y2;
71   } else {
72     ylo = y2; yhi = y1;
73   }
74   int ylo2 ((int) ceil (ylo));
75   int yhi2 ((int) floor (yhi));
76   if (ylo2 < yhi2)
77     drawVerticalLineP (x, ylo2, yhi2-1, c, opacity);
78   // What if they both fall within the same pixel:  ylo2 > yhi2
79   if (ylo2 > yhi2) {
80     assert (yhi2 == ylo2 - 1);
81     setPixel (x, yhi2, c, opacity * (yhi - ylo));
82   } else {
83     // The normal case.
84     if (ylo < ylo2)
85       setPixel (x, ylo2-1, c, opacity * (ylo2 - ylo));
86     if (yhi > yhi2)
87       setPixel (x, yhi2, c, opacity * (yhi - yhi2));
88   }
89 }
90 
91 
drawVerticalLineS(double x,double y1,double y2,Colors::Colorchoice c)92 void PixelatedGraph::drawVerticalLineS (double x, double y1, double y2,
93 					Colors::Colorchoice c) {
94   drawVerticalLinePxSy (Global::ifloor (x), y1, y2, c);
95 }
96 
97 
drawFunkyLine(double prevytide,double ytide,double nextytide,int x,Colors::Colorchoice c)98 void PixelatedGraph::drawFunkyLine (double prevytide,
99 				    double ytide,
100 				    double nextytide,
101 				    int x,
102 				    Colors::Colorchoice c) {
103   double dy, yleft, yright;
104   double slw (Global::settings["lw"].d);
105 
106   // The fix for line slope breaks down when the slope gets nasty, so
107   // switch to a more conservative strategy when that happens.  Line
108   // width becomes 1 no matter what.
109 
110 #define dohalfline(yy) {                                          \
111   double lw;                                                      \
112   if (fabs(dy) < slopeLimit)                                      \
113     lw = (1.0 + (M_SQRT2 - 1.0) * fabs(dy)) * slw / 2.0;          \
114   else                                                            \
115     lw = (fabs(dy) + slw) / 2.0;                                  \
116   if (dy < 0.0)                                                   \
117     lw = -lw;                                                     \
118   yy = ytide - lw;                                                \
119 }
120 
121   dy = ytide - prevytide;
122   dohalfline (yleft);
123   dy = ytide - nextytide;
124   dohalfline (yright);
125 
126   // Fix degenerate cases.
127   if (ytide > yleft && ytide > yright) {
128     if (yleft > yright)
129       yleft = ytide + slw / 2.0;
130     else
131       yright = ytide + slw / 2.0;
132   } else if (ytide < yleft && ytide < yright) {
133     if (yleft < yright)
134       yleft = ytide - slw / 2.0;
135     else
136       yright = ytide - slw / 2.0;
137   }
138   drawVerticalLinePxSy (x, yleft, yright, c);
139 }
140 
141 
drawX(double x,double y)142 void PixelatedGraph::drawX (double x, double y) {
143   int ix = Global::ifloor (x);
144   int iy = Global::ifloor (y);
145   drawVerticalLineP   (ix, iy-4, iy+4, Colors::foreground);
146   drawHorizontalLineP (ix-4, ix+4, iy, Colors::foreground);
147 }
148 
149 
drawLevels(const SafeVector<double> & val,const SafeVector<double> & y,double yzulu,bool isCurrent,const SafeVector<BlendBlob> & blendBlobs)150 void PixelatedGraph::drawLevels (const SafeVector<double> &val,
151 				 const SafeVector<double> &y,
152 				 double yzulu,
153 				 bool isCurrent
154 #ifdef blendingTest
155 				 , const SafeVector<BlendBlob> &blendBlobs
156 #endif
157 				 ) {
158   const char gs (Global::settings["gs"].c);
159   const double opacity (gs == 's' ? Global::settings["to"].d : 1.0);
160 
161   // Harmonize this with the quantized y coordinate of the 0 kt line to avoid
162   // anomalies like a gap between the flood curve and the line.
163   yzulu = Global::ifloor(yzulu);
164 
165   for (int x=0; x<(int)_xSize; ++x) {
166 
167     // Coloration is determined from the predicted heights, not from
168     // the eventTypes of surrounding tide events.  Ideally the two
169     // would never disagree, but for pathological sub stations they
170     // can.
171     if (isCurrent) {
172       Colors::Colorchoice c = (val[x+1] > 0.0 ? Colors::flood : Colors::ebb);
173       switch (gs) {
174       case 'd':
175         drawVerticalLinePxSy (x, yzulu, y[x+1], c, opacity);
176 	break;
177       case 'l':
178         drawFunkyLine (y[x], y[x+1], y[x+2], x, c);
179 	break;
180       case 's':
181         drawVerticalLinePxSy (x, yzulu, y[x+1], c, opacity);
182         drawFunkyLine (y[x], y[x+1], y[x+2], x, Colors::foreground);
183 	break;
184       default:
185         assert (false);
186       }
187     } else {
188       Colors::Colorchoice c = (val[x] < val[x+1] ? Colors::flood : Colors::ebb);
189       switch (gs) {
190       case 'd':
191         drawVerticalLinePxSy (x, _ySize, y[x+1], c, opacity);
192 	break;
193       case 'l':
194         drawFunkyLine (y[x], y[x+1], y[x+2], x, c);
195 	break;
196       case 's':
197         drawVerticalLinePxSy (x, _ySize, y[x+1], c, opacity);
198         drawFunkyLine (y[x], y[x+1], y[x+2], x, Colors::foreground);
199 	break;
200       default:
201         assert (false);
202       }
203     }
204 
205 #ifdef blendingTest
206     if (!blendBlobs[x+1].isNull) {
207       setPixel (x, (int)blendBlobs[x+1].firsty, Colors::mark);
208       setPixel (x, (int)blendBlobs[x+1].secondy, Colors::msl);
209     }
210 #endif
211   }
212 }
213 
214 
drawBoxS(double x1,double x2,double y1,double y2,Colors::Colorchoice c)215 void PixelatedGraph::drawBoxS (double x1, double x2, double y1, double y2,
216 Colors::Colorchoice c) {
217   int ix1 (Global::ifloor (x1)), ix2 (Global::ifloor (x2));
218   if (ix1 > ix2)
219     std::swap (ix1, ix2);
220   // Exclude upper limit so adjacent boxes don't overlap.
221   for (int x=ix1; x<ix2; ++x)
222     drawVerticalLinePxSy (x, y1, y2, c);
223 }
224 
225 
drawHorizontalLinePxSy(int xlo,int xhi,double y,Colors::Colorchoice c)226 void PixelatedGraph::drawHorizontalLinePxSy (int xlo, int xhi, double y,
227 					     Colors::Colorchoice c) {
228   drawHorizontalLineP (xlo, xhi, Global::ifloor(y), c);
229 }
230 
231 
drawHorizontalLineP(int xlo,int xhi,int y,Colors::Colorchoice c)232 void PixelatedGraph::drawHorizontalLineP (int xlo, int xhi, int y,
233 					  Colors::Colorchoice c) {
234   for (int i=xlo; i<=xhi; ++i)
235     setPixel (i, y, c);
236 }
237 
238 
239 // x quantization has to happen first to avoid an off-by-one error.  It is
240 // most obvious in TTYGraph where the effective font size is 1 pixel.  For
241 // example,
242 //   Let x = 1.1
243 //   Hour tick gets drawn at floor(1.1) = column 1
244 //   Label of length 1 gets drawn at floor(1.1 - 0.5) = column 0
245 // So you get
246 //   8
247 //    |
248 // Duh.
centerStringSxPy(double x,int y,const Dstr & s)249 void PixelatedGraph::centerStringSxPy (double x, int y, const Dstr &s) {
250   // int cast needed to prevent surprising promotion to unsigned
251   drawStringP (Global::ifloor(x)-(int)stringWidth(s)/2, y, s);
252 }
253 
254 
rightJustifyStringS(double x,double y,const Dstr & s)255 void PixelatedGraph::rightJustifyStringS (double x, double y, const Dstr &s) {
256   drawStringP (Global::ifloor(x-stringWidth(s)), Global::ifloor(y), s);
257 }
258 
259 }
260