SPH
PlotView.cpp
Go to the documentation of this file.
1 #include "gui/windows/PlotView.h"
2 #include "gui/Utils.h"
5 #include "io/Logger.h"
6 #include "io/Path.h"
7 #include <wx/button.h>
8 #include <wx/checkbox.h>
9 #include <wx/dcclient.h>
10 #include <wx/menu.h>
11 #include <wx/sizer.h>
12 
13 #include <wx/aui/auibook.h>
14 
16 
17 PlotView::PlotView(wxWindow* parent,
18  const wxSize size,
19  const wxSize padding,
20  const SharedPtr<Array<PlotData>>& list,
21  const Size defaultSelectedIdx,
22  const Optional<TicsParams> ticsParams)
23  : wxPanel(parent, wxID_ANY, wxDefaultPosition, size)
24  , padding(padding)
25  , list(list)
26  , ticsParams(ticsParams) {
27  this->SetMinSize(size);
28  this->Connect(wxEVT_PAINT, wxPaintEventHandler(PlotView::onPaint));
29  this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(PlotView::onRightUp));
30  this->Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(PlotView::onDoubleClick));
31 
32  this->updatePlot(defaultSelectedIdx);
33 }
34 
35 void PlotView::resize(const Pixel size) {
36  this->SetMinSize(wxSize(size.x, size.y));
37  // this->SetSize(wxSize(size.x, size.y));
38 }
39 
40 static Interval extendRange(const Interval& range, const bool addZero) {
41  Interval actRange = range;
42  if (addZero) {
43  const Float eps = 0.05_f * range.size();
44  actRange.extend(eps);
45  actRange.extend(-eps);
46  }
47  return actRange;
48 }
49 
50 AffineMatrix2 PlotView::getPlotTransformMatrix(const Interval& rangeX, const Interval& rangeY) const {
51  // actual size of the plot
52  const wxSize size = this->GetSize() - 2 * padding;
53 
54  // scaling factors
55  const Float scaleX = size.x / rangeX.size();
56  const Float scaleY = -size.y / rangeY.size();
57 
58  // translation
59  const Float transX = padding.x - scaleX * rangeX.lower();
60  const Float transY = size.y + padding.y - scaleY * rangeY.lower();
61 
62  return AffineMatrix2(scaleX, 0._f, 0._f, scaleY, transX, transY);
63 }
64 
65 void PlotView::updatePlot(const Size index) {
66  if (index >= list->size()) {
67  return;
68  }
69 
70  PlotData& data = (*list)[index];
71  cached.color = data.color;
72 
73  // plot needs to be synchronized as it is updated from different thread, hopefully neither updating
74  // nor drawing will take a lot of time, so we can simply lock the pointer.
75  cached.plot = data.plot;
76 }
77 
78 void PlotView::onRightUp(wxMouseEvent& UNUSED(evt)) {
79  if (list->size() <= 1) {
80  // nothing to choose from, do nothing
81  return;
82  }
83 
84  wxMenu menu;
85  Size index = 0;
86  for (PlotData& data : *list) {
87  menu.Append(index++, data.plot->getCaption());
88  }
89 
90  menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(PlotView::onMenu), nullptr, this);
91  this->PopupMenu(&menu);
92 }
93 
94 void PlotView::onDoubleClick(wxMouseEvent& UNUSED(evt)) {
95  if (!cached.plot) {
96  // no plot
97  return;
98  }
99  wxAuiNotebook* notebook = findNotebook();
100  SPH_ASSERT(notebook);
101 
102  const wxSize pad(25, 25);
103  const wxSize size = notebook->GetClientSize() - wxSize(15, 60);
104  PlotPage* page = new PlotPage(notebook, size, pad, cached.plot);
105 
106  const Size index = notebook->GetPageCount();
107  // needs to be called before, AddPage calls onPaint, which locks the mutex
108  const std::string caption = cached.plot->getCaption();
109  notebook->AddPage(page, caption);
110  notebook->SetSelection(index);
111 }
112 
113 void PlotView::onMenu(wxCommandEvent& evt) {
115  const Size index = evt.GetId();
116  SPH_ASSERT(index < list->size());
117  this->updatePlot(index);
118  this->Refresh();
119 }
120 
121 void PlotView::onPaint(wxPaintEvent& UNUSED(evt)) {
122  wxPaintDC dc(this);
123  wxSize canvasSize = dc.GetSize();
124 
125  // draw background
126  Rgba backgroundColor = Rgba(this->GetParent()->GetBackgroundColour());
127  wxBrush brush;
128  brush.SetColour(wxColour(backgroundColor.darken(0.3f)));
129  dc.SetBrush(brush);
130  dc.DrawRectangle(wxPoint(0, 0), canvasSize);
131 
132  if (!cached.plot) {
133  return;
134  }
135 
136  auto proxy = cached.plot.lock();
137  this->drawCaption(dc, *proxy);
138 
139  const Interval rangeX = extendRange(proxy->rangeX(), addZeroX);
140  const Interval rangeY = extendRange(proxy->rangeY(), addZeroY);
141  if (rangeX.size() <= 0._f || rangeY.size() <= 0) {
142  // don't assert, it probably means there are no data to draw
143  return;
144  }
145 
146  wxPen pen;
147  pen.SetColour(*wxWHITE);
148  dc.SetPen(pen);
149  this->drawAxes(dc, rangeX, rangeY);
150  this->drawPlot(dc, *proxy, rangeX, rangeY);
151 }
152 
153 void PlotView::drawPlot(wxPaintDC& dc, IPlot& lockedPlot, const Interval rangeX, const Interval rangeY) {
154  GraphicsContext context(dc, cached.color);
155  const AffineMatrix2 matrix = this->getPlotTransformMatrix(rangeX, rangeY);
156  context.setTransformMatrix(matrix);
157 
158  lockedPlot.plot(context);
159 }
160 
161 void PlotView::drawAxes(wxDC& dc, const Interval rangeX, const Interval rangeY) {
162  const wxSize size = this->GetSize();
163  const AffineMatrix2 matrix = this->getPlotTransformMatrix(rangeX, rangeY);
164 
165  // find point where y-axis appears on the polot
166  const Float x0 = -rangeX.lower() / rangeX.size();
167  if (x0 >= 0._f && x0 <= 1._f) {
168  // draw y-axis
169  const int dcX = int(padding.x + x0 * (size.x - 2 * padding.x));
170  dc.DrawLine(dcX, size.y - padding.y, dcX, padding.y);
171  if (ticsParams) {
172  Array<Float> tics = getLinearTics(rangeY, ticsParams->minCnt);
173  SPH_ASSERT(tics.size() >= ticsParams->minCnt);
174  for (const Float tic : tics) {
175  const PlotPoint plotPoint(0, tic);
176  const PlotPoint imagePoint = matrix.transformPoint(plotPoint);
177  dc.DrawLine(
178  int(imagePoint.x) - 2, int(imagePoint.y), int(imagePoint.x) + 2, int(imagePoint.y));
179  const std::wstring text = toPrintableString(tic, ticsParams->digits);
180  const wxSize extent = dc.GetTextExtent(text);
181  const int labelX =
182  (imagePoint.x > size.x / 2._f) ? int(imagePoint.x) - extent.x : int(imagePoint.x);
183  drawTextWithSubscripts(dc, text, wxPoint(labelX, int(imagePoint.y) - extent.y / 2));
184  }
185  }
186  }
187  // find point where x-axis appears on the plot
188  const Float y0 = -rangeY.lower() / rangeY.size();
189  if (y0 >= 0._f && y0 <= 1._f) {
190  // draw x-axis
191  const int dcY = int(size.y - padding.y - y0 * (size.y - 2 * padding.y));
192  dc.DrawLine(padding.x, dcY, size.x - padding.x, dcY);
193  if (ticsParams) {
194  Array<Float> tics = getLinearTics(rangeX, ticsParams->minCnt);
195  for (const Float tic : tics) {
196  const PlotPoint plotPoint(tic, 0);
197  const PlotPoint imagePoint = matrix.transformPoint(plotPoint);
198  dc.DrawLine(
199  int(imagePoint.x), int(imagePoint.y) - 2, int(imagePoint.x), int(imagePoint.y) + 2);
200  const std::wstring text = toPrintableString(tic, ticsParams->digits);
201  const wxSize extent = dc.GetTextExtent(text);
202  const int labelY =
203  (imagePoint.y < size.y / 2._f) ? int(imagePoint.y) : int(imagePoint.y) - extent.y;
204  drawTextWithSubscripts(dc, text, wxPoint(int(imagePoint.x) - extent.x / 2, labelY));
205  }
206  }
207  }
208 }
209 
210 void PlotView::drawCaption(wxDC& dc, IPlot& lockedPlot) {
211  // plot may change caption during simulation (by selecting particle, for example), so we need to get the
212  // name every time from the plot
213  const wxString label = lockedPlot.getCaption();
214  wxFont font = dc.GetFont();
215  font.MakeSmaller();
216  dc.SetFont(font);
217  const wxSize labelSize = dc.GetTextExtent(label);
218  dc.DrawText(label, dc.GetSize().x - labelSize.x, 0);
219 }
220 
221 PlotPage::PlotPage(wxWindow* parent, const wxSize size, const wxSize padding, const LockingPtr<IPlot>& plot)
222  : wxPanel(parent, wxID_ANY)
223  , plot(plot)
224  , padding(padding) {
225  this->SetMinSize(size);
226  SharedPtr<Array<PlotData>> data = makeShared<Array<PlotData>>();
227  data->push(PlotData{ plot, Rgba(0.1f, 0.1f, 0.9f) });
228 
229  wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
230  const Size toolbarHeight = 20;
231  wxBoxSizer* toolbarSizer = this->createToolbar(toolbarHeight);
232  sizer->Add(toolbarSizer);
233 
234  wxSize viewSize(size.x, size.y - toolbarHeight);
235  plotView = new PlotView(this, viewSize, padding, data, 0, TicsParams{});
236  sizer->Add(plotView);
237  this->SetSizerAndFit(sizer);
238 
239  this->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) {
240  const wxSize size = evt.GetSize();
241  plotView->resize(Pixel(size.x, size.y - toolbarHeight));
242  });
243 }
244 
245 wxBoxSizer* PlotPage::createToolbar(const Size UNUSED(toolbarHeight)) {
246  wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
247 
248  wxButton* savePlotButton = new wxButton(this, wxID_ANY, "Save Plot");
249  savePlotButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& UNUSED(evt)) {
250  Optional<Path> path = doSaveFileDialog("Save image",
251  {
252  { "PNG image", "png" },
253  { "SVG image", "svg" },
254  });
255  if (path) {
256  this->saveImage(path.value());
257  }
258  });
259  sizer->Add(savePlotButton, 0, wxALIGN_CENTER_VERTICAL);
260 
261  wxButton* saveDataButton = new wxButton(this, wxID_ANY, "Save Data");
262  saveDataButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& UNUSED(evt)) {
263  Optional<Path> path = doSaveFileDialog("Save data",
264  {
265  { "Text file", "txt" },
266  });
267  if (path) {
268  this->saveData(path.value());
269  }
270  });
271  sizer->Add(saveDataButton, 0, wxALIGN_CENTER_VERTICAL);
272 
273  wxButton* refreshButton = new wxButton(this, wxID_ANY, "Refresh");
274  refreshButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& UNUSED(evt)) { this->Refresh(); });
275  sizer->Add(refreshButton, 0, wxALIGN_CENTER_VERTICAL);
276 
277  wxCheckBox* addZeroXBox = new wxCheckBox(this, wxID_ANY, "Show zero X");
278  addZeroXBox->Bind(wxEVT_CHECKBOX, [this, addZeroXBox](wxCommandEvent& UNUSED(evt)) {
279  const bool checked = addZeroXBox->GetValue();
280  plotView->addZeroX = checked;
281  this->Refresh();
282  });
283  sizer->Add(addZeroXBox, 0, wxALIGN_CENTER_VERTICAL);
284 
285  wxCheckBox* addZeroYBox = new wxCheckBox(this, wxID_ANY, "Show zero Y");
286  addZeroYBox->Bind(wxEVT_CHECKBOX, [this, addZeroYBox](wxCommandEvent& UNUSED(evt)) {
287  const bool checked = addZeroYBox->GetValue();
288  plotView->addZeroY = checked;
289  this->Refresh();
290  });
291  sizer->Add(addZeroYBox, 0, wxALIGN_CENTER_VERTICAL);
292  return sizer;
293 }
294 
295 void PlotPage::saveImage(const Path& path) {
296  if (path.extension() == Path("png")) {
297  wxBitmap bitmap(800, 600, wxBITMAP_SCREEN_DEPTH);
298  wxMemoryDC dc(bitmap);
299  dc.SetBrush(*wxWHITE_BRUSH);
300  dc.DrawRectangle(0, 0, 800, 600);
301 
302  auto proxy = plot.lock();
303  GraphicsContext gc(dc, Rgba(0.f, 0.f, 0.5f));
304  const Interval actRangeX = extendRange(proxy->rangeX(), plotView->addZeroX);
305  const Interval actRangeY = extendRange(proxy->rangeY(), plotView->addZeroY);
306  AffineMatrix2 matrix = plotView->getPlotTransformMatrix(actRangeX, actRangeY);
307  gc.setTransformMatrix(matrix);
308  proxy->plot(gc);
309 
311  wxPen pen;
312  pen.SetColour(*wxBLACK);
313  dc.SetPen(pen);
314  plotView->drawAxes(dc, actRangeX, actRangeY);
315 
316  dc.SelectObject(wxNullBitmap);
317 
318  bitmap.SaveFile(path.native().c_str(), wxBITMAP_TYPE_PNG);
319  } else if (path.extension() == Path("svg")) {
320  auto proxy = plot.lock();
321  SvgContext gc(path, Pixel(800, 600));
322  AffineMatrix2 matrix = plotView->getPlotTransformMatrix(proxy->rangeX(), proxy->rangeY());
323  gc.setTransformMatrix(matrix);
324  proxy->plot(gc);
325  } else {
327  }
328 }
329 
330 class TextContext : public IDrawingContext {
331 private:
332  FileLogger logger;
333 
334 public:
335  explicit TextContext(const Path& path)
336  : logger(path) {}
337 
338  virtual void drawPoint(const PlotPoint& point) override {
339  logger.write(point.x, " ", point.y);
340  }
341 
342  virtual void drawErrorPoint(const ErrorPlotPoint& point) override {
343  logger.write(point.x, " ", point.y);
344  }
345 
346  virtual void drawLine(const PlotPoint& UNUSED(from), const PlotPoint& UNUSED(to)) override {
348  }
349 
350  class TextPath : public IDrawPath {
351  public:
352  virtual void addPoint(const PlotPoint& UNUSED(point)) override {
353  // N/A
354  }
355 
356  virtual void closePath() override {
357  // N/A
358  }
359 
360  virtual void endPath() override {
361  // N/A
362  }
363  };
364 
365  virtual AutoPtr<IDrawPath> drawPath() override {
366  return makeAuto<TextPath>();
367  }
368 
369  virtual void setStyle(const Size UNUSED(index)) override {
370  // possibly new plot, separate by newline
371  logger.write("");
372  }
373 
374  virtual void setTransformMatrix(const AffineMatrix2& UNUSED(matrix)) override {
375  // not applicable for text output
376  }
377 };
378 
379 void PlotPage::saveData(const Path& path) {
380  SPH_ASSERT(path.extension() == Path("txt"));
381  auto proxy = plot.lock();
382  TextContext context(path);
383  proxy->plot(context);
384 }
385 
#define SPH_ASSERT(x,...)
Definition: Assert.h:94
#define NOT_IMPLEMENTED
Helper macro marking missing implementation.
Definition: Assert.h:100
NAMESPACE_SPH_BEGIN
Definition: BarnesHut.cpp:13
@ MAIN_THREAD
Function can only be executed from main thread.
#define CHECK_FUNCTION(flags)
Definition: CheckFunction.h:40
uint32_t Size
Integral type used to index arrays (by default).
Definition: Globals.h:16
double Float
Precision used withing the code. Use Float instead of float or double where precision is important.
Definition: Globals.h:13
Logging routines of the run.
wxAuiNotebook * findNotebook()
Definition: MainWindow.cpp:28
#define UNUSED(x)
Definition: Object.h:37
#define NAMESPACE_SPH_END
Definition: Object.h:12
Object representing a path on a filesystem, similar to std::filesystem::path in c++17.
Drawing of plots.
Array< Float > getLinearTics(const Interval &interval, const Size minCount)
Returns the tics to be drawn on a linear axis of a plot.
Definition: Plot.cpp:423
Implementation of IDrawingContext for creating vector images (.svg)
void drawTextWithSubscripts(wxDC &dc, const std::wstring &text, const wxPoint point)
Definition: Utils.cpp:86
std::wstring toPrintableString(const Float value, const Size precision, const Float decimalThreshold)
Converts the value to a printable string.
Definition: Utils.cpp:121
Optional< Path > doSaveFileDialog(const std::string &title, Array< FileFormat > &&formats)
Definition: Utils.cpp:56
Random utility functions for drawing stuff to DC.
2D affine matrix
Definition: Point.h:50
PlotPoint transformPoint(const PlotPoint &p) const
Applies the affine transform on given point.
Definition: Point.h:92
Generic dynamically allocated resizable storage.
Definition: Array.h:43
INLINE TCounter size() const noexcept
Definition: Array.h:193
Wrapper of pointer that deletes the resource from destructor.
Definition: AutoPtr.h:15
File output logger.
Definition: Logger.h:160
Drawing context using wxWidgets implementation of Cairo backend.
Definition: Plot.h:19
Abstraction of a drawing context.
Definition: Plot.h:34
void write(TArgs &&... args)
Creates and logs a message by concatenating arguments.
Definition: Logger.h:37
Interface for constructing generic plots from quantities stored in storage.
Definition: Plot.h:75
virtual void plot(IDrawingContext &dc) const =0
Draws the plot into the drawing context.
virtual std::string getCaption() const =0
Returns the caption of the plot.
Object representing a 1D interval of real numbers.
Definition: Interval.h:17
INLINE Float lower() const
Returns lower bound of the interval.
Definition: Interval.h:74
INLINE void extend(const Float &value)
Extends the interval to contain given value.
Definition: Interval.h:41
INLINE Float size() const
Returns the size of the interval.
Definition: Interval.h:89
Proxy lock() const
Definition: LockingPtr.h:176
INLINE Type & value()
Returns the reference to the stored value.
Definition: Optional.h:172
Object representing a path on a filesystem.
Definition: Path.h:17
std::string native() const
Returns the native version of the path.
Definition: Path.cpp:71
Path extension() const
Returns the extension of the filename.
Definition: Path.cpp:59
PlotPage(wxWindow *parent, const wxSize size, const wxSize padding, const LockingPtr< IPlot > &plot)
Definition: PlotView.cpp:221
bool addZeroY
Include zero in y-range.
Definition: PlotView.h:44
bool addZeroX
Include zero in x-range.
Definition: PlotView.h:41
AffineMatrix2 getPlotTransformMatrix(const Interval &rangeX, const Interval &rangeY) const
Returns the transformation matrix for managed plot.
Definition: PlotView.cpp:50
PlotView(wxWindow *parent, const wxSize size, const wxSize padding, const SharedPtr< Array< PlotData >> &list, const Size defaultSelectedIdx, Optional< TicsParams > ticsParams)
Definition: PlotView.cpp:17
void resize(const Pixel size)
Definition: PlotView.cpp:35
void drawAxes(wxDC &dc, const Interval rangeX, const Interval rangeY)
Definition: PlotView.cpp:161
Definition: Color.h:8
Rgba darken(const float amount) const
Returns a color darker by given factor.
Definition: Color.h:137
virtual void closePath() override
Closes the path, connecting to the first point on the path.
Definition: PlotView.cpp:356
virtual void endPath() override
Finalizes the path. Does not connect the last point to anything.
Definition: PlotView.cpp:360
virtual void addPoint(const PlotPoint &UNUSED(point)) override
Definition: PlotView.cpp:352
virtual void drawPoint(const PlotPoint &point) override
Adds a single point to the plot.
Definition: PlotView.cpp:338
virtual void setTransformMatrix(const AffineMatrix2 &UNUSED(matrix)) override
Definition: PlotView.cpp:374
virtual void setStyle(const Size UNUSED(index)) override
Definition: PlotView.cpp:369
TextContext(const Path &path)
Definition: PlotView.cpp:335
virtual void drawLine(const PlotPoint &UNUSED(from), const PlotPoint &UNUSED(to)) override
Definition: PlotView.cpp:346
virtual void drawErrorPoint(const ErrorPlotPoint &point) override
Adds a point with error bars to the plot.
Definition: PlotView.cpp:342
virtual AutoPtr< IDrawPath > drawPath() override
Draws a path connecting points.
Definition: PlotView.cpp:365
Point with error bars.
Definition: Point.h:43
Definition: Point.h:101
Definition: Plots.h:12
Rgba color
Color of the plot.
Definition: Plots.h:17
LockingPtr< IPlot > plot
Plot to be drawn with associated mutex.
Definition: Plots.h:14
Point in 2D plot.
Definition: Point.h:16
Float y
Definition: Point.h:17
Float x
Definition: Point.h:17