File: GraphControl.cpp

package info (click to toggle)
freeorion 0.5.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 194,920 kB
  • sloc: cpp: 186,821; python: 40,979; ansic: 1,164; xml: 721; makefile: 32; sh: 7
file content (352 lines) | stat: -rw-r--r-- 12,013 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
#include "GraphControl.h"

#include "ClientUI.h"
#include "CUIControls.h"
#include "../util/i18n.h"

#include <GG/ClrConstants.h>


GraphControl::GraphControl() :
    GG::Control(GG::X0, GG::Y0, GG::X1, GG::Y1)
{
    //std::vector<std::pair<double, double>> test_data = {{0.0, 1.0}, {1.0, 3.2}, {4.2, -1}, {0, 0}, {-1, 1}};
    //m_data.push_back({test_data, GG::CLR_CYAN});

    //test_data = {{1.0, 1.0}, {2.0, 3.2}, {3.2, -1}, {4, 0}, {5, 1}};
    //m_data.push_back({test_data, GG::CLR_YELLOW});

    AutoSetRange();
}

void GraphControl::AddSeries(std::vector<std::pair<double, double>> data, GG::Clr clr) {
    if (!data.empty()) {
        m_data.emplace_back(std::move(data), clr);
        DoLayout();
    }
}

void GraphControl::Clear() {
    m_data.clear();
    DoLayout();
}

void GraphControl::SetXMin(double x_min) {
    double old_x_min = x_min;
    m_x_min = x_min;
    if (m_x_min != old_x_min)
        DoLayout();
}

void GraphControl::SetYMin(double y_min) {
    double old_y_min = y_min;
    m_y_min = y_min;
    if (m_y_min != old_y_min)
        DoLayout();
}

void GraphControl::SetXMax(double x_max) {
    double old_x_max = x_max;
    m_x_max = x_max;
    if (m_x_max != old_x_max)
        DoLayout();
}

void GraphControl::SetYMax(double y_max) {
    double old_y_max = y_max;
    m_y_max = y_max;
    if (m_y_max != old_y_max)
        DoLayout();
}

void GraphControl::SetRange(double x1, double x2, double y1, double y2) {
    if (m_x_min != x1 || m_y_min != y1 || m_x_max != x2 || m_y_max != y2) {
        m_x_min = x1;
        m_y_min = y1;
        m_x_max = x2;
        m_y_max = y2;

        DoLayout();
    }
}

void GraphControl::AutoSetRange() {
    if (m_data.empty())
        return;

    // large default values that are expected to be overwritten by the first seen value
    double x_min = 99999999.9;
    double y_min = 99999999.9;
    double x_max = -99999999.9;
    double y_max = -99999999.9;

    for (const std::pair<std::vector<std::pair<double, double>>, GG::Clr>& curve : m_data) {
        for (const std::pair<double, double>& curve_pt : curve.first) {
            if (curve_pt.first < x_min)
                x_min = curve_pt.first;
            if (curve_pt.first > x_max)
                x_max = curve_pt.first;
            if (curve_pt.second < y_min)
                y_min = curve_pt.second;
            if (curve_pt.second > y_max)
                y_max = curve_pt.second;
        }
    }

    SetRange(x_min, x_max, y_min, y_max);
}

void GraphControl::ShowPoints(bool show) {
    bool old_show_points = m_show_points;
    m_show_points = show;
    if (show != old_show_points)
        DoLayout();
}

void GraphControl::ShowLines(bool show) {
    bool old_show_lines = m_show_lines;
    m_show_lines = show;
    if (show != old_show_lines)
        DoLayout();
}

void GraphControl::ShowScale(bool show) {
    bool old_show_scale = m_show_scale;
    m_show_scale = show;
    if (show != old_show_scale)
        DoLayout();
}

void GraphControl::UseLogScale(bool log) {
    bool old_log_scale = m_log_scale;
    m_log_scale = log;
    if (log != old_log_scale)
        DoLayout();
}

void GraphControl::ScaleToZero(bool zero) {
    bool old_zero = m_zero_in_range;
    m_zero_in_range = zero;
    if (zero != old_zero)
        DoLayout();
}

void GraphControl::SizeMove(GG::Pt ul, GG::Pt lr) {
    const auto old_sz = Size();
    GG::Control::SizeMove(ul, lr);
    if (Size() != old_sz)
        DoLayout();
}

