Skip to content

Commit

Permalink
merge: pull request #46 from brenocq/box-aspect-scale
Browse files Browse the repository at this point in the history
Box scaling (custom aspect ratios)
  • Loading branch information
brenocq authored Jan 4, 2025
2 parents 10f0beb + 280146e commit 4be7ae6
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 47 deletions.
113 changes: 73 additions & 40 deletions implot3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
#define IMGUI_DEFINE_MATH_OPERATORS
#endif

// We define this to avoid accidentally using the deprecated API
#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS
#define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS
#endif

#include "implot3d.h"
#include "implot3d_internal.h"

Expand Down Expand Up @@ -824,7 +829,6 @@ void RenderTickLabels(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImP
}

void RenderAxisLabels(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPlot3DPoint* corners, const ImVec2* corners_pix, const int axis_corners[3][2]) {
ImPlot3DPoint range_center = plot.RangeCenter();
for (int a = 0; a < 3; a++) {
const ImPlot3DAxis& axis = plot.Axes[a];
if (!axis.HasLabel())
Expand All @@ -841,12 +845,13 @@ void RenderAxisLabels(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImP
continue;

// Position at the end of the axis
ImPlot3DPoint label_pos = (corners[idx0] + corners[idx1]) * 0.5f;
ImPlot3DPoint label_pos = (PlotToNDC(corners[idx0]) + PlotToNDC(corners[idx1])) * 0.5f;
ImPlot3DPoint center_dir = label_pos.Normalized();
// Add offset
label_pos += (label_pos - range_center) * 0.4f;
label_pos += center_dir * 0.3f;

// Convert to pixel coordinates
ImVec2 label_pos_pix = PlotToPixels(label_pos);
ImVec2 label_pos_pix = NDCToPixels(label_pos);

// Adjust label position and angle
ImU32 col_ax_txt = GetStyleColorU32(ImPlot3DCol_AxisText);
Expand Down Expand Up @@ -1068,35 +1073,34 @@ double NiceNum(double x, bool round) {
return nf * ImPow(10.0, expv);
}

void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data) {
void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, float pixels, ImPlot3DFormatter formatter, void* formatter_data) {
if (range.Min == range.Max)
return;
const int nMinor = 5;
const int nMajor = 3;
const int nMinor = ImMin(ImMax(1, (int)IM_ROUND(pixels / 30.0f)), 5);
const int nMajor = ImMax(2, (int)IM_ROUND(pixels / 80.0f));
const int max_ticks_labels = 7;
const double nice_range = NiceNum(range.Size() * 0.99, false);
const double interval = NiceNum(nice_range / (nMajor - 1), true);
const double graphmin = floor(range.Min / interval) * interval;
const double graphmax = ceil(range.Max / interval) * interval;
bool first_major_set = false;
int first_major_idx = 0;
const int idx0 = ticker.TickCount(); // ticker may have user custom ticks
ImVec2 total_size(0, 0);
const int idx0 = ticker.TickCount(); // Ticker may have user custom ticks
for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) {
// is this zero? combat zero formatting issues
// Is this zero? combat zero formatting issues
if (major - interval < 0 && major + interval > 0)
major = 0;
if (range.Contains((float)major)) {
if (!first_major_set) {
first_major_idx = ticker.TickCount();
first_major_set = true;
}
total_size += ticker.AddTick(major, true, true, formatter, formatter_data).LabelSize;
ticker.AddTick(major, true, true, formatter, formatter_data);
}
for (int i = 1; i < nMinor; ++i) {
double minor = major + i * interval / nMinor;
if (range.Contains((float)minor)) {
total_size += ticker.AddTick(minor, false, true, formatter, formatter_data).LabelSize;
ticker.AddTick(minor, false, true, formatter, formatter_data);
}
}
}
Expand Down Expand Up @@ -1231,6 +1235,22 @@ void ShowPlotContextMenu(ImPlot3DPlot& plot) {
}

ImGui::Separator();

if ((ImGui::BeginMenu("Box"))) {
ImGui::PushItemWidth(75);
float temp_scale[3] = {plot.BoxScale[0], plot.BoxScale[1], plot.BoxScale[2]};
if (ImGui::DragFloat("Scale X", &temp_scale[0], 0.01f, 0.1f, 3.0f))
plot.BoxScale[0] = ImMax(temp_scale[0], 0.01f);
if (ImGui::DragFloat("Scale Y", &temp_scale[1], 0.01f, 0.1f, 3.0f))
plot.BoxScale[1] = ImMax(temp_scale[1], 0.01f);
if (ImGui::DragFloat("Scale Z", &temp_scale[2], 0.01f, 0.1f, 3.0f))
plot.BoxScale[2] = ImMax(temp_scale[2], 0.01f);
ImGui::PopItemWidth();
ImGui::EndMenu();
}

ImGui::Separator();

if ((ImGui::BeginMenu("Legend"))) {
if (ShowLegendContextMenu(plot.Items.Legend, !ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoLegend)))
ImFlipFlag(plot.Flags, ImPlot3DFlags_NoLegend);
Expand Down Expand Up @@ -1465,6 +1485,15 @@ void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, dou
GImPlot3D->CurrentPlot->FitThisFrame = false;
}

void SetupBoxScale(float x, float y, float z) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
"SetupBoxScale() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!");
IM_ASSERT_USER_ERROR(x > 0.0f && y > 0.0f && z > 0.0f, "SetupBoxScale() requires all aspect ratios to be greater than 0!");
ImPlot3DPlot& plot = *gp.CurrentPlot;
plot.BoxScale = ImPlot3DPoint(x, y, z);
}

