// # 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 //delete when finished debugging #include 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& V); // ## Scales template 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 class ImLinearScales : public ImScales { 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 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* scales_x, ImScales* scales_y){scales_x_ = scales_x; scales_y_ = scales_y;} ImScales* GetScalesX(){return scales_x_;} ImScales* GetScalesY(){return scales_y_;} protected: ImScales* scales_x_; ImScales* 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 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& 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& 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& 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& 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& 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& 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& 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& 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& 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& 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 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& 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 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; nDrawList->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 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& 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& 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 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& 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& 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 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& 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 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& 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 xx; std::vector 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 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& 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& 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 class ImArea { public: void DrawArea( ImPlot& 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 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& 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 }