void GraphControl::RClick(GG::Pt pt, GG::Flags<GG::ModKey> mod_keys) {
    auto popup = GG::Wnd::Create<CUIPopupMenu>(pt.x, pt.y);

    auto set_log_scale = [this]()
    { UseLogScale(true); };
    auto set_linear_scale = [this]()
    { UseLogScale(false); };
    popup->AddMenuItem(GG::MenuItem(UserString("USE_LINEAR_SCALE"), false, !m_log_scale, set_linear_scale));
    popup->AddMenuItem(GG::MenuItem(UserString("USE_LOG_SCALE"), false, m_log_scale, set_log_scale));

    auto set_zero_limit = [this]()
    { ScaleToZero(true); };
    auto free_limit = [this]()
    { ScaleToZero(false); };
    popup->AddMenuItem(GG::MenuItem(UserString("SCALE_TO_ZERO"), false, m_zero_in_range, set_zero_limit));
    popup->AddMenuItem(GG::MenuItem(UserString("SCALE_FREE"), false, !m_zero_in_range, free_limit));

    auto show_scale = [this]()
    { ShowScale(true); };
    auto hide_scale = [this]()
    { ShowScale(false); };
    popup->AddMenuItem(GG::MenuItem(UserString("SHOW_SCALE"), false, m_show_scale, show_scale));
    popup->AddMenuItem(GG::MenuItem(UserString("HIDE_SCALE"), false, !m_show_scale, hide_scale));

    auto show_lines = [this]() {
        ShowLines(true);
        ShowPoints(false);
    };
    auto show_points = [this]() {
        ShowLines(false);
        ShowPoints(true);
    };
    popup->AddMenuItem(GG::MenuItem(UserString("SHOW_LINES"), false, m_show_lines, show_lines));
    popup->AddMenuItem(GG::MenuItem(UserString("SHOW_POINTS"), false, m_show_points, show_points));

    popup->Run();
}

void GraphControl::Render() {
    // background
    FlatRectangle(UpperLeft(), LowerRight(), ClientUI::WndColor(), ClientUI::WndOuterBorderColor(), 1);

    if (!m_show_lines && !m_show_points)
        return;

    static constexpr int PAD = 1;
    const int WIDTH = Value(Width()) - 2*PAD;
    const int HEIGHT = Value(Height()) - 2*PAD;

    GG::Pt ul = UpperLeft() + GG::Pt(GG::X(PAD), GG::Y(PAD));
    GG::Pt lr = ul + GG::Pt(GG::X(WIDTH), GG::Y(HEIGHT));

    GG::BeginScissorClipping(ul, lr);

    if (m_show_scale && !m_y_scale_ticks.empty()) {
        glEnable(GL_TEXTURE_2D);
        const auto font = ClientUI::GetFont();
        GG::Font::RenderState rs{ClientUI::TextColor()};
        for (auto label : m_y_scale_ticks) {
            const auto roundedlabel = boost::format("%|1$.12|") % label.second;
            font->RenderText(GG::Pt{ul.x + GG::X1, lr.y + label.first}, roundedlabel.str(), rs);
        }
    }

    glPushMatrix();
    glTranslatef(Value(ul.x), Value(lr.y), 0.0f);

    glDisable(GL_TEXTURE_2D);
    glEnable(GL_LINE_SMOOTH);
    glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    glLineWidth(2.0f);
    glPointSize(5.0f);

    m_vert_buf.activate();
    m_colour_buf.activate();

    if (m_show_lines)
        glDrawArrays(GL_LINES, 0, m_vert_buf.size());

    if (m_show_points)
        glDrawArrays(GL_POINTS, 0, m_vert_buf.size());

    glLineWidth(1.0f);
    glPointSize(1.0f);

    glPopClientAttrib();
    glDisable(GL_LINE_SMOOTH);
    glEnable(GL_TEXTURE_2D);

    glPopMatrix();

    GG::EndScissorClipping();
}

