4 XCSoar Glide Computer - http://www.xcsoar.org/
5 Copyright (C) 2000-2013 The XCSoar Project
6 A detailed list of copyright holders can be found in the file "AUTHORS".
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #include "ChartRenderer.hpp"
25 #include "Screen/Canvas.hpp"
26 #include "Screen/Layout.hpp"
27 #include "Language/Language.hpp"
29 #include "Math/LeastSquares.hpp"
30 #include "Util/StaticString.hpp"
34 #include <windef.h> /* for MAX_PATH */
37 ChartRenderer::Axis::Reset()
46 ChartRenderer::Axis::ToScreen(fixed value
) const
48 return (long)((value
- min
) * scale
);
52 ChartRenderer::ResetScale()
58 ChartRenderer::ChartRenderer(const ChartLook
&_look
, Canvas
&the_canvas
,
59 const PixelRect the_rc
)
60 :look(_look
), canvas(the_canvas
), rc(the_rc
),
61 padding_left(24), padding_bottom(19)
67 ChartRenderer::ScaleYFromData(const LeastSquares
&lsdata
)
77 y
.min
= std::min(y
.min
, lsdata
.y_min
);
78 y
.max
= std::max(y
.max
, lsdata
.y_max
);
81 if (lsdata
.sum_n
> 1) {
83 y0
= lsdata
.x_min
* lsdata
.m
+ lsdata
.b
;
84 y1
= lsdata
.x_max
* lsdata
.m
+ lsdata
.b
;
85 y
.min
= std::min({y
.min
, y0
, y1
});
86 y
.max
= std::max({y
.max
, y0
, y1
});
89 if (fabs(y
.max
- y
.min
) > fixed(50)) {
90 y
.scale
= (y
.max
- y
.min
);
91 if (positive(y
.scale
))
92 y
.scale
= fixed(rc
.bottom
- rc
.top
- padding_bottom
) / y
.scale
;
94 y
.scale
= fixed(2000);
99 ChartRenderer::ScaleXFromData(const LeastSquares
&lsdata
)
101 if (lsdata
.IsEmpty())
105 x
.min
= lsdata
.x_min
;
106 x
.max
= lsdata
.x_max
;
109 x
.min
= std::min(x
.min
, lsdata
.x_min
);
110 x
.max
= std::max(x
.max
, lsdata
.x_max
);
113 x
.scale
= (x
.max
- x
.min
);
114 if (positive(x
.scale
))
115 x
.scale
= fixed(rc
.right
- rc
.left
- padding_left
) / x
.scale
;
119 ChartRenderer::ScaleYFromValue(const fixed value
)
126 y
.min
= std::min(value
, y
.min
);
127 y
.max
= std::max(value
, y
.max
);
130 y
.scale
= (y
.max
- y
.min
);
131 if (positive(y
.scale
))
132 y
.scale
= fixed(rc
.bottom
- rc
.top
- padding_bottom
) / y
.scale
;
136 ChartRenderer::ScaleXFromValue(const fixed value
)
143 x
.min
= std::min(value
, x
.min
);
144 x
.max
= std::max(value
, x
.max
);
147 x
.scale
= (x
.max
- x
.min
);
148 if (positive(x
.scale
))
149 x
.scale
= fixed(rc
.right
- rc
.left
- padding_left
) / x
.scale
;
153 ChartRenderer::DrawLabel(const TCHAR
*text
, const fixed xv
, const fixed yv
)
155 canvas
.Select(look
.label_font
);
156 canvas
.SetBackgroundTransparent();
158 PixelSize tsize
= canvas
.CalcTextSize(text
);
159 RasterPoint pt
= ToScreen(xv
, yv
);
160 canvas
.DrawText(pt
.x
- tsize
.cx
/ 2, pt
.y
- tsize
.cy
/ 2, text
);
164 ChartRenderer::DrawNoData()
166 canvas
.Select(look
.label_font
);
167 canvas
.SetBackgroundTransparent();
169 const TCHAR
*text
= _("No data");
170 PixelSize tsize
= canvas
.CalcTextSize(text
);
172 PixelScalar x
= (rc
.left
+ rc
.right
- tsize
.cx
) / 2;
173 PixelScalar y
= (rc
.top
+ rc
.bottom
- tsize
.cy
) / 2;
175 canvas
.DrawText(x
, y
, text
);
179 ChartRenderer::DrawXLabel(const TCHAR
*text
)
181 canvas
.Select(look
.axis_label_font
);
182 canvas
.SetBackgroundTransparent();
184 PixelSize tsize
= canvas
.CalcTextSize(text
);
185 PixelScalar x
= rc
.right
- tsize
.cx
- Layout::Scale(3);
186 PixelScalar y
= rc
.bottom
- tsize
.cy
;
188 canvas
.DrawText(x
, y
, text
);
192 ChartRenderer::DrawXLabel(const TCHAR
*text
, const TCHAR
*unit
)
194 assert(text
!= NULL
);
195 assert(unit
!= NULL
);
197 StaticString
<64> buffer
;
198 buffer
.UnsafeFormat(_T("%s [%s]"), text
, unit
);
203 ChartRenderer::DrawYLabel(const TCHAR
*text
)
205 canvas
.Select(look
.axis_label_font
);
206 canvas
.SetBackgroundTransparent();
208 PixelSize tsize
= canvas
.CalcTextSize(text
);
209 PixelScalar x
= std::max(PixelScalar(2), PixelScalar(rc
.left
- tsize
.cx
));
210 PixelScalar y
= rc
.top
;
212 canvas
.DrawText(x
, y
, text
);
216 ChartRenderer::DrawYLabel(const TCHAR
*text
, const TCHAR
*unit
)
218 assert(text
!= NULL
);
219 assert(unit
!= NULL
);
221 StaticString
<64> buffer
;
222 buffer
.UnsafeFormat(_T("%s [%s]"), text
, unit
);
227 ChartRenderer::DrawTrend(const LeastSquares
&lsdata
, ChartLook::Style style
)
229 if (lsdata
.sum_n
< 2)
232 if (x
.unscaled
|| y
.unscaled
)
235 fixed xmin
, xmax
, ymin
, ymax
;
238 ymin
= lsdata
.x_min
* lsdata
.m
+ lsdata
.b
;
239 ymax
= lsdata
.x_max
* lsdata
.m
+ lsdata
.b
;
241 DrawLine(xmin
, ymin
, xmax
, ymax
, look
.GetPen(style
));
245 ChartRenderer::DrawTrendN(const LeastSquares
&lsdata
, ChartLook::Style style
)
247 if (lsdata
.sum_n
< 2)
250 if (x
.unscaled
|| y
.unscaled
)
253 fixed xmin
, xmax
, ymin
, ymax
;
255 xmax
= fixed(lsdata
.sum_n
) + fixed(0.5);
256 ymin
= lsdata
.x_min
* lsdata
.m
+ lsdata
.b
;
257 ymax
= lsdata
.x_max
* lsdata
.m
+ lsdata
.b
;
259 DrawLine(xmin
, ymin
, xmax
, ymax
, look
.GetPen(style
));
263 ChartRenderer::DrawLine(const fixed xmin
, const fixed ymin
,
264 const fixed xmax
, const fixed ymax
, const Pen
&pen
)
266 if (x
.unscaled
|| y
.unscaled
)
269 assert(pen
.IsDefined());
271 canvas
.DrawLine(ToScreen(xmin
, ymin
), ToScreen(xmax
, ymax
));
275 ChartRenderer::DrawFilledLine(const fixed xmin
, const fixed ymin
,
276 const fixed xmax
, const fixed ymax
,
281 line
[0] = ToScreen(xmin
, ymin
);
282 line
[1] = ToScreen(xmax
, ymax
);
284 line
[2].x
= line
[1].x
;
285 line
[2].y
= ScreenY(fixed(0));
286 line
[3].x
= line
[0].x
;
287 line
[3].y
= line
[2].y
;
289 canvas
.Select(brush
);
290 canvas
.SelectNullPen();
291 canvas
.DrawTriangleFan(line
, 4);
295 ChartRenderer::DrawLine(const fixed xmin
, const fixed ymin
,
296 const fixed xmax
, const fixed ymax
,
297 ChartLook::Style style
)
299 DrawLine(xmin
, ymin
, xmax
, ymax
, look
.GetPen(style
));
303 ChartRenderer::DrawBarChart(const LeastSquares
&lsdata
)
305 if (x
.unscaled
|| y
.unscaled
)
308 canvas
.Select(look
.bar_brush
);
309 canvas
.SelectNullPen();
311 for (unsigned i
= 0, n
= lsdata
.slots
.size(); i
!= n
; i
++) {
312 PixelScalar
xmin((fixed(i
) + fixed(1.2)) * x
.scale
313 + fixed(rc
.left
+ padding_left
));
314 PixelScalar ymin
= ScreenY(y
.min
);
315 PixelScalar
xmax((fixed(i
) + fixed(1.8)) * x
.scale
316 + fixed(rc
.left
+ padding_left
));
317 PixelScalar ymax
= ScreenY(lsdata
.slots
[i
].y
);
318 canvas
.Rectangle(xmin
, ymin
, xmax
, ymax
);
323 ChartRenderer::DrawFilledLineGraph(const LeastSquares
&lsdata
)
325 assert(lsdata
.slots
.size() >= 2);
327 const unsigned n
= lsdata
.slots
.size() + 2;
328 RasterPoint
*points
= point_buffer
.get(n
);
330 RasterPoint
*p
= points
;
331 for (auto i
= lsdata
.slots
.begin(), end
= lsdata
.slots
.end();
333 *p
++ = ToScreen(i
->x
, i
->y
);
334 const RasterPoint
&last
= p
[-1];
335 *p
++ = RasterPoint
{ last
.x
, rc
.bottom
- padding_bottom
};
336 *p
++ = RasterPoint
{ points
[0].x
, rc
.bottom
- padding_bottom
};
338 assert(p
== points
+ n
);
340 canvas
.DrawPolygon(points
, n
);
344 ChartRenderer::DrawLineGraph(const LeastSquares
&lsdata
, const Pen
&pen
)
346 assert(lsdata
.slots
.size() >= 2);
348 const unsigned n
= lsdata
.slots
.size();
349 RasterPoint
*points
= point_buffer
.get(n
);
351 RasterPoint
*p
= points
;
352 for (auto i
= lsdata
.slots
.begin(), end
= lsdata
.slots
.end();
354 *p
++ = ToScreen(i
->x
, i
->y
);
355 assert(p
== points
+ n
);
358 canvas
.DrawPolyline(points
, n
);
362 ChartRenderer::DrawLineGraph(const LeastSquares
&lsdata
,
363 ChartLook::Style style
)
365 DrawLineGraph(lsdata
, look
.GetPen(style
));
369 ChartRenderer::FormatTicText(TCHAR
*text
, const fixed val
, const fixed step
)
371 if (step
< fixed(1)) {
372 _stprintf(text
, _T("%.1f"), (double)val
);
374 _stprintf(text
, _T("%.0f"), (double)val
);
379 ChartRenderer::DrawXGrid(const fixed tic_step
, ChartLook::Style style
,
380 const fixed unit_step
, bool draw_units
)
382 DrawXGrid(tic_step
, look
.GetPen(style
), unit_step
, draw_units
);
386 ChartRenderer::DrawXGrid(fixed tic_step
, const Pen
&pen
,
387 fixed unit_step
, bool draw_units
)
389 assert(positive(tic_step
));
392 canvas
.Select(look
.axis_value_font
);
393 canvas
.SetBackgroundTransparent();
397 /** the minimum next position of the text, to avoid overlapping */
398 PixelScalar next_text
= rc
.left
;
400 /* increase tic step so graph not too crowded */
401 while ((x
.max
-x
.min
)/tic_step
> fixed(10)) {
402 tic_step
*= fixed(2);
403 unit_step
*= fixed(2);
405 // bool do_units = ((x.max-zero)/tic_step)<10;
408 line
[1].y
= rc
.bottom
- padding_bottom
;
410 fixed start
= (int)(x
.min
/ tic_step
) * tic_step
;
412 for (fixed xval
= start
; xval
<= x
.max
; xval
+= tic_step
) {
413 const PixelScalar xmin
= ScreenX(xval
);
414 line
[0].x
= line
[1].x
= xmin
;
416 // STYLE_THINDASHPAPER
417 if (xmin
>= rc
.left
+ padding_left
&& xmin
<= rc
.right
) {
418 canvas
.DrawLine(line
[0], line
[1]);
420 if (draw_units
&& xmin
>= next_text
) {
421 TCHAR unit_text
[MAX_PATH
];
422 FormatTicText(unit_text
, xval
* unit_step
/ tic_step
, unit_step
);
424 canvas
.DrawText(xmin
, rc
.bottom
- Layout::Scale(17), unit_text
);
426 next_text
= xmin
+ canvas
.CalcTextSize(unit_text
).cx
+ Layout::FastScale(2);
433 ChartRenderer::DrawYGrid(const fixed tic_step
, ChartLook::Style style
,
434 const fixed unit_step
, bool draw_units
)
436 DrawYGrid(tic_step
, look
.GetPen(style
), unit_step
, draw_units
);
440 ChartRenderer::DrawYGrid(fixed tic_step
, const Pen
&pen
,
441 fixed unit_step
, bool draw_units
)
443 assert(positive(tic_step
));
446 canvas
.Select(look
.axis_value_font
);
447 canvas
.SetBackgroundTransparent();
451 /* increase tic step so graph not too crowded */
452 while ((y
.max
-y
.min
)/tic_step
> fixed(10)) {
453 tic_step
*= fixed(2);
454 unit_step
*= fixed(2);
457 line
[0].x
= rc
.left
+ padding_left
;
458 line
[1].x
= rc
.right
;
460 fixed start
= (int)(y
.min
/ tic_step
) * tic_step
;
462 for (fixed yval
= start
; yval
<= y
.max
; yval
+= tic_step
) {
463 const PixelScalar ymin
= ScreenY(yval
);
464 line
[0].y
= line
[1].y
= ymin
;
466 // STYLE_THINDASHPAPER
467 if (ymin
>= rc
.top
&& ymin
<= rc
.bottom
- padding_bottom
) {
468 canvas
.DrawLine(line
[0], line
[1]);
471 TCHAR unit_text
[MAX_PATH
];
472 FormatTicText(unit_text
, yval
* unit_step
/ tic_step
, unit_step
);
474 canvas
.DrawText(rc
.left
+ Layout::Scale(8), ymin
, unit_text
);
481 ChartRenderer::ScreenX(fixed _x
) const
483 return rc
.left
+ padding_left
+ x
.ToScreen(_x
);
487 ChartRenderer::ScreenY(fixed _y
) const
489 return rc
.bottom
- padding_bottom
- y
.ToScreen(_y
);
493 ChartRenderer::DrawFilledY(const std::vector
<std::pair
<fixed
, fixed
>> &vals
,
494 const Brush
&brush
, const Pen
* pen
)
498 const unsigned fsize
= vals
.size()+2;
499 RasterPoint
*line
= point_buffer
.get(fsize
);
501 for (unsigned i
= 0; i
< vals
.size(); ++i
)
502 line
[i
+ 2] = ToScreen(vals
[i
].first
, vals
[i
].second
);
504 line
[0].x
= rc
.left
+ padding_left
;
505 line
[0].y
= line
[fsize
-1].y
;
506 line
[1].x
= rc
.left
+ padding_left
;
507 line
[1].y
= line
[2].y
;
509 canvas
.Select(brush
);
511 canvas
.SelectNullPen();
515 canvas
.DrawPolygon(line
, fsize
);
519 ChartRenderer::DrawDot(const fixed x
, const fixed y
, const PixelScalar width
)
521 RasterPoint p
= ToScreen(x
, y
);
522 RasterPoint line
[4] = { { p
.x
, p
.y
- width
},
523 { p
.x
- width
, p
.y
},
524 { p
.x
, p
.y
+ width
},
525 { p
.x
+ width
, p
.y
} };
526 canvas
.SelectNullPen();
527 canvas
.DrawTriangleFan(line
, 4);