1285 lines
54 KiB
C++
1285 lines
54 KiB
C++
// # Low level plotting features and components for creating basic to advanced plots and charts
|
|
|
|
// BUGS:
|
|
// - titles does not move in both x/y directions correctly relative to the figure
|
|
|
|
#pragma once
|
|
|
|
#include "imgui.h"
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
#include "imgui_internal.h"
|
|
|
|
#include <iostream> //delete when finished debugging
|
|
#include <vector>
|
|
|
|
namespace ImGui
|
|
{
|
|
// ## Interpolation
|
|
// - interpolate
|
|
// - sample
|
|
// - splines
|
|
// static inline time_t ImLerp(const time_t& a, const time_t& b, const time_t& t); //TODO [DM]
|
|
// static inline float ImBSBasis(float t1, float v0, float v1, float v2, float v3); //TODO [DM] BSpline basis function
|
|
// static inline ImVec4 ImBSBasis(const ImVec4& t1, const ImVec4& v0, const ImVec4& v1, const ImVec4& v2, const ImVec4& v3); //TODO [DM] BSpline basis function used for colors
|
|
// static inline int ImBSerp(int* v, float t, bool closed = false); //TODO [DM] BSpline interpolate from [a, b) or [a, b] (closed=true)
|
|
// static inline float ImBSerp(float* v, float t, bool closed = false); //TODO [DM] BSpline interpolate from [a, b) or [a, b] (closed=true)
|
|
// static inline ImVec4 ImBSerp(const ImVec4* v, const ImVec4& t, bool closed = false); //TODO [DM] BSpline interpolate used for colors from [a, b) or [a, b] (closed=true)
|
|
// static inline int* ImQuaLerp(int a, int b, float t, int n); //TODO [DM] Quantized linear sampler
|
|
// static inline float* ImQuaLerp(float a, float b, float t, int n); //TODO [DM] Quantized linear sampler
|
|
// static inline time_t* ImQuaLerp(const time_t& a, const time_t& b, const time_t& t, int n); //TODO [DM] Quantized linear sampler
|
|
// static inline int* ImQuaBSerp(int a, int b, float t, int n, bool closed = false); //TODO [DM] Quantized BSpline sampler
|
|
// static inline float* ImQuaBSerp(float a, float b, float t, int n, bool closed = false); //TODO [DM] Quantized BSpline sampler
|
|
// static inline ImVec4* ImQuaBSerp(const ImVec4& a, const ImVec4& b, const ImVec4& t, int n, bool closed = false); //TODO [DM] Quantized BSpline sampler used for colors
|
|
|
|
bool is_inside_area(const ImVec2& P, const ImVector<ImVec2>& V);
|
|
|
|
// ## Scales
|
|
template<typename Ta, typename Tb>
|
|
class ImScales
|
|
{
|
|
public:
|
|
void SetDomain(const Ta& min, const Ta& max){domain_min_ = min; domain_max_ = max;} // input data ranges
|
|
void SetRange(const Tb min, const Tb max){range_min_ = min; range_max_ = max;} // output data ranges
|
|
Ta GetDomainMin(){return domain_min_;}
|
|
Ta GetDomainMax(){return domain_max_;}
|
|
Tb GetRangeMin(){return range_min_;}
|
|
Tb GetRangeMax(){return range_max_;}
|
|
virtual Tb Scale(const Ta& t) = 0;
|
|
protected:
|
|
Ta domain_min_;
|
|
Ta domain_max_;
|
|
Tb range_min_;
|
|
Tb range_max_;
|
|
};
|
|
|
|
/**
|
|
* @brief Continuous linear scales used for ImVec2 and ImVec4 (colors
|
|
*
|
|
*/
|
|
template<typename Ta, typename Tb>
|
|
class ImLinearScales : public ImScales<Ta, Tb>
|
|
{
|
|
public:
|
|
Tb Scale(const Ta& t)
|
|
{
|
|
const Ta tx = (t - this->domain_min_) / (this->domain_max_ - this->domain_min_);
|
|
const Tb ty = ImLerp(this->range_min_, this->range_max_, tx);
|
|
return ty;
|
|
}
|
|
// void Pow();
|
|
// void Log();
|
|
// void Time();
|
|
// void Sequential();
|
|
// void Continuous(); // used for colors
|
|
// // quantized scales
|
|
// void Quantize(); // maps a continouus variable to a discrete scale
|
|
// void Quantile(ImVec2& range, float& val); // maps a continuous varible to a sampled domain
|
|
// void Threshold();
|
|
// // discrete scales
|
|
// void Ordered();
|
|
// void Categorical();
|
|
// void Band();
|
|
// void Round();
|
|
// void Point();
|
|
};
|
|
|
|
// ## Plot element
|
|
struct ImPlotProperties
|
|
{
|
|
ImVec2 plot_size = ImVec2(480.0f, 480.0f);
|
|
float margin_bottom = 50.0f;
|
|
float margin_top = 50.0f;
|
|
float margin_left = 50.0f;
|
|
float margin_right = 50.0f;
|
|
ImU32 bg_col = GetColorU32(ImGuiCol_FrameBg);
|
|
const char* title = NULL;
|
|
ImFont* title_font = NULL;
|
|
float title_font_size = 18.0f;
|
|
ImU32 title_font_col = 0;
|
|
};
|
|
|
|
template<typename Ta, typename Tb>
|
|
class ImPlot
|
|
{
|
|
public:
|
|
|
|
ImVec2 _(ImVec2 &a, ImVec2 &b) { return ImVec2(a.x + b.x, a.y + b.y); }
|
|
ImVec2 _(const ImVec2 a, const ImVec2 b) { return ImVec2(a.x + b.x, a.y + b.y); }
|
|
ImVec2 __(ImVec2 &a, ImVec2 &b) { return ImVec2(a.x - b.x, a.y - b.y); }
|
|
ImVec2 __(const ImVec2 a, const ImVec2 b) { return ImVec2(a.x - b.x, a.y - b.y); }
|
|
/**
|
|
* @brief Draw the figure with an optional title
|
|
*
|
|
*/
|
|
void DrawFigure()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
const ImGuiStyle& style = g.Style;
|
|
|
|
// Create the window
|
|
const ImRect frame_bb(window->DC.CursorPos, _(window->DC.CursorPos , ImVec2(properties_.plot_size.x, properties_.plot_size.y)));
|
|
const ImRect inner_bb(_(frame_bb.Min , style.FramePadding), __(frame_bb.Max , style.FramePadding));
|
|
const ImRect figure_bb(ImVec2(inner_bb.Min.x + properties_.margin_left, inner_bb.Min.y + properties_.margin_top),
|
|
ImVec2(inner_bb.Max.x - properties_.margin_right, inner_bb.Max.y - properties_.margin_bottom));
|
|
const ImRect total_bb(frame_bb.Min, frame_bb.Max);
|
|
RenderFrame(frame_bb.Min, frame_bb.Max, properties_.bg_col, true, style.FrameRounding);
|
|
|
|
// Draw the title
|
|
const ImVec2 title_size = CalcTextSize(properties_.title, NULL, true);
|
|
if (title_size.x > 0.0f)
|
|
{
|
|
// centered by default (add parameter for title position)
|
|
const ImVec2 title_pos(
|
|
inner_bb.Min.x + (inner_bb.Max.x - inner_bb.Min.x)*0.5f - title_size.x*0.5,
|
|
inner_bb.Min.y);
|
|
window->DrawList->AddText(properties_.title_font, properties_.title_font_size, title_pos, properties_.title_font_col, properties_.title);
|
|
}
|
|
|
|
// update scales range
|
|
scales_x_->SetRange(figure_bb.Min.x, figure_bb.Max.x);
|
|
scales_y_->SetRange(figure_bb.Max.y, figure_bb.Min.y);
|
|
};
|
|
|
|
void SetProperties(ImPlotProperties& properties){properties_ = properties;}
|
|
void SetScales(ImScales<Ta, float>* scales_x, ImScales<Tb, float>* scales_y){scales_x_ = scales_x; scales_y_ = scales_y;}
|
|
ImScales<Ta, float>* GetScalesX(){return scales_x_;}
|
|
ImScales<Tb, float>* GetScalesY(){return scales_y_;}
|
|
|
|
protected:
|
|
ImScales<Ta, float>* scales_x_;
|
|
ImScales<Tb, float>* scales_y_;
|
|
ImPlotProperties properties_;
|
|
};
|
|
|
|
// ## Axes
|
|
struct ImAxisProperties
|
|
{
|
|
const char* axis_title = NULL;
|
|
ImFont* axis_title_font = NULL;
|
|
float axis_title_font_size = 0.0f;
|
|
ImU32 axis_title_font_col = 0;
|
|
const char* axis_tick_format = "%4.2f"; ///< string format
|
|
float axis_thickness = 1.0f;
|
|
ImU32 axis_col = 0;
|
|
ImFont* axis_tick_font = NULL;
|
|
float axis_tick_font_size = 12.0f;
|
|
ImU32 axis_font_col = 0;
|
|
float grid_lines_thickness = 1.0f;
|
|
ImU32 grid_lines_col = 0;
|
|
};
|
|
|
|
template<typename Ta, typename Tb>
|
|
class ImAxis
|
|
{
|
|
public:
|
|
ImVec2 _(ImVec2 &a, ImVec2 &b) { return ImVec2(a.x + b.x, a.y + b.y); }
|
|
ImVec2 _(const ImVec2 a, const ImVec2 b) { return ImVec2(a.x + b.x, a.y + b.y); }
|
|
ImVec2 __(ImVec2 &a, ImVec2 &b) { return ImVec2(a.x - b.x, a.y - b.y); }
|
|
ImVec2 __(const ImVec2 a, const ImVec2 b) { return ImVec2(a.x - b.x, a.y - b.y); }
|
|
/**
|
|
* @brief Draw X axis
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param orientation Options are Top, Bottom
|
|
* @param axis_tick_min Minimum axis tick value
|
|
* @param axis_tick_max Maximum axis tick value
|
|
* @param axis_tick_span Spacing between axis ticks
|
|
*
|
|
*/
|
|
void DrawXAxis(ImPlot<Ta, Tb>& figure, const char* orientation,
|
|
const Ta& axis_tick_min, const Ta& axis_tick_max, const Ta& axis_tick_span)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
// Tick major
|
|
const ImVec2 tick_size = CalcTextSize(properties_.axis_title, NULL, true);
|
|
|
|
Ta tick_value = axis_tick_min;
|
|
while (tick_value <= axis_tick_max)
|
|
{
|
|
// Tick label
|
|
char tick_label[64];
|
|
sprintf(tick_label, properties_.axis_tick_format, tick_value);
|
|
|
|
// Tick Position
|
|
if (strcmp(orientation, "Top") == 0)
|
|
{
|
|
ImVec2 tick_pos = ImVec2(figure.GetScalesX()->Scale(tick_value), figure.GetScalesY()->GetRangeMax() - tick_size.y); // interpolate the position
|
|
window->DrawList->AddText(properties_.axis_tick_font, properties_.axis_tick_font_size, tick_pos, properties_.axis_font_col, tick_label);
|
|
}
|
|
else if (strcmp(orientation, "Bottom") == 0)
|
|
{
|
|
ImVec2 tick_pos = ImVec2(figure.GetScalesX()->Scale(tick_value), figure.GetScalesY()->GetRangeMin() + tick_size.y); // interpolate the position
|
|
window->DrawList->AddText(properties_.axis_tick_font, properties_.axis_tick_font_size, tick_pos, properties_.axis_font_col, tick_label);
|
|
}
|
|
|
|
tick_value += axis_tick_span;
|
|
}
|
|
|
|
_DrawXAxisAxis(figure, orientation);
|
|
_DrawXAxisTitle(figure, orientation);
|
|
};
|
|
|
|
/**
|
|
* @brief Draw X axis
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param orientation Options are Top, Bottom
|
|
* @param axis_tick_pos Positions of the axis ticks
|
|
* @param axis_tick_labels Labels of the axis ticks
|
|
*
|
|
*/
|
|
void DrawXAxis(ImPlot<Ta, Tb>& figure, const char* orientation,
|
|
const Ta* axis_tick_pos, const char* axis_tick_labels[], const int& n_ticks)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
// Tick major
|
|
const ImVec2 tick_size = CalcTextSize(properties_.axis_title, NULL, true);
|
|
|
|
for (int n=0; n < n_ticks; ++n)
|
|
{
|
|
// Tick label
|
|
char tick_label[64];
|
|
sprintf(tick_label, properties_.axis_tick_format, axis_tick_labels[n]);
|
|
|
|
// Tick Position
|
|
if (strcmp(orientation, "Top") == 0)
|
|
{
|
|
ImVec2 tick_pos = ImVec2(figure.GetScalesX()->Scale(axis_tick_pos[n]), figure.GetScalesY()->GetRangeMax() - tick_size.y); // interpolate the position
|
|
window->DrawList->AddText(properties_.axis_tick_font, properties_.axis_tick_font_size, tick_pos, properties_.axis_font_col, tick_label);
|
|
}
|
|
else if (strcmp(orientation, "Bottom") == 0)
|
|
{
|
|
ImVec2 tick_pos = ImVec2(figure.GetScalesX()->Scale(axis_tick_pos[n]), figure.GetScalesY()->GetRangeMin() + tick_size.y); // interpolate the position
|
|
window->DrawList->AddText(properties_.axis_tick_font, properties_.axis_tick_font_size, tick_pos, properties_.axis_font_col, tick_label);
|
|
}
|
|
}
|
|
|
|
_DrawXAxisAxis(figure, orientation);
|
|
_DrawXAxisTitle(figure, orientation);
|
|
};
|
|
|
|
void _DrawXAxisAxis(ImPlot<Ta, Tb>& figure, const char* orientation)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
// Axis
|
|
if (strcmp(orientation, "Top") == 0)
|
|
{
|
|
window->DrawList->AddLine(ImVec2(figure.GetScalesX()->GetRangeMin(), figure.GetScalesY()->GetRangeMax()),
|
|
ImVec2(figure.GetScalesX()->GetRangeMax(), figure.GetScalesY()->GetRangeMax()), properties_.axis_col, properties_.axis_thickness);
|
|
}
|
|
else if (strcmp(orientation, "Bottom") == 0)
|
|
{
|
|
window->DrawList->AddLine(ImVec2(figure.GetScalesX()->GetRangeMin(), figure.GetScalesY()->GetRangeMin()),
|
|
ImVec2(figure.GetScalesX()->GetRangeMax(), figure.GetScalesY()->GetRangeMin()), properties_.axis_col, properties_.axis_thickness);
|
|
}
|
|
};
|
|
void _DrawXAxisTitle(ImPlot<Ta, Tb>& figure, const char* orientation)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
// Tick major
|
|
const ImVec2 tick_size = CalcTextSize(properties_.axis_title, NULL, true);
|
|
|
|
// Axis title
|
|
const ImVec2 title_size = CalcTextSize(properties_.axis_title, NULL, true);
|
|
|
|
if (strcmp(orientation, "Top") == 0)
|
|
{
|
|
ImVec2 title_pos = ImVec2(
|
|
figure.GetScalesX()->GetRangeMin() + (figure.GetScalesX()->GetRangeMax() - figure.GetScalesX()->GetRangeMin())*0.5f - title_size.x*0.5f,
|
|
figure.GetScalesY()->GetRangeMax() - (title_size.y + tick_size.y));
|
|
window->DrawList->AddText(properties_.axis_title_font, properties_.axis_title_font_size, title_pos, properties_.axis_title_font_col, properties_.axis_title);
|
|
}
|
|
else if (strcmp(orientation, "Bottom") == 0)
|
|
{
|
|
ImVec2 title_pos = ImVec2(
|
|
figure.GetScalesX()->GetRangeMin() + (figure.GetScalesX()->GetRangeMax() - figure.GetScalesX()->GetRangeMin())*0.5f - title_size.x*0.5f,
|
|
figure.GetScalesY()->GetRangeMin() + (title_size.y + tick_size.y));
|
|
window->DrawList->AddText(properties_.axis_title_font, properties_.axis_title_font_size, title_pos, properties_.axis_title_font_col, properties_.axis_title);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Draw Y axis
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param orientation Options are Left, Right
|
|
* @param axis_tick_min Minimum axis tick value
|
|
* @param axis_tick_max Maximum axis tick value
|
|
* @param axis_tick_span Spacing between axis ticks
|
|
*
|
|
*/
|
|
void DrawYAxis(ImPlot<Ta, Tb>& figure, const char* orientation,
|
|
const Tb& axis_tick_min, const Tb& axis_tick_max, const Tb& axis_tick_span)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
// Tick major
|
|
const ImVec2 tick_size = CalcTextSize(properties_.axis_title, NULL, true);
|
|
|
|
Tb tick_value = axis_tick_min;
|
|
while (tick_value <= axis_tick_max)
|
|
{
|
|
// Tick label
|
|
char tick_label[64];
|
|
sprintf(tick_label, properties_.axis_tick_format, tick_value);
|
|
|
|
// Tick Position
|
|
if (strcmp(orientation, "Left") == 0)
|
|
{
|
|
ImVec2 tick_pos = ImVec2(figure.GetScalesX()->GetRangeMin() - tick_size.y-20, figure.GetScalesY()->Scale(tick_value)); // interpolate the position
|
|
window->DrawList->AddText(properties_.axis_tick_font, properties_.axis_tick_font_size, tick_pos, properties_.axis_font_col, tick_label);
|
|
}
|
|
else if (strcmp(orientation, "Right") == 0)
|
|
{
|
|
ImVec2 tick_pos = ImVec2(figure.GetScalesX()->GetRangeMax() + tick_size.y, figure.GetScalesY()->Scale(tick_value)); // interpolate the position
|
|
window->DrawList->AddText(properties_.axis_tick_font, properties_.axis_tick_font_size, tick_pos, properties_.axis_font_col, tick_label);
|
|
}
|
|
|
|
tick_value += axis_tick_span;
|
|
}
|
|
|
|
_DrawYAxisAxis(figure, orientation);
|
|
_DrawYAxisTitle(figure, orientation);
|
|
};
|
|
|
|
/**
|
|
* @brief Draw Y axis
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param orientation Options are Left, Right
|
|
* @param axis_tick_pos Positions of the axis ticks
|
|
* @param axis_tick_labels Labels of the axis ticks
|
|
*
|
|
*/
|
|
void DrawYAxis(ImPlot<Ta, Tb>& figure, const char* orientation,
|
|
const Tb* axis_tick_pos, const char* axis_tick_labels[], const int& n_ticks)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
// Tick major
|
|
const ImVec2 tick_size = CalcTextSize(properties_.axis_title, NULL, true);
|
|
|
|
for (int n=0; n < n_ticks; ++n)
|
|
{
|
|
// Tick label
|
|
char tick_label[64];
|
|
sprintf(tick_label, properties_.axis_tick_format, axis_tick_labels[n]);
|
|
|
|
if (strcmp(orientation, "Left") == 0)
|
|
{
|
|
ImVec2 tick_pos = ImVec2(figure.GetScalesX()->GetRangeMin() - tick_size.y, figure.GetScalesY()->Scale(axis_tick_pos[n])); // interpolate the position
|
|
window->DrawList->AddText(properties_.axis_tick_font, properties_.axis_tick_font_size, tick_pos, properties_.axis_font_col, tick_label);
|
|
}
|
|
else if (strcmp(orientation, "Right") == 0)
|
|
{
|
|
ImVec2 tick_pos = ImVec2(figure.GetScalesX()->GetRangeMax() + tick_size.y, figure.GetScalesY()->Scale(axis_tick_pos[n])); // interpolate the position
|
|
window->DrawList->AddText(properties_.axis_tick_font, properties_.axis_tick_font_size, tick_pos, properties_.axis_font_col, tick_label);
|
|
}
|
|
}
|
|
|
|
_DrawYAxisAxis(figure, orientation);
|
|
_DrawYAxisTitle(figure, orientation);
|
|
};
|
|
|
|
void _DrawYAxisAxis(ImPlot<Ta, Tb>& figure, const char* orientation)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
// Axis
|
|
if (strcmp(orientation, "Left") == 0)
|
|
{
|
|
window->DrawList->AddLine(ImVec2(figure.GetScalesX()->GetRangeMin(), figure.GetScalesY()->GetRangeMin()),
|
|
ImVec2(figure.GetScalesX()->GetRangeMin(), figure.GetScalesY()->GetRangeMax()), properties_.axis_col, properties_.axis_thickness);
|
|
}
|
|
else if (strcmp(orientation, "Right") == 0)
|
|
{
|
|
window->DrawList->AddLine(ImVec2(figure.GetScalesX()->GetRangeMax(), figure.GetScalesY()->GetRangeMin()),
|
|
ImVec2(figure.GetScalesX()->GetRangeMax(), figure.GetScalesY()->GetRangeMax()), properties_.axis_col, properties_.axis_thickness);
|
|
}
|
|
};
|
|
void _DrawYAxisTitle(ImPlot<Ta, Tb>& figure, const char* orientation)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
char c;
|
|
char *title = (char *)properties_.axis_title;
|
|
const ImFontGlyph *glyph;
|
|
|
|
// Tick major
|
|
const ImVec2 tick_size = CalcTextSize(properties_.axis_title, NULL, true);
|
|
|
|
// Axis title
|
|
const ImVec2 title_size = CalcTextSize(properties_.axis_title, NULL, true);
|
|
|
|
if (strcmp(orientation, "Left") == 0)
|
|
{
|
|
ImVec2 title_pos = ImVec2(
|
|
figure.GetScalesX()->GetRangeMin() - (title_size.y * 2 + tick_size.y)-20,
|
|
figure.GetScalesY()->GetRangeMin() + (figure.GetScalesY()->GetRangeMax() - figure.GetScalesY()->GetRangeMin())*0.5f + title_size.x*0.5f);
|
|
window->DrawList->AddText(properties_.axis_title_font, properties_.axis_title_font_size, title_pos, properties_.axis_title_font_col, properties_.axis_title);
|
|
/* while((c = *(title++))) {
|
|
glyph = properties_.axis_title_font->FindGlyph(c);
|
|
if (!glyph) continue;
|
|
window->DrawList->PrimReserve(6, 4);
|
|
window->DrawList->PrimQuadUV(
|
|
_(title_pos , ImVec2(glyph->Y0, -glyph->X0)),
|
|
_(title_pos , ImVec2(glyph->Y0, -glyph->X1)),
|
|
_(title_pos , ImVec2(glyph->Y1, -glyph->X1)),
|
|
_(title_pos , ImVec2(glyph->Y1, -glyph->X0)),
|
|
ImVec2(glyph->U0, glyph->V0),
|
|
ImVec2(glyph->U1, glyph->V0),
|
|
ImVec2(glyph->U1, glyph->V1),
|
|
ImVec2(glyph->U0, glyph->V1),
|
|
properties_.axis_title_font_col);
|
|
title_pos.y -= glyph->AdvanceX;
|
|
}*/
|
|
}
|
|
else if (strcmp(orientation, "Right") == 0)
|
|
{
|
|
ImVec2 title_pos = ImVec2(
|
|
figure.GetScalesX()->GetRangeMax() + (title_size.y * 2 + tick_size.y),
|
|
figure.GetScalesY()->GetRangeMin() + (figure.GetScalesY()->GetRangeMax() - figure.GetScalesY()->GetRangeMin())*0.5f + title_size.x*0.5f);
|
|
//window->DrawList->AddText(properties_.axis_title_font, properties_.axis_title_font_size, title_pos, properties_.axis_title_font_col, properties_.axis_title);
|
|
while((c = *(title++))) {
|
|
glyph = properties_.axis_title_font->FindGlyph(c);
|
|
if (!glyph) continue;
|
|
window->DrawList->PrimReserve(6, 4);
|
|
window->DrawList->PrimQuadUV(
|
|
_(title_pos , ImVec2(glyph->Y0, -glyph->X0)),
|
|
_(title_pos , ImVec2(glyph->Y0, -glyph->X1)),
|
|
_(title_pos , ImVec2(glyph->Y1, -glyph->X1)),
|
|
_(title_pos , ImVec2(glyph->Y1, -glyph->X0)),
|
|
ImVec2(glyph->U0, glyph->V0),
|
|
ImVec2(glyph->U1, glyph->V0),
|
|
ImVec2(glyph->U1, glyph->V1),
|
|
ImVec2(glyph->U0, glyph->V1),
|
|
properties_.axis_title_font_col);
|
|
title_pos.y -= glyph->AdvanceX;
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Draw X Axis Gridlines
|
|
*
|
|
* @param figure The figure to draw on
|
|
*
|
|
*/
|
|
void DrawXGridLines(ImPlot<Ta, Tb>& figure,
|
|
const Ta& axis_tick_min, const Ta& axis_tick_max, const Ta& axis_tick_span)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
Ta tick_value = axis_tick_min;
|
|
while (tick_value <= axis_tick_max)
|
|
{
|
|
window->DrawList->AddLine(ImVec2(figure.GetScalesX()->Scale(tick_value), figure.GetScalesY()->GetRangeMax()),
|
|
ImVec2(figure.GetScalesX()->Scale(tick_value), figure.GetScalesY()->GetRangeMin()),
|
|
properties_.grid_lines_col, properties_.grid_lines_thickness);
|
|
|
|
tick_value += axis_tick_span;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Draw Y Axis Gridlines
|
|
*
|
|
* @param figure The figure to draw on
|
|
*
|
|
*/
|
|
void DrawYGridLines(ImPlot<Ta, Tb>& figure,
|
|
const Tb& axis_tick_min, const Tb& axis_tick_max, const Tb& axis_tick_span)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
Tb tick_value = axis_tick_min;
|
|
while (tick_value <= axis_tick_max)
|
|
{
|
|
window->DrawList->AddLine(ImVec2(figure.GetScalesX()->GetRangeMin(), figure.GetScalesY()->Scale(tick_value)),
|
|
ImVec2(figure.GetScalesX()->GetRangeMax(), figure.GetScalesY()->Scale(tick_value)),
|
|
properties_.grid_lines_col, properties_.grid_lines_thickness);
|
|
|
|
tick_value += axis_tick_span;
|
|
}
|
|
};
|
|
|
|
void SetProperties(ImAxisProperties& properties){properties_ = properties;}
|
|
|
|
private:
|
|
ImAxisProperties properties_;
|
|
};
|
|
|
|
// ## Plot legends and other features
|
|
struct ImColorBarProperties
|
|
{
|
|
// TODO
|
|
};
|
|
|
|
struct ImLegendProperties
|
|
{
|
|
ImU32 stroke_col = 0;
|
|
float stroke_width = 1.0f;
|
|
ImU32 fill_col = 0; ///< Background color of the legend
|
|
ImFont* series_font = NULL; ///< Font type for series labels
|
|
float series_font_size = 0.0f; ///< Font size of the series labels
|
|
ImU32 series_font_col = 0; ///< Color of the series labels
|
|
};
|
|
|
|
template<typename Ta, typename Tb>
|
|
class ImLegend
|
|
{
|
|
public:
|
|
|
|
/**@brief Draw a plot legend
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param pos Pos of the legend (TL, TR, BL, BR)
|
|
* @param col Background color of the legend
|
|
* @param series List of series labels
|
|
* @param series_color List of series colors
|
|
*/
|
|
void DrawLegend(ImPlot<Ta, Tb>& figure, const char* pos,
|
|
const char* series[], const ImU32 series_color[], const int& n_series)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
//assert(IM_ARRAYSIZE(series) == IM_ARRAYSIZE(series_color));
|
|
|
|
ImVec2 legend_pos = ImVec2(figure.GetScalesX()->GetRangeMax(), figure.GetScalesY()->GetRangeMax()); //TR
|
|
if (!strcmp(pos, "TL")) legend_pos = ImVec2(figure.GetScalesX()->GetRangeMax(), figure.GetScalesY()->GetRangeMin());
|
|
else if (!strcmp(pos, "TR")) legend_pos = ImVec2(figure.GetScalesX()->GetRangeMax(), figure.GetScalesY()->GetRangeMax());
|
|
else if (!strcmp(pos, "BR")) legend_pos = ImVec2(figure.GetScalesX()->GetRangeMin(), figure.GetScalesY()->GetRangeMax());
|
|
else if (!strcmp(pos, "BL")) legend_pos = ImVec2(figure.GetScalesX()->GetRangeMin(), figure.GetScalesY()->GetRangeMin());
|
|
|
|
|
|
// Deduce the maximum text size
|
|
ImVec2 series_size = ImVec2(0.0f, 0.0f);
|
|
for (int n=0; n<n_series; ++n)
|
|
{
|
|
ImVec2 series_size_tmp = CalcTextSize(series[n], NULL, true);
|
|
if (series_size_tmp.x > series_size.x) series_size = series_size_tmp;
|
|
}
|
|
|
|
// Legend attributes
|
|
const float series_spacing = 0.1*series_size.y;
|
|
const float box_length = 0.9*series_size.y;
|
|
const float height = n_series * series_size.y + n_series * series_spacing + series_spacing;
|
|
const float width = series_size.x + box_length + 1.0f + 6*series_spacing;
|
|
|
|
// Draw box
|
|
window->DrawList->AddRect(ImVec2(legend_pos.x, legend_pos.y), ImVec2(legend_pos.x + width, legend_pos.y + height), properties_.stroke_col);
|
|
|
|
for (int n=0; n<n_series; ++n)
|
|
{
|
|
const float start_y_pos = n*series_size.y + n*series_spacing + series_spacing;
|
|
|
|
// Draw box and color for each series
|
|
window->DrawList->AddRectFilled(
|
|
ImVec2(legend_pos.x + series_spacing, legend_pos.y + start_y_pos),
|
|
ImVec2(legend_pos.x + series_spacing + box_length, legend_pos.y + start_y_pos + box_length),
|
|
series_color[n]);
|
|
|
|
// Label colored box with series name
|
|
window->DrawList->AddText(properties_.series_font, properties_.series_font_size,
|
|
ImVec2(legend_pos.x + series_spacing + box_length + 1.0f, legend_pos.y + start_y_pos),
|
|
properties_.series_font_col, series[n]);
|
|
|
|
}
|
|
};
|
|
|
|
void SetProperties(ImLegendProperties& properties){properties_ = properties;}
|
|
|
|
private:
|
|
ImLegendProperties properties_;
|
|
};
|
|
|
|
// ## Error Bars
|
|
struct ImErrorBarProperties
|
|
{
|
|
ImU32 error_bar_stroke_col = 0;
|
|
float error_bar_stroke_width = 1.0f;
|
|
const char* error_bar_cap_style = "Straight"; ///< Options are "Straight", "Circular"
|
|
float error_bar_cap_width = 4.0f;
|
|
};
|
|
|
|
template<typename Ta, typename Tb>
|
|
class ImErrorBars
|
|
{
|
|
public:
|
|
/**
|
|
* @brief Draw Error Bars
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param dx1 Upper error bar lengths
|
|
* @param dx2 Lower error bar lengths
|
|
*
|
|
*/
|
|
void DrawErrorBarsX(ImPlot<Ta, Tb>& figure,
|
|
const Ta* x_data, const Tb* y_data, const int& n_data,
|
|
const Ta* dx1, const Ta* dx2,
|
|
const Tb& error_offset, const Ta* error_bottoms)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
for (int n = 0; n < n_data; ++n)
|
|
{
|
|
// error bars
|
|
const float centre_scaled_x = figure.GetScalesX()->Scale(x_data[n] + error_bottoms[n]);
|
|
const float centre_scaled_y = figure.GetScalesY()->Scale(y_data[n]) + error_offset;
|
|
|
|
const ImVec2 point = ImVec2(centre_scaled_x, centre_scaled_y);
|
|
const ImVec2 error_high = ImVec2(figure.GetScalesX()->Scale(x_data[n] + error_bottoms[n] + dx1[n]), centre_scaled_y);
|
|
const ImVec2 error_low = ImVec2(figure.GetScalesX()->Scale(x_data[n] + error_bottoms[n] - dx2[n]), centre_scaled_y);
|
|
|
|
window->DrawList->AddLine(point, error_high, properties_.error_bar_stroke_col, properties_.error_bar_stroke_width);
|
|
window->DrawList->AddLine(point, error_low, properties_.error_bar_stroke_col, properties_.error_bar_stroke_width);
|
|
|
|
// caps
|
|
if (strcmp(properties_.error_bar_cap_style, "Straight")==0)
|
|
{
|
|
if (dx1[n] > 0)
|
|
{
|
|
const ImVec2 error_high_down = ImVec2(figure.GetScalesX()->Scale(x_data[n] + error_bottoms[n] + dx1[n]), centre_scaled_y - properties_.error_bar_cap_width * 0.5);
|
|
const ImVec2 error_high_up = ImVec2(figure.GetScalesX()->Scale(x_data[n] + error_bottoms[n] + dx1[n]), centre_scaled_y + properties_.error_bar_cap_width * 0.5);
|
|
window->DrawList->AddLine(error_high_down, error_high_up, properties_.error_bar_stroke_col, properties_.error_bar_stroke_width);
|
|
}
|
|
|
|
if (dx2[n] > 0)
|
|
{
|
|
const ImVec2 error_low_down = ImVec2(figure.GetScalesX()->Scale(x_data[n] + error_bottoms[n] - dx2[n]), centre_scaled_y - properties_.error_bar_cap_width * 0.5);
|
|
const ImVec2 error_low_up = ImVec2(figure.GetScalesX()->Scale(x_data[n] + error_bottoms[n] - dx2[n]), centre_scaled_y + properties_.error_bar_cap_width * 0.5);
|
|
window->DrawList->AddLine(error_low_down, error_low_up, properties_.error_bar_stroke_col, properties_.error_bar_stroke_width);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Draw Error Bars
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param dy1 Upper error bar lengths
|
|
* @param dy2 Lower error bar lengths
|
|
*
|
|
*/
|
|
void DrawErrorBarsY(ImPlot<Ta, Tb>& figure,
|
|
const Ta* x_data, const Tb* y_data, const int& n_data,
|
|
const Tb* dy1, const Tb* dy2,
|
|
const Ta& error_offset, const Tb* error_bottoms)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
for (int n = 0; n < n_data; ++n)
|
|
{
|
|
// error bars
|
|
const float centre_scaled_x = figure.GetScalesX()->Scale(x_data[n]) + error_offset;
|
|
const float centre_scaled_y = figure.GetScalesY()->Scale(y_data[n] + error_bottoms[n]);
|
|
|
|
const ImVec2 point = ImVec2(centre_scaled_x, centre_scaled_y);
|
|
const ImVec2 error_high = ImVec2(centre_scaled_x, figure.GetScalesY()->Scale(y_data[n] + error_bottoms[n] + dy1[n]));
|
|
const ImVec2 error_low = ImVec2(centre_scaled_x, figure.GetScalesY()->Scale(y_data[n] + error_bottoms[n] - dy2[n]));
|
|
|
|
window->DrawList->AddLine(point, error_high, properties_.error_bar_stroke_col, properties_.error_bar_stroke_width);
|
|
window->DrawList->AddLine(point, error_low, properties_.error_bar_stroke_col, properties_.error_bar_stroke_width);
|
|
|
|
// caps
|
|
if (strcmp(properties_.error_bar_cap_style, "Straight")==0)
|
|
{
|
|
if (dy1[n] > 0)
|
|
{
|
|
const ImVec2 error_high_left = ImVec2(centre_scaled_x - properties_.error_bar_cap_width * 0.5,
|
|
figure.GetScalesY()->Scale(y_data[n] + error_bottoms[n] + dy1[n]));
|
|
const ImVec2 error_high_right = ImVec2(centre_scaled_x + properties_.error_bar_cap_width * 0.5,
|
|
figure.GetScalesY()->Scale(y_data[n] + error_bottoms[n] + dy1[n]));
|
|
window->DrawList->AddLine(error_high_left, error_high_right, properties_.error_bar_stroke_col, properties_.error_bar_stroke_width);
|
|
}
|
|
|
|
if (dy2[n] > 0)
|
|
{
|
|
const ImVec2 error_low_left = ImVec2(centre_scaled_x - properties_.error_bar_cap_width * 0.5,
|
|
figure.GetScalesY()->Scale(y_data[n] + error_bottoms[n] - dy2[n]));
|
|
const ImVec2 error_low_right = ImVec2(centre_scaled_x + properties_.error_bar_cap_width * 0.5,
|
|
figure.GetScalesY()->Scale(y_data[n] + error_bottoms[n] - dy2[n]));
|
|
window->DrawList->AddLine(error_low_left, error_low_right, properties_.error_bar_stroke_col, properties_.error_bar_stroke_width);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void SetProperties(ImErrorBarProperties& properties){properties_ = properties;}
|
|
|
|
private:
|
|
ImErrorBarProperties properties_;
|
|
};
|
|
|
|
// ## Labels (for e.g., scatter plot)
|
|
struct ImLabelProperties
|
|
{
|
|
ImFont* label_font = NULL; ///< Label font
|
|
ImU32 label_font_col = 0; ///< Label font color
|
|
float label_font_size = 12.0f; ///< Label font size
|
|
ImVec2 label_offset_pos = ImVec2(0.0f, 0.0f); ///< Offset position of the label
|
|
};
|
|
|
|
template<typename Ta, typename Tb>
|
|
class ImLabels
|
|
{
|
|
public:
|
|
/**
|
|
* @brief Draw Labels
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param x_data
|
|
* @param y_data
|
|
* @param n_data
|
|
* @param labels Label for each data point of length n (matching order of x/y_data)
|
|
*
|
|
*/
|
|
void DrawLabels(ImPlot<Ta, Tb>& figure,
|
|
const Ta* x_data, const Tb* y_data, const int& n_data, const char* labels[],
|
|
const Ta& x_offset, const Tb& y_offset, const Ta* x_bottoms, const Ta* y_bottoms)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
if (labels == NULL) return;
|
|
|
|
for (int n = 0; n < n_data; ++n)
|
|
{
|
|
// labels
|
|
const float centre_scaled_x = figure.GetScalesX()->Scale(x_data[n] + x_bottoms[0]) + x_offset;
|
|
const float centre_scaled_y = figure.GetScalesY()->Scale(y_data[n] + y_bottoms[0]) + y_offset;
|
|
window->DrawList->AddText(properties_.label_font, properties_.label_font_size, ImVec2(centre_scaled_x, centre_scaled_y), properties_.label_font_col, labels[n]);
|
|
}
|
|
};
|
|
void DrawLabels(ImPlot<Ta, Tb>& figure,
|
|
const Ta* x_data, const Tb* y_data, const int& n_data, const char* labels[])
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
if (labels == NULL) return;
|
|
|
|
for (int n = 0; n < n_data; ++n)
|
|
{
|
|
// labels
|
|
const float centre_scaled_x = figure.GetScalesX()->Scale(x_data[n]);
|
|
const float centre_scaled_y = figure.GetScalesY()->Scale(y_data[n]);
|
|
window->DrawList->AddText(properties_.label_font, properties_.label_font_size, ImVec2(centre_scaled_x, centre_scaled_y), properties_.label_font_col, labels[n]);
|
|
}
|
|
};
|
|
|
|
void SetProperties(ImLabelProperties& properties){properties_ = properties;}
|
|
private:
|
|
ImLabelProperties properties_;
|
|
};
|
|
|
|
// ## Markers (for e.g., scatter plot)
|
|
struct ImMarkerProperties
|
|
{
|
|
ImU32 marker_stroke_col = 0; ///< circle (or other symbol) stroke color
|
|
float marker_stroke_width = 1.0f; ///< circle (or other symbol) stroke width
|
|
ImU32 marker_fill_col = 0; ///< circle (or other symbol) fill color
|
|
ImU32 marker_hovered_col = 0; ///< circle (or other symbol) fill color on hover
|
|
// char* tool_tip_format = "%4.2f"; ///< tooltip format
|
|
};
|
|
|
|
template<typename Ta, typename Tb>
|
|
class ImMarkers
|
|
{
|
|
public:
|
|
/**
|
|
* @brief Draw Markers
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param x_data
|
|
* @param y_data
|
|
* @param r_data Radius of the markers
|
|
* @param n_data
|
|
* @param series Name of the marker series (used for tooltip)
|
|
*
|
|
*/
|
|
void DrawMarkers(ImPlot<Ta, Tb>& figure,
|
|
const Ta* x_data, const Tb* y_data, const float* r_data, const int& n_data,
|
|
const char* series)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
for (int n = 0; n < n_data; ++n)
|
|
{
|
|
// Points
|
|
const float centre_scaled_x = figure.GetScalesX()->Scale(x_data[n]);
|
|
const float centre_scaled_y = figure.GetScalesY()->Scale(y_data[n]);
|
|
window->DrawList->AddCircleFilled(ImVec2(centre_scaled_x, centre_scaled_y), r_data[n], properties_.marker_fill_col, 12);
|
|
|
|
// Tooltip on hover
|
|
if (centre_scaled_x - r_data[n] <= g.IO.MousePos.x &&
|
|
centre_scaled_x + r_data[n] >= g.IO.MousePos.x &&
|
|
centre_scaled_y - r_data[n] <= g.IO.MousePos.y &&
|
|
centre_scaled_y + r_data[n] >= g.IO.MousePos.y)
|
|
{
|
|
SetTooltip("%s\n%s: %8.4g\n%s: %8.4g", series, "x", x_data[n], "y", y_data[n]);
|
|
window->DrawList->AddCircleFilled(ImVec2(centre_scaled_x, centre_scaled_y), r_data[n], properties_.marker_hovered_col, 12);
|
|
}
|
|
}
|
|
};
|
|
|
|
void SetProperties(ImMarkerProperties& properties){properties_ = properties;}
|
|
private:
|
|
ImMarkerProperties properties_;
|
|
};
|
|
|
|
// ## Lines (for e.g., line plot)
|
|
struct ImLineProperties
|
|
{
|
|
ImU32 line_stroke_col = 0; ///< line stroke color
|
|
float line_stroke_width = 1.5f; ///< line stroke width
|
|
float line_stroke_dash = 0.0f; ///< spacing of the dash
|
|
float line_stroke_gap = 0.0f; ///< spacing between dashes
|
|
const char* line_interp = "None"; ///< "None" for a straight line and "Bezier" for a curved line
|
|
};
|
|
|
|
template<typename Ta, typename Tb>
|
|
class ImLines
|
|
{
|
|
public:
|
|
/**
|
|
* @brief Draw Lines
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param x_data
|
|
* @param y_data
|
|
* @param n_data
|
|
*
|
|
*/
|
|
void DrawLines(ImPlot<Ta, Tb>& figure,
|
|
const Ta* x_data, const Tb* y_data, const int& n_data)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
std::vector<int> xx;
|
|
std::vector<int> yy;
|
|
xx.push_back(int(figure.GetScalesX()->Scale(x_data[0])));
|
|
yy.push_back(int(figure.GetScalesY()->Scale(y_data[0])));
|
|
for (int i = 1; i < n_data; i++) {
|
|
if (int(figure.GetScalesX()->Scale(x_data[i])) == xx.back())
|
|
yy.back() = (yy.back() + int(figure.GetScalesY()->Scale(y_data[i]))) / 2;
|
|
else {
|
|
xx.push_back(int(figure.GetScalesX()->Scale(x_data[i])));
|
|
yy.push_back(int(figure.GetScalesY()->Scale(y_data[i])));
|
|
}
|
|
}
|
|
OutputDebugString((to_string(n_data) + "->" + to_string(xx.size()) + "\n").c_str());
|
|
|
|
for (unsigned int n = 1; n < xx.size(); ++n)
|
|
{
|
|
// Line
|
|
if (n > 0 && strcmp(properties_.line_interp, "None") == 0)
|
|
{
|
|
window->DrawList->AddLine(
|
|
ImVec2(xx[n - 1], yy[n - 1]),
|
|
ImVec2(xx[n], yy[n]),
|
|
properties_.line_stroke_col, properties_.line_stroke_width);
|
|
}
|
|
else if (n > 0 && strcmp(properties_.line_interp, "Bezier") == 0)
|
|
{
|
|
// TODO
|
|
}
|
|
}
|
|
|
|
if (g.IO.MousePos.x >= figure.GetScalesX()->GetRangeMin() && g.IO.MousePos.x <= figure.GetScalesX()->GetRangeMax() &&
|
|
g.IO.MousePos.y >= figure.GetScalesY()->GetRangeMax() && g.IO.MousePos.y <= figure.GetScalesY()->GetRangeMin())
|
|
{
|
|
window->DrawList->AddLine(
|
|
ImVec2(g.IO.MousePos.x, figure.GetScalesY()->GetRangeMin()),
|
|
ImVec2(g.IO.MousePos.x, figure.GetScalesY()->GetRangeMax()),
|
|
ImGui::ColorConvertFloat4ToU32(ImVec4(255.0f, 255.0f, 255.0f, 100.0f)), properties_.line_stroke_width);
|
|
|
|
for (unsigned int n = 1; n < xx.size(); ++n)
|
|
{
|
|
if (g.IO.MousePos.x >= xx[n - 1] && g.IO.MousePos.x <= xx[n])
|
|
{
|
|
int idx;
|
|
if (g.IO.MousePos.x == xx[n - 1] || g.IO.MousePos.x < (xx[n] - xx[n - 1]) / 2)
|
|
idx = n - 1;
|
|
else
|
|
idx = n;
|
|
idx = idx * n_data / xx.size();
|
|
BeginTooltipEx(0, false);
|
|
ImGui::Text("x: %.3f", x_data[idx]);
|
|
ImGui::Text("y: %.3f", y_data[idx]);
|
|
EndTooltip();
|
|
window->DrawList->AddCircleFilled(ImVec2(xx[n], yy[n]), 5, ImGui::ColorConvertFloat4ToU32(ImVec4(0.0f, 255.0f, 0.0f, 100.0f)), 12);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void SetProperties(ImLineProperties& properties){properties_ = properties;}
|
|
private:
|
|
ImLineProperties properties_;
|
|
};
|
|
|
|
// ## Bars (for e.g., bar plot)
|
|
struct ImBarProperties
|
|
{
|
|
float bar_width = 10.0f;
|
|
ImU32 bar_stroke_col = 0;
|
|
float bar_stroke_width = 1.0f;
|
|
ImU32 bar_fill_col = 0;
|
|
ImU32 bar_hovered_col = 0;
|
|
};
|
|
|
|
/**
|
|
* @brief Bar plots. The orientiation of the bars is specified
|
|
* by the `orientiation` parameter. Stacked or Staggered bar
|
|
* representations are controlled manually by the user via
|
|
* the `bar_bottoms` and `bar_offset` parameters.
|
|
*/
|
|
template<typename Ta, typename Tb>
|
|
class ImBars
|
|
{
|
|
public:
|
|
/**
|
|
* @brief Draw vertical Bars
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param y_data
|
|
* @param n_data
|
|
* @param series Name of the marker series (used for tooltip)
|
|
*
|
|
*/
|
|
void DrawBars(ImPlot<Ta, Tb>& figure,
|
|
const Tb* y_data, const int& n_data,
|
|
const Ta& bar_offset, const Tb* bar_bottoms,
|
|
const char* series)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
const float bar_span = (figure.GetScalesX()->GetRangeMax() - figure.GetScalesX()->GetRangeMin())/(n_data - 1);
|
|
for (int n = 0; n < n_data; ++n)
|
|
{
|
|
// Bars
|
|
// const ImVec2 bar_BL = ImVec2(figure.GetScalesX()->GetRangeMin() + n*bar_span + bar_offset,
|
|
// figure.GetScalesY()->GetRangeMin() + figure.GetScalesY()->Scale(bar_bottoms[n]));
|
|
// const ImVec2 bar_TR = ImVec2(figure.GetScalesX()->GetRangeMin() + n*bar_span + properties_.bar_width + bar_offset,
|
|
// figure.GetScalesY()->GetRangeMin() + figure.GetScalesY()->Scale(bar_bottoms[n] + y_data[n]));
|
|
const ImVec2 bar_BL = ImVec2(figure.GetScalesX()->GetRangeMin() + n*bar_span + bar_offset,
|
|
figure.GetScalesY()->Scale(bar_bottoms[n]));
|
|
const ImVec2 bar_TR = ImVec2(figure.GetScalesX()->GetRangeMin() + n*bar_span + properties_.bar_width + bar_offset,
|
|
figure.GetScalesY()->Scale(bar_bottoms[n] + y_data[n]));
|
|
window->DrawList->AddRectFilled(bar_BL, bar_TR, properties_.bar_fill_col);
|
|
|
|
// printf("BL: %f, %f; TR: %f, %f", bar_BL.x, bar_BL.y, bar_TR.x, bar_TR.y);
|
|
// system("pause");
|
|
|
|
// Tooltip on hover
|
|
if (bar_BL.x <= g.IO.MousePos.x &&
|
|
bar_TR.x >= g.IO.MousePos.x &&
|
|
bar_BL.y >= g.IO.MousePos.y &&
|
|
bar_TR.y <= g.IO.MousePos.y)
|
|
{
|
|
SetTooltip("%s\n%s: %8.4g", series, "y", y_data[n]);
|
|
window->DrawList->AddRectFilled(bar_BL, bar_TR, properties_.bar_hovered_col);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Draw horizontal Bars
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param y_data
|
|
* @param n_data
|
|
*
|
|
*/
|
|
void DrawBarsH(ImPlot<Ta, Tb>& figure,
|
|
const Ta* x_data, const int& n_data,
|
|
const Tb& bar_offset, const Ta* bar_bottoms,
|
|
const char* series)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
const float bar_span = (figure.GetScalesY()->GetRangeMax() - figure.GetScalesY()->GetRangeMin())/(n_data - 1);
|
|
for (int n = 0; n < n_data; ++n)
|
|
{
|
|
// Bars
|
|
// const ImVec2 bar_BL = ImVec2(figure.GetScalesX()->GetRangeMin() + n*bar_span + bar_offset,
|
|
// figure.GetScalesY()->GetRangeMin() + figure.GetScalesY()->Scale(bar_bottoms[n]));
|
|
// const ImVec2 bar_TR = ImVec2(figure.GetScalesX()->GetRangeMin() + n*bar_span + properties_.bar_width + bar_offset,
|
|
// figure.GetScalesY()->GetRangeMin() + figure.GetScalesY()->Scale(bar_bottoms[n] + y_data[n]));
|
|
const ImVec2 bar_BL = ImVec2(figure.GetScalesX()->Scale(bar_bottoms[n]),
|
|
figure.GetScalesY()->GetRangeMin() + n*bar_span + bar_offset);
|
|
const ImVec2 bar_TR = ImVec2(figure.GetScalesX()->Scale(bar_bottoms[n] + x_data[n]),
|
|
figure.GetScalesY()->GetRangeMin() + n*bar_span + properties_.bar_width + bar_offset);
|
|
window->DrawList->AddRectFilled(bar_BL, bar_TR, properties_.bar_fill_col);
|
|
|
|
// printf("BL: %f, %f; TR: %f, %f", bar_BL.x, bar_BL.y, bar_TR.x, bar_TR.y);
|
|
// system("pause");
|
|
|
|
// Tooltip on hover
|
|
if (bar_BL.x <= g.IO.MousePos.x &&
|
|
bar_TR.x >= g.IO.MousePos.x &&
|
|
bar_BL.y <= g.IO.MousePos.y &&
|
|
bar_TR.y >= g.IO.MousePos.y)
|
|
{
|
|
SetTooltip("%s\n%s: %8.4g", series, "x", x_data[n]);
|
|
window->DrawList->AddRectFilled(bar_BL, bar_TR, properties_.bar_hovered_col);
|
|
}
|
|
}
|
|
};
|
|
|
|
void SetProperties(ImBarProperties& properties){properties_ = properties;}
|
|
private:
|
|
ImBarProperties properties_;
|
|
};
|
|
|
|
struct ImAreaProperties
|
|
{
|
|
ImU32 area_fill_col = 0;
|
|
ImU32 area_hover_col = 0;
|
|
};
|
|
|
|
template<typename Ta, typename Tb>
|
|
class ImArea
|
|
{
|
|
public:
|
|
void DrawArea(
|
|
ImPlot<Ta, Tb>& figure,
|
|
const Ta* x_data,
|
|
const Tb* y_data,
|
|
const size_t n_data,
|
|
const Tb * y_data_bottoms,
|
|
const char* series = NULL
|
|
)
|
|
{
|
|
if (n_data < 2) return;
|
|
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImDrawList* dl = window->DrawList;
|
|
dl->PathLineTo(ImVec2(figure.GetScalesX()->Scale(x_data[0]), figure.GetScalesY()->Scale(y_data_bottoms[0])));
|
|
|
|
for (size_t i = 0; i < n_data; ++i)
|
|
dl->PathLineTo(ImVec2(figure.GetScalesX()->Scale(x_data[i]), figure.GetScalesY()->Scale(y_data_bottoms[i] + y_data[i])));
|
|
|
|
dl->PathLineTo(ImVec2(figure.GetScalesX()->Scale(x_data[n_data-1]), figure.GetScalesY()->Scale(y_data_bottoms[n_data-1])));
|
|
|
|
ImVec2 pointer(GImGui->IO.MousePos.x, GImGui->IO.MousePos.y);
|
|
|
|
const bool check = is_inside_area(pointer, dl->_Path);
|
|
|
|
if (check && series) {
|
|
SetTooltip("%s", series);
|
|
}
|
|
|
|
if (check && properties_.area_hover_col) {
|
|
dl->PathFillConvex(properties_.area_hover_col);
|
|
}
|
|
|
|
dl->PathFillConvex(properties_.area_fill_col);
|
|
};
|
|
|
|
void SetProperties(ImAreaProperties& properties)
|
|
{
|
|
properties_ = properties;
|
|
}
|
|
|
|
private:
|
|
ImAreaProperties properties_;
|
|
};
|
|
|
|
|
|
|
|
// # High level plotting functions
|
|
|
|
// ## Charts
|
|
// ### Basics
|
|
// - Line
|
|
// - Scatter
|
|
// - Bar (stacked)
|
|
// - Barh
|
|
// - BoxPlot
|
|
// - Area (stacked)
|
|
// - Heatmap
|
|
// - Histogram (density and binning)
|
|
// ### Advanced
|
|
// - Contour
|
|
// - Stem
|
|
// - Stream
|
|
// - Polar
|
|
// - Radar
|
|
// - Hexagonal binning
|
|
// - Histogram 2D
|
|
// - Violin
|
|
// - Joint
|
|
// - Strip
|
|
// - Swarm
|
|
|
|
// ## Pie (for e.g., pie or donught plot)
|
|
struct ImPieProperties
|
|
{
|
|
float inner_radius = 0.0f; ///< change to create a donught plot
|
|
float outer_radius = 100.0f; ///< controls the size of the pie
|
|
ImU32 pie_stroke_col = 0;
|
|
float pie_stroke_width = 1.0f;
|
|
ImU32 pie_hovered_col = 0;
|
|
int pie_segments = 128; ///< number of segments to use when drawing the circle
|
|
// if this is too low, the segment will not be drawn!
|
|
};
|
|
|
|
template<typename Ta, typename Tb>
|
|
class ImPie
|
|
{
|
|
public:
|
|
/**
|
|
* @brief Draw Pie
|
|
*
|
|
* @param figure The figure to draw on
|
|
* @param x_data Numerical values
|
|
* @param y_data Color used for each of the pie segments
|
|
* @param n_data
|
|
* @param series Name of the pie segment series (used for tooltip)
|
|
*
|
|
*/
|
|
void DrawPie(ImPlot<Ta, Tb>& figure,
|
|
const Ta* x_data, const ImU32* colors, const int& n_data, const char* series[])
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
// calculate the total x
|
|
float x_data_total = 0;
|
|
for (int n = 0; n < n_data; ++n)
|
|
x_data_total += x_data[n];
|
|
|
|
// add each arc
|
|
float x_data_prev_rad = 0.0f;
|
|
for (int n = 0; n < n_data; ++n)
|
|
{
|
|
const float x_data_rad = IM_PI*2.0f*x_data[n]/x_data_total + x_data_prev_rad; // convert x_data to radians
|
|
const int n_segments = (int)ceilf(x_data[n]/x_data_total*(float)properties_.pie_segments); // determine the number of segments
|
|
const ImVec2 centre = ImVec2(
|
|
(figure.GetScalesX()->GetRangeMin() + figure.GetScalesX()->GetRangeMax())/2,
|
|
(figure.GetScalesY()->GetRangeMin() + figure.GetScalesY()->GetRangeMax())/2
|
|
);
|
|
|
|
// // line 1
|
|
// const ImVec2 vec_line_start = ImVec2(
|
|
// ImCos(x_data_prev_rad)*properties_.inner_radius + centre.x,
|
|
// ImSin(x_data_prev_rad)*properties_.inner_radius + centre.y);
|
|
// const ImVec2 vec_line_end = ImVec2(
|
|
// ImCos(x_data_prev_rad)*properties_.outer_radius + centre.x,
|
|
// ImSin(x_data_prev_rad)*properties_.outer_radius + centre.y);
|
|
|
|
// start: end of the outer arc. end: start of the inner arc
|
|
const ImVec2 vec1 = ImVec2(
|
|
ImCos(x_data_rad)*properties_.inner_radius + centre.x,
|
|
ImSin(x_data_rad)*properties_.inner_radius + centre.y);
|
|
|
|
// start: end of the inner arc. end: start of the outer arc
|
|
const ImVec2 vec2 = ImVec2(
|
|
ImCos(x_data_prev_rad)*properties_.outer_radius + centre.x,
|
|
ImSin(x_data_prev_rad)*properties_.outer_radius + centre.y);
|
|
|
|
// draw the pie segment
|
|
// window->DrawList->AddLine(vec_line_start, vec_line_end, ImGui::ColorConvertFloat4ToU32(ImVec4(255.0f, 255.0f, 255.0f, 255.0f)));
|
|
window->DrawList->PathArcTo(centre, properties_.outer_radius, x_data_prev_rad, x_data_rad, n_segments); // outer arc
|
|
window->DrawList->PathLineTo(vec1); // start outer to inner arc line
|
|
window->DrawList->PathArcTo(centre, properties_.inner_radius, x_data_rad, x_data_prev_rad, n_segments); // inner arc
|
|
window->DrawList->PathLineTo(vec2); // end inner arc to outer arc line
|
|
window->DrawList->PathFillConvex(colors[n]);
|
|
|
|
// draw the tooltip
|
|
// [BUG: there appears to be a "gap" in the convex hull...]
|
|
if (is_inside_area(ImVec2(GImGui->IO.MousePos.x, GImGui->IO.MousePos.y), window->DrawList->_Path)) {
|
|
SetTooltip("%s\n%s: %8.4g", series[n], "x", x_data[n]);
|
|
window->DrawList->PathFillConvex(properties_.pie_hovered_col);
|
|
}
|
|
|
|
x_data_prev_rad = x_data_rad;
|
|
}
|
|
}
|
|
|
|
void SetProperties(ImPieProperties& properties){properties_ = properties;}
|
|
private:
|
|
ImPieProperties properties_;
|
|
};
|
|
|
|
// ## Layouts and hierarchies
|
|
// - Pie
|
|
// - circos layout
|
|
// - swim lanes
|
|
// - bundle diagram
|
|
// - chord diagram
|
|
// - force directed graph
|
|
// - force layout
|
|
// - indented tree layout
|
|
// - pack layout
|
|
// - partition layout
|
|
// - radial dendrogram
|
|
// - radial tree layout
|
|
// - sankey diagram
|
|
// - treemap layout
|
|
// - vertical dendrogram
|
|
}
|