void GraphControl::DoLayout() {
    static constexpr int PAD = 1;
    const int WIDTH = Value(Width()) - 2*PAD;
    const int HEIGHT = Value(Height()) - 2*PAD;
    m_vert_buf.clear();
    m_colour_buf.clear();
    m_y_scale_ticks.clear();

    // X range to plot
    double shown_x_max = m_x_max + 1;
    double shown_x_min = m_x_min - 1;
    double x_range = shown_x_max - shown_x_min;

    // Y range to plot
    double shown_y_max = m_y_max;
    double shown_y_min = m_y_min;
    double step = (shown_y_max - shown_y_min) / 10.0;
    if (m_log_scale) {
        double effective_max_val = std::max(1.0, std::max(std::abs(m_y_max), std::abs(m_y_min)));
        double effective_min_val = std::max(1.0, std::min(std::abs(m_y_max), std::abs(m_y_min)));
        // force log scale to start at 0.0 or larger
        shown_y_min = std::max(0.0, std::floor(std::log10(effective_min_val)));
        // take larger of abs of signed min and max y values as max of range, or at least miminum 0 = log10(1.0)
        shown_y_max = std::ceil(std::log10(effective_max_val));
        step = 1.0;
    } else {
        // plot from or to 0 ?
        double effective_max_val = m_y_max;// > 0.0 ? m_y_max : 0.0;
        double effective_min_val = m_y_min;// > 0.0 ? 0.0 : m_y_min;
        if (m_zero_in_range) {
            effective_max_val = m_y_max > 0.0 ? m_y_max : 0.0;
            effective_min_val = m_y_min > 0.0 ? 0.0 : m_y_min;
        }

        // pick step between scale lines based on magnitude of range and next bigger round number
        double effective_range = std::abs(effective_max_val - effective_min_val);
        double pow10_below = std::round(std::pow(10.0, std::floor(std::log10(effective_range))));
        double ratio_above_pow10 = effective_range / pow10_below;
        if (ratio_above_pow10 <= 2)
            step = 0.2 * pow10_below;   // eg. effective_range is 18, pow10_below = 10, ratio_above_pow10 = 1.8, step = 2
        else if (ratio_above_pow10 <= 5)
            step = 0.5 * pow10_below;   // eg. effective_range is 32, pow10_below = 10, ratio_above_pow10 = 3.2, step = 5
        else
            step = pow10_below;         // eg. effective_range is 87, pow10_below = 10, ratio_above_pow10 = 8.7, step = 10
        // round min down and max up to nearest multiple of step
        shown_y_min = std::round(step * std::floor(effective_min_val / step));
        shown_y_max = std::round(step * std::ceil(effective_max_val / step));
    }
    double y_range = shown_y_max - shown_y_min;


    // plot horizontal scale marker lines
    if (m_show_scale) {
        for (double line_y = shown_y_min + step; line_y <= shown_y_max; line_y = line_y + step) {
            const auto& screen_y = static_cast<float>((line_y - shown_y_min) * HEIGHT / y_range);
            m_vert_buf.store(GG::X1, -screen_y);    // OpenGL is positive down / negative up
            m_colour_buf.store(GG::LightenClr(ClientUI::WndColor()));
            m_vert_buf.store(GG::X(WIDTH - 1), -screen_y);
            m_colour_buf.store(GG::LightenClr(ClientUI::WndColor()));

            if (m_log_scale)
                m_y_scale_ticks[GG::Y(-screen_y)] = std::pow(10.0, std::round(line_y));
            else
                m_y_scale_ticks[GG::Y(-screen_y)] = line_y;
        }
    }

    // plot lines connecting successive data points
    for (auto& curve : m_data) {
        auto curve_pts = curve.first;
        if (curve_pts.empty())
            continue;
        if (m_log_scale) {
            for (auto& pt : curve_pts)
                pt.second = std::log10(std::max(1.0, std::abs(pt.second)));   // ignore sign of points, trunscate log scale at minimum 0 = log10(1.0)
        }

        const auto& curve_colour = curve.second;

        // pairs of n and n+1 point, starting with first-second, to the second-last-last
        for (auto curve_it = curve_pts.begin(); curve_it != curve_pts.end(); ++curve_it) {
            auto next_it = curve_it;
            ++next_it;
            if (next_it == curve_pts.end())
                break;

            {
                const auto& curve_x = curve_it->first;
                const auto& screen_x = static_cast<float>((curve_x - shown_x_min) * WIDTH / x_range);
                const auto& curve_y = curve_it->second;
                const auto& screen_y = static_cast<float>((curve_y - shown_y_min) * HEIGHT / y_range);
                m_vert_buf.store(screen_x, -screen_y);  // OpenGL is positive down / negative up
                m_colour_buf.store(curve_colour);
            }

            {
                const auto& curve_x = next_it->first;
                const auto& screen_x = static_cast<float>((curve_x - shown_x_min) * WIDTH / x_range);
                const auto& curve_y = next_it->second;
                const auto& screen_y = static_cast<float>((curve_y - shown_y_min) * HEIGHT / y_range);
                m_vert_buf.store(screen_x, -screen_y);  // OpenGL is positive down / negative up
                m_colour_buf.store(curve_colour);
            }
        }
    }

    m_vert_buf.createServerBuffer();
    m_colour_buf.createServerBuffer();

    // todo: determine screen and data points at which to draw scale ticks
    //
    // ticks should be placed at increments of powers of 10 multiplied by 2, 5, or 10.
    // eg. 4,6,8,10,12,14; -5,0,5,10,15,20; 0,10,20,30,40,50
}