void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
Expand Down Expand Up @@ -1552,22 +1581,22 @@ ImPlot3DPoint PixelsToPlotPlane(const ImVec2& pix, ImPlane3D plane, bool mask) {
return O + D * t;
};

// Helper lambda to check if point P is within the plot box
auto InRange = [&](const ImPlot3DPoint& P) {
return P.x >= -0.5 && P.x <= 0.5 &&
P.y >= -0.5 && P.y <= 0.5 &&
P.z >= -0.5 && P.z <= 0.5;
};

// Compute which plane to intersect with
bool active_faces[3];
ComputeActiveFaces(active_faces, plot.Rotation, plot.Axes);

// Calculate intersection point with the planes
ImPlot3DPoint P = IntersectPlane(active_faces[plane] ? 0.5f : -0.5f);
ImPlot3DPoint P = IntersectPlane(active_faces[plane] ? 0.5f * plot.BoxScale[plane] : -0.5f * plot.BoxScale[plane]);
if (P.IsNaN())
return P;

// Helper lambda to check if point P is within the plot box
auto InRange = [&](const ImPlot3DPoint& P) {
return P.x >= -0.5f * plot.BoxScale.x && P.x <= 0.5f * plot.BoxScale.x &&
P.y >= -0.5f * plot.BoxScale.y && P.y <= 0.5f * plot.BoxScale.y &&
P.z >= -0.5f * plot.BoxScale.z && P.z <= 0.5f * plot.BoxScale.z;
};

