TermOx
graph.hpp
1 #ifndef TERMOX_WIDGET_WIDGETS_GRAPH_HPP
2 #define TERMOX_WIDGET_WIDGETS_GRAPH_HPP
3 #include <algorithm>
4 #include <cassert>
5 #include <cmath>
6 #include <cstdint>
7 #include <iterator>
8 #include <memory>
9 #include <type_traits>
10 #include <utility>
11 #include <vector>
12 
13 #include <termox/painter/color.hpp>
14 #include <termox/painter/glyph.hpp>
15 #include <termox/painter/painter.hpp>
16 #include <termox/widget/boundary.hpp>
17 #include <termox/widget/point.hpp>
18 #include <termox/widget/widget.hpp>
19 
20 namespace ox {
21 
23 
26 template <typename Number_t = double>
27 class Graph : public Widget {
28  public:
30  struct Coordinate {
31  Number_t x;
32  Number_t y;
33  };
34 
35  public:
36  struct Parameters {
37  Boundary<Number_t> boundary = {};
38  std::vector<Coordinate> coordinates = {};
39  };
40 
41  public:
43  explicit Graph(Boundary<Number_t> b = {}, std::vector<Coordinate> x = {})
44  : boundary_{b}, coordinates_{std::move(x)}
45  {
46  assert(b.west < b.east && b.south < b.north);
47  auto constexpr empty_braille = U'⠀';
48  this->set_wallpaper(empty_braille);
49  }
50 
52  explicit Graph(Parameters p) : Graph{p.boundary, std::move(p.coordinates)}
53  {}
54 
55  public:
58  {
59  assert(b.west < b.east && b.south < b.north);
60  boundary_ = b;
61  this->update();
62  }
63 
65  [[nodiscard]] auto boundary() const -> Boundary<Number_t>
66  {
67  return boundary_;
68  }
69 
71  void reset(std::vector<Coordinate> x)
72  {
73  coordinates_ = std::move(x);
74  this->update();
75  }
76 
78  template <typename Iter1_t, typename Iter2_t>
79  void reset(Iter1_t first, Iter2_t last)
80  {
81  static_assert(
82  std::is_same_v<typename std::iterator_traits<Iter1_t>::value_type,
83  Coordinate>,
84  "Must add with a iterators pointing to Coordinates.");
85  coordinates_.clear();
86  std::copy(first, last, std::back_inserter(coordinates_));
87  this->update();
88  }
89 
91  void add(Coordinate c)
92  {
93  coordinates_.push_back(c);
94  this->update();
95  }
96 
98  void clear()
99  {
100  coordinates_.clear();
101  this->update();
102  }
103 
105  [[nodiscard]] auto coordinates() const -> std::vector<Coordinate> const&
106  {
107  return coordinates_;
108  }
109 
110  protected:
111  auto paint_event(Painter& p) -> bool override
112  {
113  auto const area = this->area();
114  auto const h_ratio =
115  (double)area.width / distance(boundary_.west, boundary_.east);
116  auto const v_ratio =
117  (double)area.height / distance(boundary_.south, boundary_.north);
118 
119  for (auto const c : coordinates_) {
120  auto const h_offset = h_ratio * distance(boundary_.west, c.x);
121  auto const v_offset =
122  area.height - v_ratio * distance(boundary_.south, c.y);
123  if (h_offset >= area.width) // h_offset already can't be negative.
124  continue;
125  if (v_offset >= area.height || v_offset < 0)
126  continue;
127  auto const point = Point{(int)h_offset, (int)v_offset};
128  auto const mask = this->to_cell_mask(h_offset, v_offset);
129  auto current = p.at(point);
130  current.symbol = combine(current.symbol, mask);
131  p.put(current, point);
132  }
133  return Widget::paint_event(p);
134  }
135 
136  private:
137  Boundary<Number_t> boundary_;
138  std::vector<Coordinate> coordinates_;
139 
140  private:
142  [[nodiscard]] static auto distance(Number_t smaller, Number_t larger)
143  -> Number_t
144  {
145  return larger - smaller;
146  }
147 
149  [[nodiscard]] static auto to_cell_mask(double h_offset, double v_offset)
150  -> std::uint8_t
151  {
152  auto const h_cell = h_offset - std::floor(h_offset);
153  auto const v_cell = v_offset - std::floor(v_offset);
154  if (h_cell < 0.5) {
155  if (v_cell < 0.25)
156  return 0b00000001;
157  else if (v_cell < 0.5)
158  return 0b00000010;
159  else if (v_cell < 0.75)
160  return 0b00000100;
161  else
162  return 0b01000000;
163  }
164  else {
165  if (v_cell < 0.25)
166  return 0b00001000;
167  else if (v_cell < 0.5)
168  return 0b00010000;
169  else if (v_cell < 0.75)
170  return 0b00100000;
171  else
172  return 0b10000000;
173  }
174  }
175 
177  [[nodiscard]] static auto combine(char32_t braille, std::uint8_t mask)
178  -> char32_t
179  {
180  return braille | mask;
181  }
182 };
183 
185 template <typename Number_t = double>
186 [[nodiscard]] auto graph(
187  Boundary<Number_t> b = {},
188  std::vector<typename Graph<Number_t>::Coordinate> x = {})
189  -> std::unique_ptr<Graph<Number_t>>
190 {
191  return std::make_unique<Graph<Number_t>>(b, std::move(x));
192 }
193 
195 template <typename Number_t = double>
196 [[nodiscard]] auto graph(typename Graph<Number_t>::Parameters p)
197  -> std::unique_ptr<Graph<Number_t>>
198 {
199  return std::make_unique<Graph<Number_t>>(std::move(p));
200 }
201 
203 
206 template <typename Number_t = double>
207 class Color_graph : public Widget {
208  public:
210  struct Coordinate {
211  Number_t x;
212  Number_t y;
213  };
214 
215  public:
216  struct Parameters {
217  Boundary<Number_t> boundary = {};
218  std::vector<std::pair<Coordinate, Color>> coordinates = {};
219  };
220 
221  public:
224  std::vector<std::pair<Coordinate, Color>> x = {})
225  : boundary_{b}, coordinates_{std::move(x)}
226  {
227  assert(b.west < b.east && b.south < b.north);
228  }
229 
232  : Color_graph{p.boundary, std::move(p.coordinates)}
233  {}
234 
235  public:
238  {
239  assert(b.west < b.east && b.south < b.north);
240  boundary_ = b;
241  this->update();
242  }
243 
245  [[nodiscard]] auto boundary() const -> Boundary<Number_t>
246  {
247  return boundary_;
248  }
249 
251  void reset(std::vector<std::pair<Coordinate, Color>> x)
252  {
253  coordinates_ = std::move(x);
254  this->update();
255  }
256 
258 
259  template <typename Iter1_t, typename Iter2_t>
260  void reset(Iter1_t first, Iter2_t last)
261  {
262  static_assert(
263  std::is_same_v<typename std::iterator_traits<Iter1_t>::value_type,
264  std::pair<Coordinate, Color>>,
265  "Must add with a iterators pointing to Coordinates, Color pair.");
266  coordinates_.clear();
267  std::copy(first, last, std::back_inserter(coordinates_));
268  this->update();
269  }
270 
272  void add(std::pair<Coordinate, Color> p)
273  {
274  coordinates_.push_back(p);
275  this->update();
276  }
277 
279  void clear()
280  {
281  coordinates_.clear();
282  this->update();
283  }
284 
286  [[nodiscard]] auto coordinates() const
287  -> std::vector<std::pair<Coordinate, Color>> const&
288  {
289  return coordinates_;
290  }
291 
292  protected:
293  auto paint_event(Painter& p) -> bool override
294  {
295  auto const area = this->area();
296  auto const h_ratio =
297  (double)area.width / distance(boundary_.west, boundary_.east);
298  auto const v_ratio =
299  (double)area.height / distance(boundary_.south, boundary_.north);
300 
301  for (auto const& [coord, color] : coordinates_) {
302  auto const h_offset = h_ratio * distance(boundary_.west, coord.x);
303  auto const v_offset =
304  area.height - v_ratio * distance(boundary_.south, coord.y);
305  if (h_offset >= area.width) // h_offset already can't be negative.
306  continue;
307  if (v_offset >= area.height || v_offset < 0)
308  continue;
309  auto const point = Point{(int)h_offset, (int)v_offset};
310  auto const is_top = is_top_region(v_offset);
311  auto const glyph = combine(p.at(point), is_top, color);
312  p.put(glyph, point);
313  }
314  return Widget::paint_event(p);
315  }
316 
317  private:
318  Boundary<Number_t> boundary_;
319  std::vector<std::pair<Coordinate, Color>> coordinates_;
320 
321  private:
323  [[nodiscard]] static auto distance(Number_t smaller, Number_t larger)
324  -> Number_t
325  {
326  return larger - smaller;
327  }
328 
330 
331  [[nodiscard]] static auto is_top_region(double v_offset) -> bool
332  {
333  return (v_offset - std::floor(v_offset)) < 0.5;
334  }
335 
337  [[nodiscard]] static auto combine(Glyph current, bool is_top, Color c)
338  -> Glyph
339  {
340  if (current.symbol == U' ')
341  current = U'▀' | fg(Color::Background);
342  if (is_top)
343  current |= fg(c);
344  else
345  current |= bg(c);
346  return current;
347  }
348 };
349 
351 template <typename Number_t = double>
352 [[nodiscard]] auto color_graph(
353  Boundary<Number_t> b = {},
354  std::vector<std::pair<typename Color_graph<Number_t>::Coordinate, Color>>
355  x = {}) -> std::unique_ptr<Color_graph<Number_t>>
356 {
357  return std::make_unique<Color_graph<Number_t>>(b, std::move(x));
358 }
359 
361 template <typename Number_t = double>
362 [[nodiscard]] auto color_graph(typename Color_graph<Number_t>::Parameters p)
363  -> std::unique_ptr<Color_graph<Number_t>>
364 {
365  return std::make_unique<Color_graph<Number_t>>(std::move(p));
366 }
367 
369 
372 template <typename Number_t,
373  Number_t west,
374  Number_t east,
375  Number_t north,
376  Number_t south>
378  static_assert(west < east && south < north);
379 
380  public:
382  struct Coordinate {
383  Number_t x;
384  Number_t y;
385  };
386 
387  public:
388  struct Parameters {
389  std::vector<std::pair<Coordinate, Color>> coordinates = {};
390  };
391 
392  public:
395  std::vector<std::pair<Coordinate, Color>> x = {})
396  : coordinates_{std::move(x)}
397  {}
398 
401  : Color_graph_static_bounds{std::move(p.coordinates)}
402  {}
403 
404  public:
406  [[nodiscard]] static constexpr auto boundary() -> Boundary<Number_t>
407  {
408  return boundary_;
409  }
410 
412  void reset(std::vector<std::pair<Coordinate, Color>> x)
413  {
414  coordinates_ = std::move(x);
415  this->update();
416  }
417 
419 
420  template <typename Iter1_t, typename Iter2_t>
421  void reset(Iter1_t first, Iter2_t last)
422  {
423  static_assert(
424  std::is_same_v<typename std::iterator_traits<Iter1_t>::value_type,
425  std::pair<Coordinate, Color>>,
426  "Must add with a iterators pointing to Coordinates, Color pair.");
427  coordinates_.clear();
428  std::copy(first, last, std::back_inserter(coordinates_));
429  this->update();
430  }
431 
433  void add(std::pair<Coordinate, Color> p)
434  {
435  coordinates_.push_back(p);
436  this->update();
437  }
438 
440  void clear()
441  {
442  coordinates_.clear();
443  this->update();
444  }
445 
447  [[nodiscard]] auto coordinates() const
448  -> std::vector<std::pair<Coordinate, Color>> const&
449  {
450  return coordinates_;
451  }
452 
453  protected:
454  auto paint_event(Painter& p) -> bool override
455  {
456  constexpr double h_distance = distance(boundary_.west, boundary_.east);
457  constexpr double v_distance =
458  distance(boundary_.south, boundary_.north);
459 
460  auto const area = this->area();
461  auto const h_ratio = (double)area.width / h_distance;
462  auto const v_ratio = (double)area.height / v_distance;
463 
464  for (auto const& [coord, color] : coordinates_) {
465  auto const h_offset = h_ratio * distance(boundary_.west, coord.x);
466  auto const v_offset =
467  area.height - (v_ratio * distance(boundary_.south, coord.y));
468  if (h_offset >= area.width) // h_offset already can't be negative.
469  continue;
470  if (v_offset >= area.height || v_offset < 0)
471  continue;
472  auto const point = Point{(int)h_offset, (int)v_offset};
473  auto const is_top = is_top_region(v_offset);
474  auto const glyph = combine(p.at(point), is_top, color);
475  p.put(glyph, point);
476  }
477  return Widget::paint_event(p);
478  }
479 
480  private:
481  static constexpr auto boundary_ =
482  Boundary<Number_t>{west, east, north, south};
483 
484  std::vector<std::pair<Coordinate, Color>> coordinates_;
485 
486  private:
488  [[nodiscard]] static constexpr auto distance(Number_t smaller,
489  Number_t larger) -> Number_t
490  {
491  return larger - smaller;
492  }
493 
495 
496  [[nodiscard]] static auto is_top_region(double v_offset) -> bool
497  {
498  return (v_offset - std::floor(v_offset)) < 0.5;
499  }
500 
502  [[nodiscard]] static auto combine(Glyph current, bool is_top, Color c)
503  -> Glyph
504  {
505  if (current.symbol == U' ')
506  current = U'▀' | fg(Color::Background);
507  if (is_top)
508  current |= fg(c);
509  else
510  current |= bg(c);
511  return current;
512  }
513 };
514 
516 template <typename Number_t,
517  Number_t west,
518  Number_t east,
519  Number_t north,
520  Number_t south>
521 [[nodiscard]] auto color_graph_static_bounds(
522  std::vector<std::pair<
523  typename Color_graph_static_bounds<Number_t, west, east, north, south>::
524  Coordinate,
525  Color>> x = {})
526  -> std::unique_ptr<
527  Color_graph_static_bounds<Number_t, west, east, north, south>>
528 {
529  return std::make_unique<
530  Color_graph_static_bounds<Number_t, west, east, north, south>>(
531  std::move(x));
532 }
533 
535 template <typename Number_t,
536  Number_t west,
537  Number_t east,
538  Number_t north,
539  Number_t south>
540 [[nodiscard]] auto color_graph_static_bounds(
541  typename Color_graph_static_bounds<Number_t, west, east, north, south>::
542  Parameters p)
543  -> std::unique_ptr<
544  Color_graph_static_bounds<Number_t, west, east, north, south>>
545 {
546  return std::make_unique<
547  Color_graph_static_bounds<Number_t, west, east, north, south>>(
548  std::move(p));
549 }
550 
551 } // namespace ox
552 #endif // TERMOX_WIDGET_WIDGETS_GRAPH_HPP
Bounded box that can display added Color Coordinates within a static Bound.
Definition: graph.hpp:377
void reset(Iter1_t first, Iter2_t last)
Replace with copies of <Coordinate, Color>s given by iterators.
Definition: graph.hpp:421
void reset(std::vector< std::pair< Coordinate, Color >> x)
Replace the current state with the given Coordinates vector x.
Definition: graph.hpp:412
Color_graph_static_bounds(Parameters p)
Create a Color_graph_static_bounds with given Parameters.
Definition: graph.hpp:400
static constexpr auto boundary() -> Boundary< Number_t >
Return the currently set Boundary.
Definition: graph.hpp:406
Color_graph_static_bounds(std::vector< std::pair< Coordinate, Color >> x={})
Create a Color_graph_static_bounds with given Coordinates and Colors.
Definition: graph.hpp:394
void clear()
Remove all points from the Graph and repaint.
Definition: graph.hpp:440
void add(std::pair< Coordinate, Color > p)
Add a single <Coordinate, Color> to the Graph.
Definition: graph.hpp:433
auto paint_event(Painter &p) -> bool override
Handles Paint_event objects.
Definition: graph.hpp:454
auto coordinates() const -> std::vector< std::pair< Coordinate, Color >> const &
Return a reference to the current container of Coordinates.
Definition: graph.hpp:447
Bounded box that can display added Color Coordinates within the Boundary.
Definition: graph.hpp:207
auto paint_event(Painter &p) -> bool override
Handles Paint_event objects.
Definition: graph.hpp:293
auto coordinates() const -> std::vector< std::pair< Coordinate, Color >> const &
Return a reference to the current container of Coordinates.
Definition: graph.hpp:286
void reset(std::vector< std::pair< Coordinate, Color >> x)
Replace the current state with the given Coordinates vector x.
Definition: graph.hpp:251
void clear()
Remove all points from the Graph and repaint.
Definition: graph.hpp:279
Color_graph(Parameters p)
Create a Color_graph with given Parameters.
Definition: graph.hpp:231
void add(std::pair< Coordinate, Color > p)
Add a single <Coordinate, Color> to the Graph.
Definition: graph.hpp:272
Color_graph(Boundary< Number_t > b={}, std::vector< std::pair< Coordinate, Color >> x={})
Create a Color_graph with given Boundary and Coordinates, Colors.
Definition: graph.hpp:223
auto boundary() const -> Boundary< Number_t >
Return the currently set Boundary.
Definition: graph.hpp:245
void set_boundary(Boundary< Number_t > b)
Set a new Boundary and repaint the Widget.
Definition: graph.hpp:237
void reset(Iter1_t first, Iter2_t last)
Replace with copies of <Coordinate, Color>s given by iterators.
Definition: graph.hpp:260
Color numbers [0 - 180] are valid.
Definition: color.hpp:16
Bounded box that can display added Coordinates within the Boundary.
Definition: graph.hpp:27
auto paint_event(Painter &p) -> bool override
Handles Paint_event objects.
Definition: graph.hpp:111
Graph(Boundary< Number_t > b={}, std::vector< Coordinate > x={})
Create a Graph with given Boundary and Coordinates.
Definition: graph.hpp:43
void reset(Iter1_t first, Iter2_t last)
Replace the current state with copies of Coordinates given by iterators.
Definition: graph.hpp:79
void add(Coordinate c)
Add a single Coordinate to the Graph.
Definition: graph.hpp:91
Graph(Parameters p)
Create a Graph with given Parameters.
Definition: graph.hpp:52
void reset(std::vector< Coordinate > x)
Replace the current state with the given Coordinates vector x.
Definition: graph.hpp:71
void clear()
Remove all points from the Graph and repaint.
Definition: graph.hpp:98
void set_boundary(Boundary< Number_t > b)
Set a new Boundary and repaint the Widget.
Definition: graph.hpp:57
auto coordinates() const -> std::vector< Coordinate > const &
Return a reference to the current container of Coordinates.
Definition: graph.hpp:105
auto boundary() const -> Boundary< Number_t >
Return the currently set Boundary.
Definition: graph.hpp:65
Contains functions to paint Glyphs to a Widget's screen area.
Definition: painter.hpp:21
Definition: widget.hpp:31
virtual auto paint_event(Painter &p) -> bool
Handles Paint_event objects.
Definition: widget.cpp:248
void set_wallpaper(Glyph g)
Used to fill in empty space that is not filled in by paint_event().
Definition: widget.cpp:81
virtual void update()
Post a paint event to this Widget.
Definition: widget.cpp:110
auto area() const -> Area
Return the area the widget occupies.
Definition: widget.cpp:108
Four point Boundary, edges are inclusive.
Definition: boundary.hpp:10
x is horizontal, y is vertical.
Definition: graph.hpp:210
Definition: graph.hpp:216
x is horizontal, y is vertical.
Definition: graph.hpp:382
x is horizontal, y is vertical.
Definition: graph.hpp:30
Definition: graph.hpp:36