From bc1bf9b39f0cf91d0b6d49507ff50c15aa0ad4aa Mon Sep 17 00:00:00 2001 From: Oliver Dixon Date: Tue, 30 Jun 2026 03:38:51 +0100 Subject: [PATCH 1/2] feat: Add support for ImPlot-style custom getters --- implot3d.h | 33 ++++++++++++++ implot3d_demo.cpp | 109 +++++++++++++++++++++++++++++++++++++++++++++ implot3d_items.cpp | 43 ++++++++++++++++++ 3 files changed, 185 insertions(+) diff --git a/implot3d.h b/implot3d.h index 3c9450d..d50e335 100644 --- a/implot3d.h +++ b/implot3d.h @@ -479,6 +479,9 @@ struct ImPlot3DSpec { // Optionally use #user_data for context. Return the number of characters written (excluding null terminator) typedef int (*ImPlot3DFormatter)(double value, char* buff, int size, void* user_data); +// Callback signature for data getter. +typedef ImPlot3DPoint (*ImPlot3DGetter)(int idx, const void* user_data); + // Callback signature for axis transform typedef double (*ImPlot3DTransform)(double value, void* user_data); @@ -630,20 +633,50 @@ IMPLOT3D_API void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags fla // 2. If your data is in separate arrays or requires computation, you can copy/transform // it into temporary float or double arrays before plotting. // +// 3. Write a custom getter C function or C++ non-capturing lambda and pass it and optionally +// your data to an ImPlot3D function post-fixed with a G (e.g. PlotScatterG). This has a slight +// performance cost, but probably not enough to worry about unless your data is very large. +// Examples: +// +// ImPlot3DPoint MyDataGetter(int idx, const void* data) { +// const MyData* my_data = (const MyData*)data; +// return ImPlot3DPoint( +// my_data->GetX(idx), +// my_data->GetY(idx), +// my_data->GetZ(idx) +// ); +// } +// ... +// auto lambda = [](int idx, const void* data) -> ImPlot3DPoint { +// // ... +// return {x, y, z}; +// }; +// ... +// if (ImPlot3D::BeginPlot("MyPlot")) { +// MyData my_data; +// ImPlot3D::PlotScatterG("scatter", MyDataGetter, &my_data, my_data.Size()); +// ImPlot3D::PlotLineG("line", lambda, &my_data, 1000); +// ImPlot3D::EndPlot(); +// } +// // NB: All types are converted to double before plotting. You may lose information // if you try plotting extremely large 64-bit integral types. Proceed with caution! // Plots a scatter plot in 3D. Points are rendered as markers at the specified coordinates IMPLOT3D_TMP void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, int count, const ImPlot3DSpec& spec = ImPlot3DSpec()); +IMPLOT3D_API void PlotScatterG(const char* label_id, ImPlot3DGetter getter, const void* data, int count, const ImPlot3DSpec& spec = ImPlot3DSpec()); // Plots a line in 3D. Consecutive points are connected with line segments IMPLOT3D_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, int count, const ImPlot3DSpec& spec = ImPlot3DSpec()); +IMPLOT3D_API void PlotLineG(const char* label_id, ImPlot3DGetter getter, const void* data, int count, const ImPlot3DSpec& spec = ImPlot3DSpec()); // Plots triangles in 3D. Every 3 consecutive points define a triangle IMPLOT3D_TMP void PlotTriangle(const char* label_id, const T* xs, const T* ys, const T* zs, int count, const ImPlot3DSpec& spec = ImPlot3DSpec()); +IMPLOT3D_API void PlotTriangleG(const char* label_id, ImPlot3DGetter getter, const void* data, int count, const ImPlot3DSpec& spec = ImPlot3DSpec()); // Plots quads in 3D. Every 4 consecutive points define a quadrilateral IMPLOT3D_TMP void PlotQuad(const char* label_id, const T* xs, const T* ys, const T* zs, int count, const ImPlot3DSpec& spec = ImPlot3DSpec()); +IMPLOT3D_API void PlotQuadG(const char* label_id, ImPlot3DGetter getter, const void* data, int count, const ImPlot3DSpec& spec = ImPlot3DSpec()); // Plot the surface defined by a grid of vertices. The grid is defined by the x and y arrays, and the z array contains the height of each vertex. A // total of x_count * y_count vertices are expected for each array. Leave #scale_min and #scale_max both at 0 for automatic color scaling, or set them diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 1c9a8d2..1f4a2b0 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -52,6 +52,31 @@ namespace MyImPlot3D { // Example for Custom Styles section void StyleSeaborn(); +// Example for Custom Data and Getters section. +struct Vector3f { + Vector3f(const float x, const float y, const float z) : x(x), y(y), z(z) {} + + float x; + float y; + float z; +}; + +// Example for Custom Data and Getters section. +struct WaveData { + WaveData(const double x, const double z, const double amp, const double freq, const double offset) + : X(x), Z(z), Amp(amp), Freq(freq), Offset(offset) {} + + double X; + double Z; + double Amp; + double Freq; + double Offset; +}; + +ImPlot3DPoint SineWave(int idx, const void* wave_data); +ImPlot3DPoint SawWave(int idx, const void* wave_data); +ImPlot3DPoint Spiral(int idx, const void* wave_data); + } // namespace MyImPlot3D namespace ImPlot3D { @@ -1743,6 +1768,47 @@ void DemoCustomPerPointStyle() { } } +void DemoCustomDataAndGetters() { + IMGUI_DEMO_MARKER("Custom/Custom Data and Getters"); + ImGui::BulletText("You can plot custom structs using the stride feature."); + ImGui::BulletText("Most plotters can also be passed a function pointer for getting data."); + ImGui::Indent(); + ImGui::BulletText("You can optionally pass user data to be given to your getter function."); + ImGui::BulletText("Non-capturing C++ lambdas can be passed as function pointers as well!"); + ImGui::Unindent(); + + if (ImPlot3D::BeginPlot("##Custom Data")) { + // custom structs using stride example: + const MyImPlot3D::Vector3f vec3_left_data[2] = {MyImPlot3D::Vector3f(0, 0, 0), MyImPlot3D::Vector3f(-1, -1, 1)}; + const MyImPlot3D::Vector3f vec3_right_data[2] = {MyImPlot3D::Vector3f(0, 0, 0), MyImPlot3D::Vector3f(1, 1, 1)}; + + ImPlot3D::PlotLine("Vector3f", &vec3_left_data[0].x, &vec3_left_data[0].y, &vec3_left_data[0].z, 2, + {ImPlot3DProp_Stride, sizeof(MyImPlot3D::Vector3f)}); + ImPlot3D::PlotLine("Vector3f", &vec3_right_data[0].x, &vec3_right_data[0].y, &vec3_right_data[0].z, 2, + {ImPlot3DProp_Stride, sizeof(MyImPlot3D::Vector3f)}); + + // custom getter example 1: + ImPlot3D::PlotLineG("Spiral", MyImPlot3D::Spiral, nullptr, 1000); + + // custom getter example 2: + static MyImPlot3D::WaveData sine_data(0.001, 0.18, 0.08, 3.0, 0.25); + static MyImPlot3D::WaveData saw_data(0.001, 0.18, 0.08, 3.0, 0.75); + ImPlot3D::PlotLineG("Waves", MyImPlot3D::SineWave, &sine_data, 1000); + ImPlot3D::PlotLineG("Waves", MyImPlot3D::SawWave, &saw_data, 1000); + + // you can also pass C++ lambdas: + // + // auto lambda = [](int idx, const void* data) -> ImPlot3DPoint { + // ... + // return {x, y, z}; + // }; + // + // ImPlot3D::PlotLineG("My Lambda", lambda, data, 1000); + + ImPlot3D::EndPlot(); + } +} + //----------------------------------------------------------------------------- // [SECTION] Config //----------------------------------------------------------------------------- @@ -1904,6 +1970,7 @@ void ShowAllDemos() { DemoHeader("Custom Rendering", DemoCustomRendering); DemoHeader("Custom Overlay", DemoCustomOverlay); DemoHeader("Custom Per-Point Style", DemoCustomPerPointStyle); + DemoHeader("Custom Data and Getters", DemoCustomDataAndGetters); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Config")) { @@ -2388,4 +2455,46 @@ void StyleSeaborn() { style.PlotMinSize = ImVec2(300, 225); } +ImPlot3DPoint SineWave(const int idx, const void* const wave_data) { + const auto* const wd = static_cast(wave_data); + + const double x = idx * wd->X; + const double y = wd->Z; + const double z = wd->Offset + wd->Amp * std::sin(2.0 * 3.14f * wd->Freq * x); + + return {x, y, z}; +} + +ImPlot3DPoint SawWave(const int idx, const void* const wave_data) { + const auto* const wd = static_cast(wave_data); + + const double x = idx * wd->X; + const double y = wd->Z; + + const double phase = 3.14f * wd->Freq * x; + const double z = wd->Offset + wd->Amp * (-2.0 / 3.14f * std::atan2(std::cos(phase), std::sin(phase))); + + return {x, y, z}; +} + +ImPlot3DPoint Spiral(const int idx, const void*) { + constexpr float outer_radius = 0.9f; + constexpr float inner_radius = 0.0f; + constexpr float increment_per_rev = 0.05f; + constexpr int point_count = 1000; + + constexpr float revolutions = (outer_radius - inner_radius) / increment_per_rev; + constexpr float max_theta = 2.0f * revolutions * 3.14f; + const float t = static_cast(idx) / static_cast(point_count - 1); + const float theta = max_theta * t; + + const float radius = inner_radius + increment_per_rev * theta / (2.0f * 3.14f); + + const float x = radius * std::cos(theta); + const float y = radius * std::sin(theta); + const float z = t; + + return {x, y, z}; +} + } // namespace MyImPlot3D diff --git a/implot3d_items.cpp b/implot3d_items.cpp index 19d2e68..99ef4a6 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -1017,6 +1017,20 @@ template struct GetterMeshTriangles { int Count; }; +/// Interprets a user's function pointer as ImPlot3DPoints +struct GetterFuncPtr { + GetterFuncPtr(const ImPlot3DGetter getter, const void* const data, const int count) : Getter(getter), Data(data), Count(count) {} + + template IMPLOT3D_INLINE ImPlot3DPoint operator[](I idx) const { return Getter(idx, Data); } + + template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { return Getter(idx, Data); } + + ImPlot3DGetter Getter; + const void* const Data; + const int Count; + typedef ImPlot3DPoint value_type; +}; + //----------------------------------------------------------------------------- // [SECTION] Color and Size Getters //----------------------------------------------------------------------------- @@ -1273,6 +1287,13 @@ template void PlotScatter(const char* label_id, const T* xs, const CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO +IMPLOT3D_API void PlotScatterG(const char* const label_id, const ImPlot3DGetter getter, const void* data, const int count, const ImPlot3DSpec& spec) { + if (count < 1) + return; + + return PlotScatterEx(label_id, GetterFuncPtr(getter, data, count), spec); +} + //----------------------------------------------------------------------------- // [SECTION] PlotLine //----------------------------------------------------------------------------- @@ -1341,6 +1362,13 @@ IMPLOT3D_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, const CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO +IMPLOT3D_API void PlotLineG(const char* const label_id, const ImPlot3DGetter getter, const void* data, const int count, const ImPlot3DSpec& spec) { + if (count < 2) + return; + + return PlotLineEx(label_id, GetterFuncPtr(getter, data, count), spec); +} + //----------------------------------------------------------------------------- // [SECTION] PlotTriangle //----------------------------------------------------------------------------- @@ -1391,6 +1419,14 @@ IMPLOT3D_TMP void PlotTriangle(const char* label_id, const T* xs, const T* ys, c CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO +IMPLOT3D_API void PlotTriangleG(const char* const label_id, const ImPlot3DGetter getter, const void* data, const int count, + const ImPlot3DSpec& spec) { + if (count < 3) + return; + + return PlotTriangleEx(label_id, GetterFuncPtr(getter, data, count), spec); +} + //----------------------------------------------------------------------------- // [SECTION] PlotQuad //----------------------------------------------------------------------------- @@ -1440,6 +1476,13 @@ IMPLOT3D_TMP void PlotQuad(const char* label_id, const T* xs, const T* ys, const CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO +IMPLOT3D_API void PlotQuadG(const char* const label_id, const ImPlot3DGetter getter, const void* data, const int count, const ImPlot3DSpec& spec) { + if (count < 3) + return; + + return PlotQuadEx(label_id, GetterFuncPtr(getter, data, count), spec); +} + //----------------------------------------------------------------------------- // [SECTION] PlotSurface //----------------------------------------------------------------------------- From 47501aad2296a60698fa055b7ba74f27ecb6d400 Mon Sep 17 00:00:00 2001 From: Oliver Dixon Date: Wed, 1 Jul 2026 13:43:55 +0100 Subject: [PATCH 2/2] fix: Use Im- math functions in favour of std. --- implot3d_demo.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 1f4a2b0..fbcdc1a 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -2460,7 +2460,7 @@ ImPlot3DPoint SineWave(const int idx, const void* const wave_data) { const double x = idx * wd->X; const double y = wd->Z; - const double z = wd->Offset + wd->Amp * std::sin(2.0 * 3.14f * wd->Freq * x); + const double z = wd->Offset + wd->Amp * ImSin(2.0 * 3.14f * wd->Freq * x); return {x, y, z}; } @@ -2472,7 +2472,7 @@ ImPlot3DPoint SawWave(const int idx, const void* const wave_data) { const double y = wd->Z; const double phase = 3.14f * wd->Freq * x; - const double z = wd->Offset + wd->Amp * (-2.0 / 3.14f * std::atan2(std::cos(phase), std::sin(phase))); + const double z = wd->Offset + wd->Amp * (-2.0 / 3.14f * ImAtan2(ImCos(phase), ImSin(phase))); return {x, y, z}; } @@ -2490,8 +2490,8 @@ ImPlot3DPoint Spiral(const int idx, const void*) { const float radius = inner_radius + increment_per_rev * theta / (2.0f * 3.14f); - const float x = radius * std::cos(theta); - const float y = radius * std::sin(theta); + const float x = radius * ImCos(theta); + const float y = radius * ImSin(theta); const float z = t; return {x, y, z};