// Handle mask (if one of the intersections is out of range, set it to NAN)
if (mask) {
switch (plane) {
Expand Down Expand Up @@ -1626,8 +1655,13 @@ ImPlot3DPoint PlotToNDC(const ImPlot3DPoint& point) {
SetupLock();

ImPlot3DPoint ndc_point;
for (int i = 0; i < 3; i++)
ndc_point[i] = plot.Axes[i].PlotToNDC(point[i]);
for (int i = 0; i < 3; i++) {
ImPlot3DAxis& axis = plot.Axes[i];
float ndc_range = 0.5f * plot.BoxScale[i];
float t = (point[i] - axis.Range.Min) / (axis.Range.Max - axis.Range.Min);
t *= plot.BoxScale[i];
ndc_point[i] = ImPlot3D::ImHasFlag(axis.Flags, ImPlot3DAxisFlags_Invert) ? (ndc_range - t) : (t - ndc_range);
}
return ndc_point;
}

Expand All @@ -1638,8 +1672,13 @@ ImPlot3DPoint NDCToPlot(const ImPlot3DPoint& point) {
SetupLock();

ImPlot3DPoint plot_point;
for (int i = 0; i < 3; i++)
plot_point[i] = plot.Axes[i].NDCToPlot(point[i]);
for (int i = 0; i < 3; i++) {
ImPlot3DAxis& axis = plot.Axes[i];
float ndc_range = 0.5f * plot.BoxScale[i];
float t = ImPlot3D::ImHasFlag(axis.Flags, ImPlot3DAxisFlags_Invert) ? (ndc_range - point[i]) : (point[i] + ndc_range);
t /= plot.BoxScale[i];
plot_point[i] = axis.Range.Min + t * (axis.Range.Max - axis.Range.Min);
}
return plot_point;
}

Expand All @@ -1649,9 +1688,8 @@ ImVec2 NDCToPixels(const ImPlot3DPoint& point) {
ImPlot3DPlot& plot = *gp.CurrentPlot;
SetupLock();

float zoom = ImMin(plot.PlotRect.GetWidth(), plot.PlotRect.GetHeight()) / 1.8f;
ImVec2 center = plot.PlotRect.GetCenter();
ImPlot3DPoint point_pix = zoom * (plot.Rotation * point);
ImPlot3DPoint point_pix = plot.GetBoxZoom() * (plot.Rotation * point);
point_pix.y *= -1.0f; // Invert y-axis
point_pix.x += center.x;
point_pix.y += center.y;
Expand All @@ -1666,7 +1704,7 @@ ImPlot3DRay PixelsToNDCRay(const ImVec2& pix) {
SetupLock();

// Calculate zoom factor and plot center
float zoom = ImMin(plot.PlotRect.GetWidth(), plot.PlotRect.GetHeight()) / 1.8f;
float zoom = plot.GetBoxZoom();
ImVec2 center = plot.PlotRect.GetCenter();

// Undo screen transformations to get back to NDC space
Expand Down Expand Up @@ -1820,11 +1858,11 @@ void HandleInput(ImPlot3DPlot& plot) {
ImPlot3DPoint delta_pixels(delta.x, -delta.y, 0.0f);

// Convert delta to NDC space
float zoom = ImMin(plot.PlotRect.GetWidth(), plot.PlotRect.GetHeight()) / 1.8f;
float zoom = plot.GetBoxZoom();
ImPlot3DPoint delta_NDC = plot.Rotation.Inverse() * (delta_pixels / zoom);

// Convert delta to plot space
ImPlot3DPoint delta_plot = delta_NDC * (plot.RangeMax() - plot.RangeMin());
ImPlot3DPoint delta_plot = delta_NDC * (plot.RangeMax() - plot.RangeMin()) / plot.BoxScale;

// Adjust delta for inverted axes
for (int i = 0; i < 3; i++) {
Expand Down Expand Up @@ -2060,7 +2098,8 @@ void SetupLock() {
for (int i = 0; i < 3; i++) {
ImPlot3DAxis& axis = plot.Axes[i];
axis.Ticker.Reset();
axis.Locator(axis.Ticker, axis.Range, axis.Formatter, axis.FormatterData);
float pixels = plot.GetBoxZoom() * plot.BoxScale[i];
axis.Locator(axis.Ticker, axis.Range, pixels, axis.Formatter, axis.FormatterData);
}

// Render title
Expand Down Expand Up @@ -3016,16 +3055,6 @@ void ImPlot3DAxis::ApplyFit() {
FitExtents.Max = -HUGE_VAL;
}

float ImPlot3DAxis::PlotToNDC(float value) const {
float t = (value - Range.Min) / (Range.Max - Range.Min);
return ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_Invert) ? (0.5f - t) : (t - 0.5f);
}

float ImPlot3DAxis::NDCToPlot(float value) const {
float t = ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_Invert) ? (0.5f - value) : (value + 0.5f);
return Range.Min + t * (Range.Max - Range.Min);
}

//-----------------------------------------------------------------------------
// [SECTION] ImPlot3DPlot
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -3058,6 +3087,10 @@ void ImPlot3DPlot::SetRange(const ImPlot3DPoint& min, const ImPlot3DPoint& max)
Axes[2].SetRange(min.z, max.z);
}

float ImPlot3DPlot::GetBoxZoom() const {
return ImMin(PlotRect.GetWidth(), PlotRect.GetHeight()) / 1.8f;
}

//-----------------------------------------------------------------------------
// [SECTION] ImPlot3DStyle
//-----------------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions implot3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ IMPLOT3D_API void SetupAxes(const char* x_label, const char* y_label, const char
// Sets the X/Y/Z axes range limits. If ImPlotCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits)
IMPLOT3D_API void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max, ImPlot3DCond cond = ImPlot3DCond_Once);

// Sets the plot box X/Y/Z scale. A scale of 1.0 is the default. Values greater than 1.0 enlarge the plot, while values between 0.0 and 1.0 shrink it.
IMPLOT3D_API void SetupBoxScale(float x, float y, float z);

IMPLOT3D_API void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags = 0);

//-----------------------------------------------------------------------------
Expand Down
34 changes: 34 additions & 0 deletions implot3d_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@
// [SECTION] User Namespace
// [SECTION] Helpers
// [SECTION] Plots
// [SECTION] Axes
// [SECTION] Custom
// [SECTION] Demo Window
// [SECTION] Style Editor
// [SECTION] User Namespace Implementation

// We define this to avoid accidentally using the deprecated API
#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS
#define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS
#endif

#include "implot3d.h"
#include "implot3d_internal.h"

Expand Down Expand Up @@ -535,6 +541,30 @@ void DemoNaNValues() {
}
}

//-----------------------------------------------------------------------------
// [SECTION] Axes
//-----------------------------------------------------------------------------

void DemoBoxScale() {
constexpr int N = 100;
float xs[N], ys[N], zs[N];
for (int i = 0; i < N; ++i) {
float t = i / (float)(N - 1);
xs[i] = sinf(t * 2.0f * IM_PI);
ys[i] = cosf(t * 4.0f * IM_PI);
zs[i] = t * 2.0f - 1.0f;
}

static float scale[3] = {1.0f, 1.0f, 1.0f};
ImGui::SliderFloat3("Box Scale", scale, 0.1f, 2.0f, "%.2f");

if (ImPlot3D::BeginPlot("##BoxScale")) {
ImPlot3D::SetupBoxScale(scale[0], scale[1], scale[2]);
ImPlot3D::PlotLine("3D Curve", xs, ys, zs, N);
ImPlot3D::EndPlot();
}
}

//-----------------------------------------------------------------------------
// [SECTION] Custom
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -721,6 +751,10 @@ void ShowDemoWindow(bool* p_open) {
DemoHeader("NaN Values", DemoNaNValues);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Axes")) {
DemoHeader("Box Scale", DemoBoxScale);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Custom")) {
DemoHeader("Custom Styles", DemoCustomStyles);
DemoHeader("Custom Rendering", DemoCustomRendering);
Expand Down
15 changes: 8 additions & 7 deletions implot3d_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ struct ImPlot3DTicker;
// [SECTION] Callbacks
//------------------------------------------------------------------------------

typedef void (*ImPlot3DLocator)(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data);
typedef void (*ImPlot3DLocator)(ImPlot3DTicker& ticker, const ImPlot3DRange& range, float pixels, ImPlot3DFormatter formatter, void* formatter_data);

//-----------------------------------------------------------------------------
// [SECTION] Structs
Expand Down Expand Up @@ -505,8 +505,6 @@ struct ImPlot3DAxis {
bool IsAutoFitting() const;
void ExtendFit(float value);
void ApplyFit();
float PlotToNDC(float value) const;
float NDCToPlot(float value) const;
};

// Holds plot state information that must persist after EndPlot
Expand All @@ -521,9 +519,10 @@ struct ImPlot3DPlot {
ImRect FrameRect; // Outermost bounding rectangle that encapsulates whole the plot/title/padding/etc
ImRect CanvasRect; // Frame rectangle reduced by padding
ImRect PlotRect; // Bounding rectangle for the actual plot area
// Rotation & Axes
ImPlot3DQuat Rotation;
ImPlot3DAxis Axes[3];
// Rotation & axes & box
ImPlot3DQuat Rotation; // Current rotation quaternion
ImPlot3DAxis Axes[3]; // X, Y, Z axes
ImPlot3DPoint BoxScale; // Scale factor for plot box X, Y, Z axes
// Animation
float AnimationTime; // Remaining animation time
ImPlot3DQuat RotationAnimationEnd; // End rotation for animation
Expand Down Expand Up @@ -551,6 +550,7 @@ struct ImPlot3DPlot {
Rotation = ImPlot3DQuat(0.0f, 0.0f, 0.0f, 1.0f);
for (int i = 0; i < 3; i++)
Axes[i] = ImPlot3DAxis();
BoxScale = ImPlot3DPoint(1.0f, 1.0f, 1.0f);
AnimationTime = 0.0f;
RotationAnimationEnd = Rotation;
SetupLocked = false;
Expand All @@ -576,6 +576,7 @@ struct ImPlot3DPlot {
ImPlot3DPoint RangeMax() const;
ImPlot3DPoint RangeCenter() const;
void SetRange(const ImPlot3DPoint& min, const ImPlot3DPoint& max);
float GetBoxZoom() const;
};

struct ImPlot3DContext {
Expand Down Expand Up @@ -681,7 +682,7 @@ int Formatter_Default(float value, char* buff, int size, void* data);
// [SECTION] Locator
//------------------------------------------------------------------------------

void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data);
void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, float pixels, ImPlot3DFormatter formatter, void* formatter_data);

} // namespace ImPlot3D

Expand Down

0 comments on commit 4be7ae6

Please sign in to comment.