1 #include <algorithm>
2 #include <wx/dcbuffer.h>
3 
4 #include "RammingChart.hpp"
5 #include "GUI.hpp"
6 #include "I18N.hpp"
7 
8 wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent);
9 
10 
draw()11 void Chart::draw() {
12     wxAutoBufferedPaintDC dc(this); // unbuffered DC caused flickering on win
13 
14     dc.SetBrush(GetBackgroundColour());
15     dc.SetPen(GetBackgroundColour());
16     dc.DrawRectangle(GetClientRect());  // otherwise the background would end up black on windows
17 
18     dc.SetPen(*wxBLACK_PEN);
19     dc.SetBrush(*wxWHITE_BRUSH);
20     dc.DrawRectangle(m_rect);
21 
22     if (visible_area.m_width < 0.499) {
23         dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-legend_side,m_rect.GetBottom()-m_rect.GetHeight()/2));
24         return;
25     }
26 
27 
28     if (!m_line_to_draw.empty()) {
29         for (unsigned int i=0;i<m_line_to_draw.size()-2;++i) {
30             int color = 510*((m_rect.GetBottom()-(m_line_to_draw)[i])/double(m_rect.GetHeight()));
31             dc.SetPen( wxPen( wxColor(std::min(255,color),255-std::max(color-255,0),0), 1 ) );
32             dc.DrawLine(m_rect.GetLeft()+1+i,(m_line_to_draw)[i],m_rect.GetLeft()+1+i,m_rect.GetBottom());
33         }
34         dc.SetPen( wxPen( wxColor(0,0,0), 1 ) );
35         for (unsigned int i=0;i<m_line_to_draw.size()-2;++i) {
36             if (splines)
37                 dc.DrawLine(m_rect.GetLeft()+i,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i+1]);
38             else {
39                 dc.DrawLine(m_rect.GetLeft()+i,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i]);
40                 dc.DrawLine(m_rect.GetLeft()+i+1,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i+1]);
41             }
42         }
43     }
44 
45     // draw draggable buttons
46     dc.SetBrush(*wxBLUE_BRUSH);
47     dc.SetPen( wxPen( wxColor(0,0,0), 1 ) );
48     for (auto& button : m_buttons)
49         //dc.DrawRectangle(math_to_screen(button.get_pos())-wxPoint(side/2.,side/2.), wxSize(side,side));
50         dc.DrawCircle(math_to_screen(button.get_pos()),side/2.);
51         //dc.DrawRectangle(math_to_screen(button.get_pos()-wxPoint2DDouble(0.125,0))-wxPoint(0,5),wxSize(50,10));
52 
53     // draw x-axis:
54     float last_mark = -10000;
55     for (float math_x=int(visible_area.m_x*10)/10 ; math_x < (visible_area.m_x+visible_area.m_width) ; math_x+=0.1f) {
56         int x = math_to_screen(wxPoint2DDouble(math_x,visible_area.m_y)).x;
57         int y = m_rect.GetBottom();
58         if (x-last_mark < legend_side) continue;
59         dc.DrawLine(x,y+3,x,y-3);
60         dc.DrawText(wxString().Format(wxT("%.1f"), math_x),wxPoint(x-scale_unit,y+0.5*scale_unit));
61         last_mark = x;
62     }
63 
64     // draw y-axis:
65     last_mark=10000;
66     for (int math_y=visible_area.m_y ; math_y < (visible_area.m_y+visible_area.m_height) ; math_y+=1) {
67         int y = math_to_screen(wxPoint2DDouble(visible_area.m_x,math_y)).y;
68         int x = m_rect.GetLeft();
69         if (last_mark-y < legend_side) continue;
70         dc.DrawLine(x-3,y,x+3,y);
71         dc.DrawText(wxString()<<math_y,wxPoint(x-2*scale_unit,y-0.5*scale_unit));
72         last_mark = y;
73     }
74 
75     // axis labels:
76     wxString label = _(L("Time")) + " ("+_(L("s"))+")";
77     int text_width = 0;
78     int text_height = 0;
79     dc.GetTextExtent(label,&text_width,&text_height);
80     dc.DrawText(label,wxPoint(0.5*(m_rect.GetRight()+m_rect.GetLeft())-text_width/2.f, m_rect.GetBottom()+0.5*legend_side));
81     label = _(L("Volumetric speed")) + " (" + _(L("mm³/s")) + ")";
82     dc.GetTextExtent(label,&text_width,&text_height);
83     dc.DrawRotatedText(label,wxPoint(0,0.5*(m_rect.GetBottom()+m_rect.GetTop())+text_width/2.f),90);
84 }
85 
mouse_right_button_clicked(wxMouseEvent & event)86 void Chart::mouse_right_button_clicked(wxMouseEvent& event) {
87     if (!manual_points_manipulation)
88         return;
89     wxPoint point = event.GetPosition();
90     int button_index = which_button_is_clicked(point);
91     if (button_index != -1 && m_buttons.size()>2) {
92         m_buttons.erase(m_buttons.begin()+button_index);
93         recalculate_line();
94     }
95 }
96 
97 
98 
mouse_clicked(wxMouseEvent & event)99 void Chart::mouse_clicked(wxMouseEvent& event) {
100     wxPoint point = event.GetPosition();
101     int button_index = which_button_is_clicked(point);
102     if ( button_index != -1) {
103         m_dragged = &m_buttons[button_index];
104         m_previous_mouse = point;
105     }
106 }
107 
108 
109 
mouse_moved(wxMouseEvent & event)110 void Chart::mouse_moved(wxMouseEvent& event) {
111     if (!event.Dragging() || !m_dragged) return;
112     wxPoint pos = event.GetPosition();
113     wxRect rect = m_rect;
114     rect.Deflate(side/2.);
115     if (!(rect.Contains(pos))) {  // the mouse left chart area
116         mouse_left_window(event);
117         return;
118     }
119     int delta_x = pos.x - m_previous_mouse.x;
120     int delta_y = pos.y - m_previous_mouse.y;
121     m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height);
122     m_previous_mouse = pos;
123     recalculate_line();
124 }
125 
126 
127 
mouse_double_clicked(wxMouseEvent & event)128 void Chart::mouse_double_clicked(wxMouseEvent& event) {
129     if (!manual_points_manipulation)
130         return;
131     wxPoint point = event.GetPosition();
132     if (!m_rect.Contains(point))     // the click is outside the chart
133         return;
134     m_buttons.push_back(screen_to_math(point));
135     std::sort(m_buttons.begin(),m_buttons.end());
136     recalculate_line();
137     return;
138 }
139 
140 
141 
142 
recalculate_line()143 void Chart::recalculate_line() {
144     m_line_to_draw.clear();
145     m_total_volume = 0.f;
146 
147     std::vector<wxPoint> points;
148     for (auto& but : m_buttons) {
149         points.push_back(wxPoint(math_to_screen(but.get_pos())));
150         if (points.size()>1 && points.back().x==points[points.size()-2].x) points.pop_back();
151         if (points.size()>1 && points.back().x > m_rect.GetRight()) {
152             points.pop_back();
153             break;
154         }
155     }
156 
157     // The calculation wouldn't work in case the ramming is to be turned off completely.
158     if (points.size()>1) {
159         std::sort(points.begin(),points.end(),[](wxPoint& a,wxPoint& b) { return a.x < b.x; });
160 
161         // Cubic spline interpolation: see https://en.wikiversity.org/wiki/Cubic_Spline_Interpolation#Methods
162         const bool boundary_first_derivative = true; // true - first derivative is 0 at the leftmost and rightmost point
163                                                      // false - second ---- || -------
164         const int N = points.size()-1; // last point can be accessed as N, we have N+1 total points
165         std::vector<float> diag(N+1);
166         std::vector<float> mu(N+1);
167         std::vector<float> lambda(N+1);
168         std::vector<float> h(N+1);
169         std::vector<float> rhs(N+1);
170 
171         // let's fill in inner equations
172         for (int i=1;i<=N;++i) h[i] = points[i].x-points[i-1].x;
173         std::fill(diag.begin(),diag.end(),2.f);
174         for (int i=1;i<=N-1;++i) {
175             mu[i] = h[i]/(h[i]+h[i+1]);
176             lambda[i] = 1.f - mu[i];
177             rhs[i] = 6 * ( float(points[i+1].y-points[i].y  )/(h[i+1]*(points[i+1].x-points[i-1].x)) -
178                            float(points[i].y  -points[i-1].y)/(h[i]  *(points[i+1].x-points[i-1].x))   );
179         }
180 
181         // now fill in the first and last equations, according to boundary conditions:
182         if (boundary_first_derivative) {
183             const float endpoints_derivative = 0;
184             lambda[0] = 1;
185             mu[N]     = 1;
186             rhs[0] = (6.f/h[1]) * (float(points[0].y-points[1].y)/(points[0].x-points[1].x) - endpoints_derivative);
187             rhs[N] = (6.f/h[N]) * (endpoints_derivative - float(points[N-1].y-points[N].y)/(points[N-1].x-points[N].x));
188         }
189         else {
190             lambda[0] = 0;
191             mu[N]     = 0;
192             rhs[0]    = 0;
193             rhs[N]    = 0;
194         }
195 
196         // the trilinear system is ready to be solved:
197         for (int i=1;i<=N;++i) {
198             float multiple = mu[i]/diag[i-1];    // let's subtract proper multiple of above equation
199             diag[i]-= multiple * lambda[i-1];
200             rhs[i] -= multiple * rhs[i-1];
201         }
202         // now the back substitution (vector mu contains invalid values from now on):
203         rhs[N] = rhs[N]/diag[N];
204         for (int i=N-1;i>=0;--i)
205             rhs[i] = (rhs[i]-lambda[i]*rhs[i+1])/diag[i];
206 
207         unsigned int i=1;
208         float y=0.f;
209         for (int x=m_rect.GetLeft(); x<=m_rect.GetRight() ; ++x) {
210             if (splines) {
211                 if (i<points.size()-1 && points[i].x < x ) {
212                     ++i;
213                 }
214                 if (points[0].x > x)
215                     y = points[0].y;
216                 else
217                     if (points[N].x < x)
218                         y = points[N].y;
219                     else
220                         y = (rhs[i-1]*pow(points[i].x-x,3)+rhs[i]*pow(x-points[i-1].x,3)) / (6*h[i]) +
221                             (points[i-1].y-rhs[i-1]*h[i]*h[i]/6.f) * (points[i].x-x)/h[i] +
222                             (points[i].y  -rhs[i]  *h[i]*h[i]/6.f) * (x-points[i-1].x)/h[i];
223                 m_line_to_draw.push_back(y);
224             }
225             else {
226                 float x_math = screen_to_math(wxPoint(x,0)).m_x;
227                 if (i+2<=points.size() && m_buttons[i+1].get_pos().m_x-0.125 < x_math)
228                     ++i;
229                 m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[i].get_pos().m_y)).y);
230             }
231 
232             m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1);
233             m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1);
234             m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight());
235         }
236     }
237 
238     wxPostEvent(this->GetParent(), wxCommandEvent(EVT_WIPE_TOWER_CHART_CHANGED));
239     Refresh();
240 }
241 
242 
243 
get_ramming_speed(float sampling) const244 std::vector<float> Chart::get_ramming_speed(float sampling) const {
245     std::vector<float> speeds_out;
246 
247     const int number_of_samples = std::round( visible_area.m_width / sampling);
248     if (number_of_samples>0) {
249         const int dx = (m_line_to_draw.size()-1) / number_of_samples;
250         for (int j=0;j<number_of_samples;++j) {
251             float left =  screen_to_math(wxPoint(0,m_line_to_draw[j*dx])).m_y;
252             float right = screen_to_math(wxPoint(0,m_line_to_draw[(j+1)*dx])).m_y;
253             speeds_out.push_back((left+right)/2.f);
254         }
255     }
256     return speeds_out;
257 }
258 
259 
get_buttons() const260 std::vector<std::pair<float,float>> Chart::get_buttons() const {
261     std::vector<std::pair<float, float>> buttons_out;
262     for (const auto& button : m_buttons)
263         buttons_out.push_back(std::make_pair(float(button.get_pos().m_x),float(button.get_pos().m_y)));
264     return buttons_out;
265 }
266 
267 
268 
269 
270 BEGIN_EVENT_TABLE(Chart, wxWindow)
271 EVT_MOTION(Chart::mouse_moved)
272 EVT_LEFT_DOWN(Chart::mouse_clicked)
273 EVT_LEFT_UP(Chart::mouse_released)
274 EVT_LEFT_DCLICK(Chart::mouse_double_clicked)
275 EVT_RIGHT_DOWN(Chart::mouse_right_button_clicked)
276 EVT_LEAVE_WINDOW(Chart::mouse_left_window)
277 EVT_PAINT(Chart::paint_event)
278 END_EVENT_TABLE()
279