From 50ef945465e611ecb134cfe6b9bff8d532142258 Mon Sep 17 00:00:00 2001 From: Pascal Thomet Date: Thu, 2 Jan 2025 14:53:14 +0100 Subject: [PATCH] Work on demo_implot (3D part) --- .../imgui_bundle/demos_cpp/demo_implot.cpp | 148 +++++++++++++++++- .../imgui_bundle/demos_python/demo_implot.py | 147 ++++++++++++++++- 2 files changed, 284 insertions(+), 11 deletions(-) diff --git a/bindings/imgui_bundle/demos_cpp/demo_implot.cpp b/bindings/imgui_bundle/demos_cpp/demo_implot.cpp index 55759205..989b2618 100644 --- a/bindings/imgui_bundle/demos_cpp/demo_implot.cpp +++ b/bindings/imgui_bundle/demos_cpp/demo_implot.cpp @@ -5,7 +5,15 @@ #include "implot/implot.h" #include "immapp/immapp.h" #include "demo_utils/api_demos.h" +#include "imgui_internal.h" +#ifdef IMGUI_BUNDLE_WITH_IMPLOT3D +#include "implot3d/implot3d.h" +#endif + +// ======================= +// Demos for ImPlot (2D) +// ======================= void DemoMixedPlot() { @@ -96,11 +104,136 @@ static void DemoHeatmap() } +// ======================= +// Demos for ImPlot3D +// ======================= + +#ifdef IMGUI_BUNDLE_WITH_IMPLOT3D +void Demo3D_LinePlots() { + static float xs1[1001], ys1[1001], zs1[1001]; + for (int i = 0; i < 1001; i++) { + xs1[i] = i * 0.001f; + ys1[i] = 0.5f + 0.5f * cosf(50 * (xs1[i] + (float)ImGui::GetTime() / 10)); + zs1[i] = 0.5f + 0.5f * sinf(50 * (xs1[i] + (float)ImGui::GetTime() / 10)); + } + static double xs2[20], ys2[20], zs2[20]; + for (int i = 0; i < 20; i++) { + xs2[i] = i * 1 / 19.0f; + ys2[i] = xs2[i] * xs2[i]; + zs2[i] = xs2[i] * ys2[i]; + } + if (ImPlot3D::BeginPlot("Line Plots")) { + ImPlot3D::SetupAxes("x", "y", "z"); + ImPlot3D::PlotLine("f(x)", xs1, ys1, zs1, 1001); + ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Circle); + ImPlot3D::PlotLine("g(x)", xs2, ys2, zs2, 20, ImPlot3DLineFlags_Segments); + ImPlot3D::EndPlot(); + } +} + + +void Demo3D_SurfacePlots() { + constexpr int N = 20; + static float xs[N * N], ys[N * N], zs[N * N]; + static float t = 0.0f; + t += ImGui::GetIO().DeltaTime; + + // Define the range for X and Y + constexpr float min_val = -1.0f; + constexpr float max_val = 1.0f; + constexpr float step = (max_val - min_val) / (N - 1); + + // Populate the xs, ys, and zs arrays + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + int idx = i * N + j; + xs[idx] = min_val + j * step; // X values are constant along rows + ys[idx] = min_val + i * step; // Y values are constant along columns + zs[idx] = ImSin(2 * t + ImSqrt((xs[idx] * xs[idx] + ys[idx] * ys[idx]))); // z = sin(2t + sqrt(x^2 + y^2)) + } + } + + // Choose fill color + ImGui::Text("Fill color"); + static int selected_fill = 1; // Colormap by default + static ImVec4 solid_color = ImVec4(0.8f, 0.8f, 0.2f, 0.6f); + const char* colormaps[] = {"Viridis", "Plasma", "Hot", "Cool", "Pink", "Jet", + "Twilight", "RdBu", "BrBG", "PiYG", "Spectral", "Greys"}; + static int sel_colormap = 5; // Jet by default + { + ImGui::Indent(); + + // Choose solid color + ImGui::RadioButton("Solid", &selected_fill, 0); + if (selected_fill == 0) { + ImGui::SameLine(); + ImGui::ColorEdit4("##SurfaceSolidColor", (float*)&solid_color); + } + + // Choose colormap + ImGui::RadioButton("Colormap", &selected_fill, 1); + if (selected_fill == 1) { + ImGui::SameLine(); + ImGui::Combo("##SurfaceColormap", &sel_colormap, colormaps, IM_ARRAYSIZE(colormaps)); + } + ImGui::Unindent(); + } + + // Choose range + static bool custom_range = false; + static float range_min = -1.0f; + static float range_max = 1.0f; + ImGui::Checkbox("Custom range", &custom_range); + { + ImGui::Indent(); + + if (!custom_range) + ImGui::BeginDisabled(); + ImGui::SliderFloat("Range min", &range_min, -1.0f, range_max - 0.01f); + ImGui::SliderFloat("Range max", &range_max, range_min + 0.01f, 1.0f); + if (!custom_range) + ImGui::EndDisabled(); + + ImGui::Unindent(); + } + + // Begin the plot + if (selected_fill == 1) + ImPlot3D::PushColormap(colormaps[sel_colormap]); + if (ImPlot3D::BeginPlot("Surface Plots", ImVec2(-1, 400), ImPlot3DFlags_NoClip)) { + // Set styles + ImPlot3D::SetupAxesLimits(-1, 1, -1, 1, -1.5, 1.5); + ImPlot3D::PushStyleVar(ImPlot3DStyleVar_FillAlpha, 0.8f); + if (selected_fill == 0) + ImPlot3D::SetNextFillStyle(solid_color); + ImPlot3D::SetNextLineStyle(ImPlot3D::GetColormapColor(1)); + + // Plot the surface + if (custom_range) + ImPlot3D::PlotSurface("Wave Surface", xs, ys, zs, N, N, (double)range_min, (double)range_max); + else + ImPlot3D::PlotSurface("Wave Surface", xs, ys, zs, N, N); + + // End the plot + ImPlot3D::PopStyleVar(); + ImPlot3D::EndPlot(); + } + if (selected_fill == 1) + ImPlot3D::PopColormap(); +} +#endif // #ifdef IMGUI_BUNDLE_WITH_IMPLOT3D + + +// ======================= +// Main demo function +// ======================= void demo_implot() { ImGuiMd::RenderUnindented(R"( - # ImPlot - [Implot](https://github.com/epezent/implot) provides immediate Mode Plotting for ImGui. + # ImPlot & ImPlot3D + * [Implot](https://github.com/epezent/implot) provides immediate Mode Plotting for ImGui. + * [Implot3D](https://github.com/brenocq/implot3d) provides immediate Mode 3D Plotting, with an API inspired from ImPlot. + You can see lots of demos together with their code [online](https://traineq.org/implot_demo/src/implot_demo.html) )"); if (ImGui::Button("View the full demo")) @@ -108,10 +241,17 @@ void demo_implot() BrowseToUrl("https://traineq.org/implot_demo/src/implot_demo.html"); } ImGui::NewLine(); - if (ImGui::CollapsingHeader("Mixed plot##2", ImGuiTreeNodeFlags_DefaultOpen)) + if (ImGui::CollapsingHeader("ImPlot: Mixed plot##2", ImGuiTreeNodeFlags_DefaultOpen)) DemoMixedPlot(); - if (ImGui::CollapsingHeader("Heatmap")) + if (ImGui::CollapsingHeader("ImPlot: Heatmap")) DemoHeatmap(); + +#ifdef IMGUI_BUNDLE_WITH_IMPLOT3D + if (ImGui::CollapsingHeader("ImPlot3D: Line plots##2")) + Demo3D_LinePlots(); + if (ImGui::CollapsingHeader("ImPlot3D: Surface Plots##2")) + Demo3D_SurfacePlots(); +#endif } #else // IMGUI_BUNDLE_WITH_IMPLOT diff --git a/bindings/imgui_bundle/demos_python/demo_implot.py b/bindings/imgui_bundle/demos_python/demo_implot.py index 63a1e6e8..975f49da 100644 --- a/bindings/imgui_bundle/demos_python/demo_implot.py +++ b/bindings/imgui_bundle/demos_python/demo_implot.py @@ -3,9 +3,13 @@ import math import numpy as np from numpy.typing import NDArray -from imgui_bundle import imgui, implot, imgui_md, immapp, ImVec2, ImVec4 +from imgui_bundle import imgui, implot, implot3d, imgui_md, immapp, ImVec2, ImVec4 +# ======================== +# Demos for ImPlot (2D) +# ======================== + class DemoDragRectState: x_data: NDArray[np.float64] y_data1: NDArray[np.float64] @@ -170,11 +174,134 @@ def demo_heatmap(): implot.pop_colormap() +# ======================== +# Demos for ImPlot3D +# ======================== +def demo3d_lineplots(): + xs1 = np.linspace(0, 1, 1001) + ys1 = 0.5 + 0.5 * np.cos(50 * (xs1 + imgui.get_time() / 10)) + zs1 = 0.5 + 0.5 * np.sin(50 * (xs1 + imgui.get_time() / 10)) + + xs2 = np.linspace(0, 1, 20) + ys2 = xs2 * xs2 + zs2 = xs2 * ys2 + + if implot3d.begin_plot("Line Plots"): + implot3d.setup_axes("x", "y", "z") + implot3d.plot_line("f(x)", xs1, ys1, zs1) + implot3d.set_next_marker_style(implot3d.Marker_.circle.value) + implot3d.plot_line("g(x)", xs2, ys2, zs2, implot3d.LineFlags_.segments.value) + implot3d.end_plot() + + +def demo3d_surfaceplots(): + statics = demo3d_surfaceplots + N = 20 + if not hasattr(statics, "xs"): + statics.xs = np.zeros(N * N) + statics.ys = np.zeros(N * N) + statics.zs = np.zeros(N * N) + statics.t = 0.0 + + statics.t += imgui.get_io().delta_time + + # Define the range for X and Y + min_val = -1.0 + max_val = 1.0 + step = (max_val - min_val) / (N - 1) + + # Populate the xs, ys, and zs arrays + for i in range(N): + for j in range(N): + idx = i * N + j + statics.xs[idx] = min_val + j * step # X values are constant along rows + statics.ys[idx] = min_val + i * step # Y values are constant along columns + # z = sin(2t + sqrt(x^2 + y^2)) + statics.zs[idx] = math.sin(2 * statics.t + math.sqrt(statics.xs[idx] * statics.xs[idx] + statics.ys[idx] * statics.ys[idx])) + + # Choose fill color + imgui.text("Fill color") + if not hasattr(statics, "selected_fill"): + statics.selected_fill = 1 # Colormap by default + if not hasattr(statics, "solid_color"): + statics.solid_color = [0.8, 0.8, 0.2, 0.6] + colormaps = [ + "Viridis", "Plasma", "Hot", "Cool", "Pink", "Jet", + "Twilight", "RdBu", "BrBG", "PiYG", "Spectral", "Greys" + ] + if not hasattr(statics, "sel_colormap"): + statics.sel_colormap = 5 # Jet by default + + imgui.indent() + + # Choose solid color + if imgui.radio_button("Solid", statics.selected_fill == 0): + statics.selected_fill = 0 + if statics.selected_fill == 0: + imgui.same_line() + _, statics.solid_color = imgui.color_edit4("##SurfaceSolidColor", statics.solid_color) + + # Choose colormap + if imgui.radio_button("Colormap", statics.selected_fill == 1): + statics.selected_fill = 1 + if statics.selected_fill == 1: + imgui.same_line() + _, statics.sel_colormap = imgui.combo("##SurfaceColormap", statics.sel_colormap, colormaps) + + imgui.unindent() + + # Choose range + if not hasattr(statics, "custom_range"): + statics.custom_range = False + if not hasattr(statics, "range_min"): + statics.range_min = -1.0 + if not hasattr(statics, "range_max"): + statics.range_max = 1.0 + _, statics.custom_range = imgui.checkbox("Custom range", statics.custom_range) + imgui.indent() + if not statics.custom_range: + imgui.begin_disabled() + _, statics.range_min = imgui.slider_float("Range min", statics.range_min, -1.0, statics.range_max - 0.01) + _, statics.range_max = imgui.slider_float("Range max", statics.range_max, statics.range_min + 0.01, 1.0) + if not statics.custom_range: + imgui.end_disabled() + imgui.unindent() + + # Begin the plot + if statics.selected_fill == 1: + implot3d.push_colormap(colormaps[statics.sel_colormap]) + if implot3d.begin_plot("Surface Plots", ImVec2(-1, 400), implot3d.Flags_.no_clip.value): + # Set styles + implot3d.setup_axes_limits(-1, 1, -1, 1, -1.5, 1.5) + implot3d.push_style_var(implot3d.StyleVar_.fill_alpha.value, 0.8) + if statics.selected_fill == 0: + implot3d.set_next_fill_style(statics.solid_color) + implot3d.set_next_line_style(implot3d.get_colormap_color(1)) + + # Plot the surface + if statics.custom_range: + implot3d.plot_surface("Wave Surface", statics.xs, statics.ys, statics.zs, N, N, statics.range_min, statics.range_max) + else: + implot3d.plot_surface("Wave Surface", statics.xs, statics.ys, statics.zs, N, N) + + # End the plot + implot3d.pop_style_var() + implot3d.end_plot() + + if statics.selected_fill == 1: + implot3d.pop_colormap() + + +# ======================= +# Main demo function +# ======================= def demo_gui(): imgui_md.render_unindented( """ - # ImPlot - [Implot](https://github.com/epezent/implot) provides immediate Mode Plotting for ImGui. + # ImPlot & ImPlot3D + * [Implot](https://github.com/epezent/implot) provides immediate Mode Plotting for ImGui. + * [Implot3D](https://github.com/brenocq/implot3d) provides immediate Mode 3D Plotting, with an API inspired from ImPlot. + You can see lots of demos together with their code [online](https://traineq.org/implot_demo/src/implot_demo.html) """ ) @@ -184,16 +311,22 @@ def demo_gui(): webbrowser.open("https://traineq.org/implot_demo/src/implot_demo.html") imgui.new_line() - if imgui.collapsing_header("Drag Rects"): + if imgui.collapsing_header("ImPlot: Drag Rects"): demo_drag_rects() - if imgui.collapsing_header("Mixed plot##2", imgui.TreeNodeFlags_.default_open.value): + if imgui.collapsing_header("ImPlot: Mixed plot##2"): demo_mixed_plot() - if imgui.collapsing_header("Heatmap"): + if imgui.collapsing_header("ImPlot: Heatmap"): demo_heatmap() + if imgui.collapsing_header("ImPlot3D: Line plots ##2"): + demo3d_lineplots() + if imgui.collapsing_header("ImPlot3D: Surface Plots##2"): + demo3d_surfaceplots() + + def main(): - immapp.run(demo_gui, with_implot=True, with_markdown=True, window_size=(1000, 800)) + immapp.run(demo_gui, with_implot=True, with_implot3d=True, with_markdown=True, window_size=(1000, 800)) if __name__ == "__main__":