epezent / implot

Immediate Mode Plotting
MIT License
4.5k stars 495 forks source link

How can I rotate x labels? #523

Open dgm3333 opened 9 months ago

dgm3333 commented 9 months ago

I want to rotate x labels in the plot, so I get something like the following (but with the label actually at the correct tick location inside the plot). I can't find an example so could anyone give me any pointers.

Presumably this would be done using ImPlot::SetupAxisFormat with a custom formatter which calls ImPlot::PlotText with the ImPlotTextFlags_Vertical flag (which internally calls the private function ::AddTextVertical)?

I was also wondering about arbitrary angles so used the alternative as below. . NB there is a bug in this code: Imgui clips the text before it is rotated. This means text at the edges can have parts missing (as you can see in this image with the text at the right)

image

// Rotating text from here: https://github.com/ocornut/imgui/issues/6923


#include "imgui_internal.h"

#include "imgui_internal.h"
//internal bounding boxes
// BB = outermost 'true' bounding box (including bounding box for handles)
// DR = bounding box for drawing
// IN = inner (ie inside bufferzone for boundaries handles
// CE = centron (prob central 10%)
// TL...B = bb for handles for grabbing the various edges/corners
enum bbEnum { BB_BB, BB_DR, BB_IN, BB_CE, BB_TL, BB_TR, BB_BL, BB_BR, BB_L, BB_R, BB_T, BB_B };

int rotation_start_index;
void ImRotateStart()
{
    rotation_start_index = ImGui::GetWindowDrawList()->VtxBuffer.Size;
}

ImVec2 ImRotationCenter()
{
    ImVec2 l(FLT_MAX, FLT_MAX), u(-FLT_MAX, -FLT_MAX); // bounds

    const auto& buf = ImGui::GetWindowDrawList()->VtxBuffer;
    for (int i = rotation_start_index; i < buf.Size; i++)
        l = ImMin(l, buf[i].pos), u = ImMax(u, buf[i].pos);

    return ImVec2((l.x + u.x) / 2, (l.y + u.y) / 2); // or use _ClipRectStack?
}

void ImRotateEnd(float rad, ImVec2 center = ImRotationCenter())
{
    // Adjust the angle to match the standard horizontal orientation
    rad -= IM_PI / 2.0f; // Subtract 90 degrees (PI/2 radians)

    float s = sin(rad), c = cos(rad);
    center = ImRotate(center, s, c) - center;

    auto& buf = ImGui::GetWindowDrawList()->VtxBuffer;
    for (int i = rotation_start_index; i < buf.Size; i++)
        buf[i].pos = ImRotate(buf[i].pos, s, c) - center;
}

void drawRotatedText(std::string textToRotate, float angleToRotate = 0, bbEnum rotationCentre = BB_CE, bool isRads = true)
{
    // Calculate rotation angle in radians
    float rad = angleToRotate;
    if (!isRads)
        rad = rad * IM_PI / 180.0f;

    ImVec2 textStartPosition = ImGui::GetWindowPos() + ImGui::GetCursorPos();           // ImRotateEnd uses GetWindowDrawList which is based on position of the screen

    // Calculate text size to determine bounding box
    ImVec2 textSize = ImGui::CalcTextSize(textToRotate.c_str());

    ImVec2 rotationCenter;

    // Calculate rotation center based on bbEnum value
    switch (rotationCentre)
    {
    case BB_CE:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x * 0.5f, textStartPosition.y + textSize.y * 0.5f);
        break;
    case BB_TL:
        rotationCenter = textStartPosition;
        break;
    case BB_TR:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x, textStartPosition.y);
        break;
    case BB_BL:
        rotationCenter = ImVec2(textStartPosition.x, textStartPosition.y + textSize.y);
        break;
    case BB_BR:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x, textStartPosition.y + textSize.y);
        break;
    case BB_L:
        rotationCenter = ImVec2(textStartPosition.x, textStartPosition.y + textSize.y / 2);
        break;
    case BB_R:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x, textStartPosition.y + textSize.y / 2);
        break;
    case BB_T:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x / 2, textStartPosition.y);
        break;
    case BB_B:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x / 2, textStartPosition.y + textSize.y);
        break;
    default:
        std::cout << "ERROR: invalid rotationCentre in drawRotatedText\n";
        return;
    }

    // Start rotation
    ImRotateStart();

    //std::cout << ImGui::GetCursorPosX() << " " << ImGui::GetCursorPosY() << std::endl;
    // Render the text
    ImGui::Text(textToRotate.c_str());

    // Apply the rotation
    ImRotateEnd(rad, rotationCenter);
}

// Function to display the ImPlot child with rotated X-axis tick labels
void rotatedXLabelExample() {
    ImGui::Begin("Rotated Label Example");

    // Dummy data for plotting
    static double maxTime = 10.0;
    static std::vector<double> modtimeCosts = { 2.0, 4.0, 6.0, 8.0, 5.0 };
    static std::vector<std::string> modNames = { "Module 1", "Module 2", "Module 3", "Module 4", "Module 5" };

    ImGui::BeginChild("ModuleProcessingTimeCostChild", ImVec2(0, 0), false);

    ImVec2 spaceAvail = ImGui::GetContentRegionAvail();

    if (ImPlot::BeginPlot("Module Processing Time Cost", "Module", "Time Cost", ImVec2(-1, spaceAvail.y - 250)))
    {
        // Customize X-axis tick labels
        double x_ticks[5];
        const char* x_labels[5];
        for (size_t i = 0; i < modNames.size(); ++i)
        {
            x_ticks[i] = static_cast<double>(i);
            x_labels[i] = modNames[i].c_str();
        }

        ImPlot::SetupAxisTicks(ImAxis_X1, x_ticks, 5, x_labels, true);

        ImPlot::SetupAxes("Module", "Time (secs)", ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
        ImPlot::SetupAxisLimits(ImAxis_Y1, 0, maxTime, ImGuiCond_Always);

        ImPlot::PlotBars("Processing Time Cost", modtimeCosts.data(), modtimeCosts.size());

        ImPlot::EndPlot();
    }

    // Rotate the X-axis tick labels
    float xLoc = 70;
    float rightMargin = 30;
    float xStep = (spaceAvail.x - xLoc - rightMargin) / (float)modNames.size();
    float yLoc = ImGui::GetCursorPosY();
    for (int i = 0; i < modNames.size(); ++i) {
        ImVec2 textSize = ImGui::CalcTextSize(modNames[i].c_str());
        ImGui::SetCursorPos({ xLoc, yLoc + textSize.x });
        // This has to be called from within a plot
        //ImPlot::PlotText(modNames[i].c_str(), xLoc, yLoc + textSize.x, { 0, 0 }, ImPlotTextFlags_Vertical);
        drawRotatedText(modNames[i], IM_PI * 1.5, BB_L);
        xLoc += xStep;
    }

    ImGui::EndChild();
    ImGui::End();
}