diff --git a/implot.h b/implot.h index a64aaf4e..f134cccb 100644 --- a/implot.h +++ b/implot.h @@ -102,6 +102,7 @@ typedef int ImPlotStemsFlags; // -> ImPlotStemsFlags_ typedef int ImPlotInfLinesFlags; // -> ImPlotInfLinesFlags_ typedef int ImPlotPieChartFlags; // -> ImPlotPieChartFlags_ typedef int ImPlotHeatmapFlags; // -> ImPlotHeatmapFlags_ +typedef int ImPlotQuiverFlags; // -> ImPlotQuiverFlags_ typedef int ImPlotHistogramFlags; // -> ImPlotHistogramFlags_ typedef int ImPlotDigitalFlags; // -> ImPlotDigitalFlags_ typedef int ImPlotImageFlags; // -> ImPlotImageFlags_ @@ -309,6 +310,14 @@ enum ImPlotHeatmapFlags_ { ImPlotHeatmapFlags_ColMajor = 1 << 10, // data will be read in column major order }; +// Flags for PlotQuiver +enum ImPlotQuiverFlags_ { + ImPlotQuiverFlags_None = 0, // default + ImPlotQuiverFlags_NoClip = 1 << 10, // arrows on the edge of a plot will not be clipped + ImPlotQuiverFlags_FixedSize = 1 << 11, // all arrows will have the same size + ImPlotQuiverFlags_ColorByMagnitude = 1 << 12 // arrow colors will be mapped to their magnitudes +}; + // Flags for PlotHistogram and PlotHistogram2D enum ImPlotHistogramFlags_ { ImPlotHistogramFlags_None = 0, // default @@ -915,6 +924,9 @@ IMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int // Plots a 2D heatmap chart. Values are expected to be in row-major order by default. Leave #scale_min and scale_max both at 0 for automatic color scaling, or set them to a predefined range. #label_fmt can be set to nullptr for no labels. IMPLOT_TMP void PlotHeatmap(const char* label_id, const T* 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); +// Plots a standard 2D quiver plot. The direction and magnitude of the arrows are determined by #us and #vs. Set #mag_min and #mag_max to specify a range of magnitudes to map to the arrow colors. Set mag_min = mag_max = 0 to use the full colormap range. +IMPLOT_TMP void PlotQuiver(const char* label_id, const T* xs, const T* ys,const T* us, const T* vs, int count, double mag_min=0, double mag_max=0, ImPlotQuiverFlags flags=0, int offset=0, int stride=sizeof(T)); + // Plots a horizontal histogram. #bins can be a positive integer or an ImPlotBin_ method. If #range is left unspecified, the min/max of #values will be used as the range. // Otherwise, outlier values outside of the range are not binned. The largest bin count or density is returned. IMPLOT_TMP double PlotHistogram(const char* label_id, const T* values, int count, int bins=ImPlotBin_Sturges, double bar_scale=1.0, ImPlotRange range=ImPlotRange(), ImPlotHistogramFlags flags=0); @@ -1142,6 +1154,8 @@ IMPLOT_API void SetNextFillStyle(const ImVec4& col = IMPLOT_AUTO_COL, float alph IMPLOT_API void SetNextMarkerStyle(ImPlotMarker marker = IMPLOT_AUTO, float size = IMPLOT_AUTO, const ImVec4& fill = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO, const ImVec4& outline = IMPLOT_AUTO_COL); // Set the error bar style for the next item only. IMPLOT_API void SetNextErrorBarStyle(const ImVec4& col = IMPLOT_AUTO_COL, float size = IMPLOT_AUTO, float weight = IMPLOT_AUTO); +// Set the quiver style for the next item only. +IMPLOT_API void SetNextQuiverStyle(float size, const ImVec4& col = IMPLOT_AUTO_COL); // Gets the last item primary color (i.e. its legend icon color) IMPLOT_API ImVec4 GetLastItemColor(); diff --git a/implot_demo.cpp b/implot_demo.cpp index 02e3aaad..1ed65ef1 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -78,6 +78,16 @@ void StyleSeaborn(); namespace ImPlot { +static void HelpMarker(const char* desc) { + ImGui::TextDisabled("(?)"); + if (ImGui::BeginItemTooltip()) { + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + template inline T RandomRange(T min, T max) { T scale = rand() / (T) RAND_MAX; @@ -737,6 +747,75 @@ void Demo_Heatmaps() { //----------------------------------------------------------------------------- +void Demo_QuiverPlots(){ + static float xs[100], ys[100], us[100], vs[100]; + for (int i = 0; i < 10; ++i) { + for(int j = 0; j < 10; ++j){ + int idx = i*10 + j; + xs[idx] = ((float)j * 0.1f) - 0.5; + ys[idx] = ((float)i * 0.1f) - 0.5; + + // Taylor-Green vortex + float k = 2.0f * 3.14159f; + us[idx] = sinf(k * xs[idx]) * cosf(k * ys[idx]); + vs[idx] = -cosf(k * xs[idx]) * sinf(k * ys[idx]); + } + } + + static float mag_min = 0.00f; + static float mag_max = 1.0f; + static float base_size = 12.0f; + static ImPlotColormap map = ImPlotColormap_Viridis; + + if (ImPlot::ColormapButton(ImPlot::GetColormapName(map), ImVec2(225,0), map)) { + map = (map + 1) % ImPlot::GetColormapCount(); + } + ImGui::SameLine(); + ImGui::LabelText("##Colormap Index", "%s", "Change Colormap"); + + ImGui::SetNextItemWidth(225); + ImGui::DragFloatRange2("Min / Max Magnitude", &mag_min, &mag_max, 0.01f, -20, 20,nullptr,nullptr,ImGuiSliderFlags_AlwaysClamp); + if (mag_max < mag_min) { + mag_max = mag_min; + } + ImGui::SameLine(); + HelpMarker("Minumum and maximum magnitudes for color mapping"); + + ImGui::SetNextItemWidth(225); + ImGui::DragFloat("Base Size", &base_size, 0.1f, 0, 100); + ImGui::SameLine(); + HelpMarker("Maximum arrow size in pixels. The actual size will depend on the arrow's magnitude"); + + static ImPlotQuiverFlags qv_flags = ImPlotQuiverFlags_ColorByMagnitude; + + CHECKBOX_FLAG(qv_flags, ImPlotQuiverFlags_NoClip); + ImGui::SameLine(); + HelpMarker("Arrows on the edge of the plot will not be clipped"); + + CHECKBOX_FLAG(qv_flags, ImPlotQuiverFlags_FixedSize); + ImGui::SameLine(); + HelpMarker("All arrows will have the length set to base size"); + + CHECKBOX_FLAG(qv_flags, ImPlotQuiverFlags_ColorByMagnitude); + ImGui::SameLine(); + HelpMarker("Arrow will be colored by on their magnitudes"); + + ImPlot::PushColormap(map); + if (ImPlot::BeginPlot("Quiver Plot", ImVec2(ImGui::GetTextLineHeight()*28, ImGui::GetTextLineHeight()*28))) { + ImPlot::SetupAxisTicks(ImAxis_X1, -0.5, 0.5, 11); + ImPlot::SetupAxisTicks(ImAxis_Y1, -0.5, 0.5, 11); + ImPlot::SetNextQuiverStyle(base_size, ImPlot::GetColormapColor(1)); + ImPlot::SetupAxes("x", "y"); + ImPlot::PlotQuiver("Magnitude", xs, ys, us, vs, 100, mag_min, mag_max, qv_flags); + ImPlot::EndPlot(); + } + ImGui::SameLine(); + ImPlot::ColormapScale("##QuiverScale", mag_min, mag_max); + ImPlot::PopColormap(); +} + +//----------------------------------------------------------------------------- + void Demo_Histogram() { static ImPlotHistogramFlags hist_flags = ImPlotHistogramFlags_Density; static int bins = 50; @@ -2286,6 +2365,7 @@ void ShowDemoWindow(bool* p_open) { DemoHeader("Infinite Lines", Demo_InfiniteLines); DemoHeader("Pie Charts", Demo_PieCharts); DemoHeader("Heatmaps", Demo_Heatmaps); + DemoHeader("Quiver Plots", Demo_QuiverPlots); DemoHeader("Histogram", Demo_Histogram); DemoHeader("Histogram 2D", Demo_Histogram2D); DemoHeader("Digital Plots", Demo_DigitalPlots); diff --git a/implot_internal.h b/implot_internal.h index 7d3ff49e..3f74264d 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -1199,6 +1199,7 @@ struct ImPlotNextItemData { ImPlotMarker Marker; float MarkerSize; float MarkerWeight; + float QuiverSize; float FillAlpha; float ErrorBarSize; float ErrorBarWeight; @@ -1218,6 +1219,7 @@ struct ImPlotNextItemData { LineWeight = MarkerSize = MarkerWeight = FillAlpha = ErrorBarSize = ErrorBarWeight = DigitalBitHeight = DigitalBitGap = IMPLOT_AUTO; Marker = IMPLOT_AUTO; HasHidden = Hidden = false; + } }; diff --git a/implot_items.cpp b/implot_items.cpp index 68dc47da..80b82d70 100644 --- a/implot_items.cpp +++ b/implot_items.cpp @@ -351,6 +351,13 @@ void SetNextErrorBarStyle(const ImVec4& col, float size, float weight) { gp.NextItemData.ErrorBarWeight = weight; } +void SetNextQuiverStyle(float size, const ImVec4& col) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.QuiverSize = size; + gp.NextItemData.Colors[ImPlotCol_Fill] = col; + +} + ImVec4 GetLastItemColor() { ImPlotContext& gp = *GImPlot; if (gp.PreviousItem) @@ -559,6 +566,27 @@ struct IndexerConst { // [SECTION] Getters //----------------------------------------------------------------------------- +// Implementation of a 3D point with double precision. +struct ImPlotPoint3D { + double x, y, z; + constexpr ImPlotPoint3D() : x(0.0), y(0.0), z(0.0) { } + constexpr ImPlotPoint3D(double _x, double _y, double _z) : x(_x), y(_y), z(_z) { } + double& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1 || idx == 2); return ((double*)(void*)(char*)this)[idx]; } + double operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1 || idx == 2); return ((const double*)(const void*)(const char*)this)[idx]; } +}; + +// Implementation of a 4D point with double precision. +struct ImPlotPoint4D { + double x, y, z, w; + IMPLOT_API constexpr ImPlotPoint4D() : x(0.0), y(0.0), z(0.0), w(0.0) { } + IMPLOT_API constexpr ImPlotPoint4D(double _x, double _y, double _z, double _w) : x(_x), y(_y), z(_z), w(_w) { } + IMPLOT_API constexpr ImPlotPoint4D(const ImVec2& p, const ImVec2& q) : x((double)p.x), y((double)p.y), z((double)q.x), w((double)q.y) { } + IMPLOT_API constexpr ImPlotPoint4D(const ImVec4& r) : x((double)r.x), y((double)r.y), z((double)r.z), w((double)r.w) { } + IMPLOT_API double& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1 || idx == 2 || idx == 3); return ((double*)(void*)(char*)this)[idx]; } + IMPLOT_API double operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1 || idx == 2 || idx == 3); return ((const double*)(const void*)(const char*)this)[idx]; } +}; + + template struct GetterXY { GetterXY(_IndexerX x, _IndexerY y, int count) : IndxerX(x), IndxerY(y), Count(count) { } @@ -570,15 +598,6 @@ struct GetterXY { const int Count; }; -// Double precision point with three coordinates used by ImPlot. -struct ImPlotPoint3D { - double x, y, z; - constexpr ImPlotPoint3D() : x(0.0), y(0.0), z(0.0) { } - constexpr ImPlotPoint3D(double _x, double _y, double _z) : x(_x), y(_y), z(_z) { } - double& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1 || idx == 2); return ((double*)(void*)(char*)this)[idx]; } - double operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1 || idx == 2); return ((const double*)(const void*)(const char*)this)[idx]; } -}; - template struct GetterXYZ { GetterXYZ(_IndexerX x, _IndexerY y, _IndexerZ z, int count) : IndxerX(x), IndxerY(y), IndxerZ(z), Count(count) { } @@ -591,6 +610,22 @@ struct GetterXYZ { const int Count; }; +template +struct GetterXYZW { + GetterXYZW(_IndexerX x, _IndexerY y, _IndexerZ z, _IndexerW w, int count) + : IndxerX(x), IndxerY(y), IndxerZ(z), IndxerW(w), Count(count) { } + + template IMPLOT_INLINE ImPlotPoint4D operator()(I idx) const { + return ImPlotPoint4D(IndxerX(idx), IndxerY(idx), IndxerZ(idx), IndxerW(idx)); + } + + const _IndexerX IndxerX; + const _IndexerY IndxerY; + const _IndexerZ IndxerZ; + const _IndexerW IndxerW; + const int Count; +}; + /// Interprets a user's function pointer as ImPlotPoints struct GetterFuncPtr { GetterFuncPtr(ImPlotGetter getter, void* data, int count) : @@ -747,6 +782,20 @@ struct Fitter2 { const _Getter2& Getter2; }; + +template +struct FitterVector { + FitterVector(const _Getter1& getter) : Getter(getter) { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + for (int i = 0; i < Getter.Count; ++i) { + ImPlotPoint4D p = Getter(i); + x_axis.ExtendFitWith(y_axis, p.x, p.y); + y_axis.ExtendFitWith(x_axis, p.y, p.x); + } + } + const _Getter1& Getter; +}; + template struct FitterBarV { FitterBarV(const _Getter1& getter1, const _Getter2& getter2, double width) : @@ -1686,6 +1735,78 @@ struct RendererCircleLine : RendererBase { mutable ImVec2 UV1; }; +template +struct RendererVectorFill : RendererBase { + RendererVectorFill(const _Getter& getter, const ImVec2* marker, int count, float size, ImU32 col, double min_mag, double max_mag, bool color_coded, bool normalized) : + RendererBase(getter.Count, (count-2)*3, count), + Getter(getter), + Marker(marker), + Count(count), + Size(size), + Col(col), + MinMag(min_mag), + MaxMag(max_mag), + ColorCoded(color_coded), + Normalized(normalized) + { } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImPlotPoint4D vec = Getter(prim); + + ImVec2 p; + p.x = this->Transformer.Tx(vec.x); + p.y = this->Transformer.Ty(vec.y); + + float theta = ImAtan2(-vec.w, vec.z); + float cos_theta = ImCos(theta); + float sin_theta = ImSin(theta); + double mag = (double)ImSqrt(vec.z*vec.z + vec.w*vec.w); + + double size_scale = Normalized ? 1.0 : ImClamp(ImRemap01(mag, MinMag, MaxMag), 0.0, 1.0); + ImU32 color_final = Col; + if (ColorCoded) { + double t = ImClamp(ImRemap01(mag, MinMag, MaxMag), 0.0, 1.0); + color_final = ImColor(SampleColormap((float)t)); + } + + bool should_render = (p.x >= cull_rect.Min.x && p.y >= cull_rect.Min.y && + p.x <= cull_rect.Max.x && p.y <= cull_rect.Max.y); + if (Normalized) should_render = should_render && (mag > 1e-6); + + if (should_render) { + for (int i = 0; i < Count; i++) { + float rotated_x = Marker[i].x * cos_theta - Marker[i].y * sin_theta; + float rotated_y = Marker[i].x * sin_theta + Marker[i].y * cos_theta; + draw_list._VtxWritePtr[0].pos.x = p.x + rotated_x * Size * size_scale; + draw_list._VtxWritePtr[0].pos.y = p.y + rotated_y * Size * size_scale; + draw_list._VtxWritePtr[0].uv = UV; + draw_list._VtxWritePtr[0].col = color_final; + draw_list._VtxWritePtr++; + } + for (int i = 2; i < Count; i++) { + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + i - 1); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + i); + draw_list._IdxWritePtr += 3; + } + draw_list._VtxCurrentIdx += (ImDrawIdx)Count; + return true; + } + return false; + } + const _Getter& Getter; + const ImVec2* Marker; + const int Count; + const float Size; + const ImU32 Col; + mutable ImVec2 UV; + double MinMag; + double MaxMag; + bool ColorCoded; + bool Normalized; +}; static const ImVec2 MARKER_FILL_CIRCLE[10] = {ImVec2(1.0f, 0.0f), ImVec2(0.809017f, 0.58778524f),ImVec2(0.30901697f, 0.95105654f),ImVec2(-0.30901703f, 0.9510565f),ImVec2(-0.80901706f, 0.5877852f),ImVec2(-1.0f, 0.0f),ImVec2(-0.80901694f, -0.58778536f),ImVec2(-0.3090171f, -0.9510565f),ImVec2(0.30901712f, -0.9510565f),ImVec2(0.80901694f, -0.5877853f)}; static const ImVec2 MARKER_FILL_SQUARE[4] = {ImVec2(SQRT_1_2,SQRT_1_2), ImVec2(SQRT_1_2,-SQRT_1_2), ImVec2(-SQRT_1_2,-SQRT_1_2), ImVec2(-SQRT_1_2,SQRT_1_2)}; @@ -1694,6 +1815,16 @@ static const ImVec2 MARKER_FILL_UP[3] = {ImVec2(SQRT_3_2,0.5f),ImVec2(0,-1 static const ImVec2 MARKER_FILL_DOWN[3] = {ImVec2(SQRT_3_2,-0.5f),ImVec2(0,1),ImVec2(-SQRT_3_2,-0.5f)}; static const ImVec2 MARKER_FILL_LEFT[3] = {ImVec2(-1,0), ImVec2(0.5, SQRT_3_2), ImVec2(0.5, -SQRT_3_2)}; static const ImVec2 MARKER_FILL_RIGHT[3] = {ImVec2(1,0), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, -SQRT_3_2)}; +static const ImVec2 MARKER_FILL_ARROW[7] = { + ImVec2(0.5, 0), + ImVec2(0.0, -0.3), + ImVec2(0.0, -0.1), + ImVec2(-0.5, -0.1), + ImVec2(-0.5, 0.1), + ImVec2(0.0, 0.1), + ImVec2(0.0, 0.3) +}; + static const ImVec2 MARKER_LINE_CIRCLE[20] = { ImVec2(1.0f, 0.0f), ImVec2(0.809017f, 0.58778524f), @@ -2769,6 +2900,67 @@ void PlotHeatmap(const char* label_id, const T* values, int rows, int cols, doub CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO +//----------------------------------------------------------------------------- +// [SECTION] PlotQuiver +//----------------------------------------------------------------------------- + +template +void PlotQuiverEx(const char* label_id, const Getter& getter, const double mag_min, const double mag_max, ImPlotQuiverFlags flags){ + if (BeginItemEx(label_id, FitterVector(getter), flags,ImPlotCol_Fill)) { + if (getter.Count <= 0) { + EndItem(); + return; + } + const ImPlotNextItemData& s = GetItemData(); + + const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_Fill]); + const bool color_coded = ImHasFlag(flags, ImPlotQuiverFlags_ColorByMagnitude); + const bool normalized = ImHasFlag(flags, ImPlotQuiverFlags_FixedSize); + + double final_mag_min = mag_min; + double final_mag_max = mag_max; + + if (color_coded && mag_min == mag_max) { + final_mag_min = INFINITY; + final_mag_max = -INFINITY; + for (int i = 0; i < getter.Count; ++i) { + ImPlotPoint4D p = getter(i); + double mag = ImSqrt(p.z*p.z + p.w*p.w); + if (mag < final_mag_min) final_mag_min = mag; + if (mag > final_mag_max) final_mag_max = mag; + } + if (final_mag_min == final_mag_max) { + final_mag_max = final_mag_min + 1.0; + } + } + + if (ImHasFlag(flags, ImPlotQuiverFlags_NoClip)) { + PopPlotClipRect(); + PushPlotClipRect(s.QuiverSize); + } + RenderPrimitives1(getter, MARKER_FILL_ARROW, 7, s.QuiverSize, col_fill, final_mag_min, final_mag_max, color_coded, normalized); + + EndItem(); + } +} + +template +void PlotQuiver(const char* label_id, const T* xs, const T* ys,const T* us, const T* vs, int count, double mag_min, double mag_max, ImPlotQuiverFlags flags, int offset, int stride) { + GetterXYZW,IndexerIdx,IndexerIdx,IndexerIdx> getter( + IndexerIdx(xs,count,offset,stride), + IndexerIdx(ys,count,offset,stride), + IndexerIdx(us,count,offset,stride), + IndexerIdx(vs,count,offset,stride), + count); + return PlotQuiverEx(label_id, getter, mag_min, mag_max, flags); +} + +#define INSTANTIATE_MACRO(T) \ + template IMPLOT_API void PlotQuiver(const char* label_id, const T* xs, const T* ys, const T* us, const T* vs, int count, double mag_min, double mag_max, ImPlotQuiverFlags flags, int offset, int stride); +CALL_INSTANTIATE_FOR_NUMERIC_TYPES() +#undef INSTANTIATE_MACRO + + //----------------------------------------------------------------------------- // [SECTION] PlotHistogram //-----------------------------------------------------------------------------