pthom / imgui_bundle

Your fast track to powerful GUIs: Dear ImGui Bundle, an extensive toolkit for Python and C++ with immediate mode efficiency.
https://pthom.github.io/imgui_bundle/
MIT License
687 stars 72 forks source link

Heatmap Demo #81

Closed nick-0 closed 1 year ago

nick-0 commented 1 year ago

Not sure how to do a PR with the auto-generator, so I thought i would leave this here.

image

Minimal example:


from imgui_bundle import imgui, immapp, implot
import numpy as np

def gui():
    imgui.text("Hello, heatmap!")
    x = np.linspace(-4,4, 401)
    xx = np.outer(x,x)
    yy = np.sinc(xx)
    rows,cols = yy.shape
    n_ticks = 5
    x_ticks = y_ticks =[str(x) for x in np.linspace(-4,4,n_ticks)]

    axis_flags = implot.AxisFlags_.lock | implot.AxisFlags_.no_grid_lines | implot.AxisFlags_.no_tick_marks
    cmap = implot.Colormap_.viridis
    implot.push_colormap(cmap)
    imgui.begin_group()
    if implot.begin_plot("Sinc Function",[imgui.get_content_region_avail().x -70,-1],implot.Flags_.no_legend | implot.Flags_.no_mouse_text):
        implot.setup_axes(None,None, axis_flags, axis_flags)
        implot.setup_axis_ticks(implot.ImAxis_.x1,0,1,n_ticks,x_ticks,False)
        implot.setup_axis_ticks(implot.ImAxis_.y1,0,1,n_ticks,y_ticks,False)
        implot.plot_heatmap("##heatmap", yy, rows, cols, yy.min(), yy.max(), None, [0,1],[1,0],0)
        implot.end_plot()
    imgui.end_group()
    imgui.same_line()
    implot.colormap_scale("##heatmap_scale",yy.min(),yy.max(),imgui.ImVec2(60,-1),"%g", 0,cmap)
    implot.pop_colormap()

immapp.run(
    gui_function=gui,  # The Gui function to run
    window_title="Hello Heatmap",  # the window title
    window_size_auto=True,  # Auto size the application window given its widgets
    # Uncomment the next line to restore window position and size from previous run
    # window_restore_previous_geometry==True
    with_implot=True
)

And the bindings:


m.def("setup_axis_ticks", 
    [](ImAxis axis, double v_min, double v_max, int n_ticks, std::vector<std::string> labels, bool keep_default=false)
    {
        std::vector<const char*> label_char;
        for (std::string const& str : labels){
            label_char.push_back(str.data());
        }
        ImPlot::SetupAxisTicks(axis, v_min, v_max, n_ticks, label_char.data(), keep_default);
    }, py::arg("axis"), py::arg("v_min"), py::arg("v_max"), py::arg("n_ticks"), py::arg("labels"), py::arg("keep_default"),
    "Sets an axis' ticks and optionally the labels for the next plot. To keep the default ticks, set #keep_default=true."
    );

    m.def("plot_heatmap",
        [](const char * label_id, const py::array & values, int rows, int cols, double scale_min = 0, double scale_max=0, const char * label_fmt="%.1f", const ImPlotPoint& bounds_min=ImPlotPoint(0,0), const ImPlotPoint& bounds_max=ImPlotPoint(1,1), ImPlotHeatmapFlags flags = 0)
        {
            auto PlotHeatmap_adapt_c_buffers = [](const char * label_id, const py::array & values, int rows, int cols, double scale_min = 0, double scale_max=0, const char * label_fmt="%.1f",  const ImPlotPoint& bounds_min=ImPlotPoint(0,0), const ImPlotPoint& bounds_max=ImPlotPoint(1,1),  ImPlotHeatmapFlags flags = 0)
            {
                // convert py::array to C standard buffer (const)
                const void * values_from_pyarray = values.data();

                #ifdef _WIN32
                using np_uint_l = uint32_t;
                using np_int_l = int32_t;
                #else
                using np_uint_l = uint64_t;
                using np_int_l = int64_t;
                #endif
                // call the correct template version by casting
                char values_type = values.dtype().char_();
                if (values_type == 'B')
                    ImPlot::PlotHeatmap(label_id, static_cast<const uint8_t *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'b')
                    ImPlot::PlotHeatmap(label_id, static_cast<const int8_t *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'H')
                    ImPlot::PlotHeatmap(label_id, static_cast<const uint16_t *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'h')
                    ImPlot::PlotHeatmap(label_id, static_cast<const int16_t *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'I')
                    ImPlot::PlotHeatmap(label_id, static_cast<const uint32_t *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'i')
                    ImPlot::PlotHeatmap(label_id, static_cast<const int32_t *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'L')
                    ImPlot::PlotHeatmap(label_id, static_cast<const np_uint_l *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'l')
                    ImPlot::PlotHeatmap(label_id, static_cast<const np_int_l *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'f')
                    ImPlot::PlotHeatmap(label_id, static_cast<const float *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'd')
                    ImPlot::PlotHeatmap(label_id, static_cast<const double *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'g')
                    ImPlot::PlotHeatmap(label_id, static_cast<const long double *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                else if (values_type == 'q')
                    ImPlot::PlotHeatmap(label_id, static_cast<const long long *>(values_from_pyarray), rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
                // If we reach this point, the array type is not supported!
                else
                    throw std::runtime_error(std::string("Bad array type ('") + values_type + "') for param values");
            };

            PlotHeatmap_adapt_c_buffers(label_id, values,  rows, cols, scale_min, scale_max, label_fmt, bounds_min, bounds_max, flags);
        },     py::arg("label_id"), py::arg("values"), py::arg("rows"), py::arg("cols"), py::arg("scale_min")= 0 , py::arg("scale_max") = 0, py::arg("label_fmt")="%.1f", py::arg("bounds_min")= (0,0), py::arg("bounds_max")=(1,1),py::arg("flags")=0 );

~Nick

pthom commented 1 year ago

Hi,

Many thanks for this!

Those two functions were excluded by the generator because their signature is too convoluted: see where they were excluded in the generator options

So, I had to add these bindings manually, by adapting the code you provided.

See work in progress in this related PR

FYI, I had to do some adaptations, here is a summary:

e2d69aa demo_heatmap python / use dpi independent sizing 0002a33: rows and cols deduced from py array (i.e do not require those in the python bindings) 5be6292: add stubs (i.e. python signatures, to enable IDE autocomplete)

pthom commented 1 year ago

Update: I merged the PR. thanks!