Symphony Of Empires
piechart.cpp
Go to the documentation of this file.
1 // Symphony of Empires
2 // Copyright (C) 2021, Symphony of Empires contributors
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <https://www.gnu.org/licenses/>.
16 //
17 // ----------------------------------------------------------------------------
18 // Name:
19 // eng3d/ui/piechart.cpp
20 //
21 // Abstract:
22 // Does some important stuff.
23 // ----------------------------------------------------------------------------
24 
25 #include <cstdlib>
26 #include <cstring>
27 #include <string>
28 #include <numeric>
29 #include <algorithm>
30 #include <glm/vec2.hpp>
31 
32 #include "eng3d/ui/piechart.hpp"
33 #include "eng3d/ui/widget.hpp"
34 #include "eng3d/ui/ui.hpp"
35 #include "eng3d/ui/tooltip.hpp"
36 #include "eng3d/texture.hpp"
37 #include "eng3d/rectangle.hpp"
38 #include "eng3d/state.hpp"
39 
40 using namespace UI;
41 
44 PieChart::PieChart(int _x, int _y, unsigned w, unsigned h, Widget* _parent)
45  : Widget(_parent, _x, _y, w, h, UI::WidgetType::PIE_CHART),
46  data{ std::vector<ChartData>() },
47  max{ 0 }
48 {
49  on_hover = &PieChart::on_hover_default;
50 }
51 
52 void PieChart::set_data(std::vector<ChartData> new_data) {
53  data = new_data;
54  max = std::accumulate(data.begin(), data.end(), 0.f, [](const auto a, const auto& e) {
55  return a + e.num;
56  });
57 }
58 
59 inline void PieChart::draw_triangle(float start_ratio, float end_ratio, Eng3D::Color color) {
60  const float x_center = width / 2.f;
61  const float y_center = height / 2.f;
62  const float radius = glm::min<float>(width, height) * 0.5;
63  float x_offset, y_offset, scale;
64 
65  x_offset = glm::cos((start_ratio - 0.25f) * 2 * glm::pi<float>());
66  y_offset = glm::sin((start_ratio - 0.25f) * 2 * glm::pi<float>());
67  scale = glm::min<float>(1.f / glm::abs(x_offset), 1.f / glm::abs(y_offset));
68  x_offset *= scale;
69  y_offset *= scale;
70 
72 
73  Eng3D::State::get_instance().ui_ctx.obj_shader->set_uniform("diffuse_color", glm::vec4(color.r, color.g, color.b, 1.f));
74  mesh.buffer.emplace_back(glm::vec2(x_center + x_offset * radius, y_center + y_offset * radius), glm::vec2(0.5f + x_offset * 0.5f, 0.5f + y_offset * 0.5f));
75 
76  x_offset = glm::cos((end_ratio - 0.25f) * 2 * glm::pi<float>());
77  y_offset = glm::sin((end_ratio - 0.25f) * 2 * glm::pi<float>());
78  scale = glm::min<float>(1.f / glm::abs(x_offset), 1.f / glm::abs(y_offset));
79  x_offset *= scale;
80  y_offset *= scale;
81  mesh.buffer.emplace_back(glm::vec2(x_center + x_offset * radius, y_center + y_offset * radius), glm::vec2(0.5f + x_offset * 0.5f, 0.5f + y_offset * 0.5f));
82  mesh.buffer.emplace_back(glm::vec2(x_center, y_center), glm::vec2(0.5f, 0.5f));
83  mesh.upload();
84  mesh.draw();
85 }
86 
88  ctx.obj_shader->set_texture(0, "diffuse_map", *ctx.piechart_overlay);
89  float counter = 0;
90  float last_corner = -0.125f;
91  float last_ratio = 0;
92  for(auto& slice : data) {
93  if(slice.num == 0.f) continue;
94  counter += slice.num;
95  float ratio = counter / max;
96  while(ratio > last_corner + 0.25f) {
97  last_corner += 0.25f;
98  draw_triangle(last_ratio, last_corner, slice.color);
99  last_ratio = last_corner;
100  }
101  draw_triangle(last_ratio, ratio, slice.color);
102  last_ratio = ratio;
103  }
104 }
105 
106 static inline bool in_triangle(glm::vec2 p, glm::vec2 center, float radius, float start_ratio, float end_ratio) {
107  if(start_ratio == 0.f || end_ratio == 0.f) return false;
108 
109  auto x_offset = glm::cos((start_ratio - 0.25f) * 2.f * glm::pi<float>());
110  auto y_offset = glm::sin((start_ratio - 0.25f) * 2.f * glm::pi<float>());
111 
112  if(abs(x_offset) == 0.f || abs(y_offset) == 0.f) return false;
113  auto scale = glm::min<float>(1.f / abs(x_offset), 1.f / abs(y_offset));
114  x_offset *= scale;
115  y_offset *= scale;
116  glm::vec2 a{ center.x + x_offset * radius, center.y + y_offset * radius };
117 
118  x_offset = glm::cos((end_ratio - 0.25f) * 2.f * glm::pi<float>());
119  y_offset = glm::sin((end_ratio - 0.25f) * 2.f * glm::pi<float>());
120 
121  if(abs(x_offset) == 0.f || abs(y_offset) == 0.f) return false;
122  scale = glm::min<float>(1.f / abs(x_offset), 1.f / abs(y_offset));
123  x_offset *= scale;
124  y_offset *= scale;
125  glm::vec2 b{ center.x + x_offset * radius, center.y + y_offset * radius };
126  glm::vec2 c{ center.x, center.y };
127 
128  float A = 0.5f * (-b.y * c.x + a.y * (-b.x + c.x) + a.x * (b.y - c.y) + b.x * c.y);
129  float sign = A < 0 ? -1 : 1;
130  float s = (a.y * c.x - a.x * c.y + (c.y - a.y) * p.x + (a.x - c.x) * p.y) * sign;
131  float t = (a.x * b.y - a.y * b.x + (a.y - b.y) * p.x + (b.x - a.x) * p.y) * sign;
132 
133  return s > 0 && t > 0 && (s + t) < 2 * A * sign;
134 }
135 
136 void PieChart::on_hover_default(Widget& w, glm::ivec2 mouse_pos, glm::ivec2 widget_pos) {
137  auto& piechart = static_cast<PieChart&>(w);
138  mouse_pos -= widget_pos;
139 
140  float x_center = piechart.width / 2.f;
141  float y_center = piechart.height / 2.f;
142  glm::ivec2 center{ x_center, y_center };
143  float radius = glm::min<float>(piechart.width, piechart.height) * 0.5;
144 
145  glm::vec2 centered_pos = mouse_pos - center;
146  if(glm::length(centered_pos) > radius) return;
147 
148  float counter = 0.f, last_corner = -0.125f, last_ratio = 0.f;
149  for(auto& slice : piechart.data) {
150  if(slice.num == 0.f) continue;
151  counter += slice.num;
152  float ratio = counter / piechart.max;
153  while(ratio > last_corner + 0.25f) {
154  last_corner += 0.25f;
155  bool is_inside = in_triangle(mouse_pos, center, radius, last_ratio, last_corner);
156  if(is_inside) {
157  piechart.set_tooltip(slice.info);
158  return;
159  }
160  last_ratio = last_corner;
161  }
162 
163  bool is_inside = in_triangle(mouse_pos, center, radius, last_ratio, last_corner);
164  if(is_inside) {
165  piechart.set_tooltip(slice.info);
166  break;
167  }
168  last_ratio = ratio;
169  }
170 }
UI::Context ui_ctx
Definition: state.hpp:128
static State & get_instance()
Definition: state.cpp:514
Generalized chart data, used mostly by chart widgets, however it's not specific to any widget.
Definition: widget.hpp:129
The UI context that handles all the ui widgets.
Definition: ui.hpp:63
std::unique_ptr< Eng3D::OpenGL::Program > obj_shader
Definition: ui.hpp:159
std::shared_ptr< Eng3D::Texture > piechart_overlay
Definition: ui.hpp:152
Piechart widget.
Definition: piechart.hpp:43
PieChart(int x, int y, unsigned w, unsigned h, std::vector< ChartData > data=std::vector< ChartData >(), Widget *_parent=nullptr)
virtual void on_render(Context &ctx, Eng3D::Rect viewport) override
Definition: piechart.cpp:87
void set_data(std::vector< ChartData > data)
Definition: piechart.cpp:52
The master widget all the other widgets inherit from, do not use directly instead use one of the many...
Definition: widget.hpp:176
std::function< void(UI::Widget &, glm::ivec2 mouse_pos, glm::ivec2 widget_pos)> on_hover
Definition: widget.hpp:354
size_t width
Definition: widget.hpp:325
size_t height
Definition: widget.hpp:325
WidgetType
The type of the widget, some widgets share types between them to keep simplicity.
Definition: widget.hpp:76
Definition: utils.hpp:35
Primitive color type used through the engine.
Definition: color.hpp:32
float g
Definition: color.hpp:33
float b
Definition: color.hpp:33
float r
Definition: color.hpp:33
Packed model - packs both vertices and texcoords into the same buffer.
Definition: mesh.hpp:144