diff --git a/implot.cpp b/implot.cpp index e33613b7..06ae78d1 100644 --- a/implot.cpp +++ b/implot.cpp @@ -4556,6 +4556,94 @@ ImPlotMarker NextMarker() { ++gp.CurrentItems->MarkerIdx; return idx; } + +void LoadDefaultLineStyles() { + const ImU32 Dash[] = {10, 6}; + const ImU32 Dot[] = {2, 6}; + const ImU32 DashDot[] = {10, 6, 2, 6}; + const ImU32 DashDotDot[] = {10, 6, 2, 6, 2, 6}; + + // The default line styles are added at fixed indices + // We use the internal add function with the desired index to ensure they are stored in the correct order and that user defined line styles are added after them + AddLineStyleInternal(ImPlotLineStyle_Dashed, Dash, sizeof(Dash)/sizeof(ImU32)); + AddLineStyleInternal(ImPlotLineStyle_Dotted, Dot, sizeof(Dot)/sizeof(ImU32)); + AddLineStyleInternal(ImPlotLineStyle_DashDot, DashDot, sizeof(DashDot)/sizeof(ImU32)); + AddLineStyleInternal(ImPlotLineStyle_DashDotDot, DashDotDot, sizeof(DashDotDot)/sizeof(ImU32)); +} + +int AddLineStyle(const ImU32* keys, int count) { + IM_ASSERT_USER_ERROR(count > 1 && count % 2 == 0, "The line style size must be greater than 1 and even!"); + // User defined line styles are added after the default line styles, so we start with an index of -1 to append to the end of the list + return AddLineStyleInternal(-1, keys, count); +} + +int AddLineStyleInternal(const int ID, const ImU32* keys, const int count) { + IM_ASSERT(ID < ImPlotLineStyle_COUNT || ID == -1); // ID must be a valid default line style index or -1 for user defined styles + ImPlotContext& gp = *GImPlot; + int period = 0; + for (int i = 0; i < count; ++i){ + period += keys[i]; + } + // Reserve space for default line styles if they haven't been added yet + if (gp.LineStyleData.Size < ImPlotLineStyle_COUNT) { + gp.LineStyleData.resize(ImPlotLineStyle_COUNT); + for (int i = 0; i < ImPlotLineStyle_COUNT; ++i) gp.LineStyleData[i].RectID = -1; + } + if (ID >= 0 && gp.LineStyleData[ID].RectID != -1) + return ID; // If the ID is already used + // Get font atlas and add a custom rectangle to it + ImFontAtlas *atlas = ImGui::GetIO().Fonts; + ImFontAtlasRectId rectID = atlas->AddCustomRect(510, 1); + int keyIdx = 0; + ImU32 k = 0; + ImU32 color = ~0; + if (rectID != -1) + { + // Build the atlas and get the pixel data + unsigned char* pixels; + int width, height; + atlas->GetTexDataAsRGBA32(&pixels, &width, &height); + // Retrieve the rectangle location and fill it with data + ImFontAtlasRect rect; + if (atlas->GetCustomRect(rectID, &rect)) + { + for (int i = 0; i < rect.h; i++) + { + // Calculate the pointer to the start of the row in the atlas + ImU32* row = (ImU32*)pixels + (rect.y + i) * width + rect.x; + for (int j = 0; j < rect.w; j++) + { + row[j] = color; + if (++k >= keys[keyIdx]) { + color = ~color; + keyIdx = (keyIdx + 1) % count; + k = 0; + } + } + } + // Add the line style data to the context + if (ID < 0) + gp.LineStyleData.push_back(ImPlotLineStyleData(rectID, period, 510)); + else + gp.LineStyleData[ID] = ImPlotLineStyleData(rectID, period, 510); + return gp.LineStyleData.Size - 1; + } + else + IM_ASSERT(0 && "Failed to retrieve custom rectangle from font atlas for line style!"); + } + else + IM_ASSERT(0 && "Failed to add custom rectangle to font atlas for line style!"); + return -1; +} + +const ImPlotLineStyleData& GetLineStyleData(ImPlotLineStyle style) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(style >= 0 && style < gp.LineStyleData.Size, "Invalid line style index!"); + const ImPlotLineStyleData& data = gp.LineStyleData[style]; + if (!ImGui::GetIO().Fonts->GetCustomRect(data.RectID, &data.Rect)) + IM_ASSERT(0 && "Failed to retrieve custom rectangle from font atlas for line style!"); + return data; +} //------------------------------------------------------------------------------ // [Section] Colormaps diff --git a/implot.h b/implot.h index 2c995a80..ccea2c36 100644 --- a/implot.h +++ b/implot.h @@ -115,6 +115,7 @@ typedef int ImPlotCond; // -> enum ImPlotCond_ typedef int ImPlotCol; // -> enum ImPlotCol_ typedef int ImPlotStyleVar; // -> enum ImPlotStyleVar_ typedef int ImPlotScale; // -> enum ImPlotScale_ +typedef int ImPlotLineStyle; // -> enum ImPlotLineStyle_ typedef int ImPlotMarker; // -> enum ImPlotMarker_ typedef int ImPlotColormap; // -> enum ImPlotColormap_ typedef int ImPlotLocation; // -> enum ImPlotLocation_ @@ -139,6 +140,7 @@ enum ImAxis_ { enum ImPlotProp_ { ImPlotProp_LineColor, // line color (applies to lines, bar edges); IMPLOT_AUTO_COL will use next Colormap color or current item color ImPlotProp_LineWeight, // line weight in pixels (applies to lines, bar edges, marker edges) + ImPlotProp_LineStyle, // line style (applies to lines) ImPlotProp_FillColor, // fill color (applies to shaded regions, bar faces); IMPLOT_AUTO_COL will use next Colormap color or current item color ImPlotProp_FillAlpha, // alpha multiplier (applies to FillColor and MarkerFillColor) ImPlotProp_Marker, // marker type; specify ImPlotMarker_Auto to use the next unused marker @@ -429,6 +431,16 @@ enum ImPlotScale_ { ImPlotScale_SymLog, // symmetric log scale }; +// Line style specifications. +enum ImPlotLineStyle_ { + ImPlotLineStyle_Solid = -1,// default + ImPlotLineStyle_Dashed, // 6px on, 6px off + ImPlotLineStyle_Dotted, // 2px on, 6px off + ImPlotLineStyle_DashDot, // 6px on, 2px off, 2px on, 2px off + ImPlotLineStyle_DashDotDot,// 6px on, 2px off, 2px on, 2px off, 2px on, 2px off + ImPlotLineStyle_COUNT +}; + // Marker specifications. enum ImPlotMarker_ { ImPlotMarker_None = -2, // no marker @@ -510,6 +522,7 @@ enum ImPlotBin_ { struct ImPlotSpec { ImVec4 LineColor = IMPLOT_AUTO_COL; // line color (applies to lines, bar edges); IMPLOT_AUTO_COL will use next Colormap color or current item color float LineWeight = 1.0f; // line weight in pixels (applies to lines, bar edges, marker edges) + ImPlotLineStyle LineStyle = ImPlotLineStyle_Solid; // line style (applies to lines) ImVec4 FillColor = IMPLOT_AUTO_COL; // fill color (applies to shaded regions, bar faces); IMPLOT_AUTO_COL will use next Colormap color or current item color float FillAlpha = 1.0f; // alpha multiplier (applies to FillColor and MarkerFillColor) ImPlotMarker Marker = ImPlotMarker_None; // marker type; specify ImPlotMarker_Auto to use the next unused marker @@ -544,6 +557,7 @@ struct ImPlotSpec { switch (prop) { case ImPlotProp_LineColor : LineColor = ImGui::ColorConvertU32ToFloat4((ImU32)v); return; case ImPlotProp_LineWeight : LineWeight = (float)v; return; + case ImPlotProp_LineStyle : LineStyle = (ImPlotLineStyle)v; return; case ImPlotProp_FillColor : FillColor = ImGui::ColorConvertU32ToFloat4((ImU32)v); return; case ImPlotProp_FillAlpha : FillAlpha = (float)v; return; case ImPlotProp_Marker : Marker = (ImPlotMarker)v; return; @@ -1216,6 +1230,11 @@ IMPLOT_API const char* GetMarkerName(ImPlotMarker idx); // Returns the next marker and advances the marker for the current plot. You need to call this between Begin/EndPlot! IMPLOT_API ImPlotMarker NextMarker(); +// Executes post-setup initialization +IMPLOT_API void LoadDefaultLineStyles(); + +IMPLOT_API int AddLineStyle(const ImU32* keys, int count); + //----------------------------------------------------------------------------- // [SECTION] Colormaps //----------------------------------------------------------------------------- diff --git a/implot_demo.cpp b/implot_demo.cpp index 2ff2a027..dcbdb82d 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -1063,6 +1063,60 @@ void Demo_RealtimePlots() { //----------------------------------------------------------------------------- +void Demo_LineStyles() { + IMGUI_DEMO_MARKER("Plots/Line Styles"); + static ImPlotSpec spec; + static bool is_init = false; + static ImPlotLineStyle custom_style[2]; + // Custom line styles are defined by an array of on/off lengths in pixels. + // The first element is the length of the dash, the second is the length of the gap, and so on. + static ImU32 styleA[] = {15, 6, 15, 6, 2, 6}; // dash, dash, dot + static ImU32 styleB[] = {20, 6, 10, 6}; // long dash, short dash + + // ImPlot::LoadDefaultLineStyles() must be called before using line styles! + // It should be called once after context creation and backend initialization + // Called here for demo purposes, but you should call it in your initialization code. + if (!is_init) { + // Register custom line style before loading defaults + custom_style[0] = ImPlot::AddLineStyle(styleA, sizeof(styleA)/sizeof(ImU32)); + // Load default line styles + ImPlot::LoadDefaultLineStyles(); + // Register custom line style after loading defaults (order doesn't matter) + custom_style[1] = ImPlot::AddLineStyle(styleB, sizeof(styleB)/sizeof(ImU32)); + is_init = true; + } + ImGui::DragFloat("Line Weight", &spec.LineWeight,0.05f,0.5f,32.0f,"%.2f px"); + + if (ImPlot::BeginPlot("##LineStyles", ImVec2(-1,0), ImPlotFlags_CanvasOnly)) { + + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(0, 5, 0, 9); + + ImS8 xs[2] = {1,4}; + ImS8 ys[2] = {7,8}; + + // Line Styles + for (int m = 0; m < ImPlotLineStyle_COUNT; ++m) { + ImGui::PushID(m); + spec.LineStyle = (ImPlotLineStyle)m; + ImPlot::PlotLine("##Style", xs, ys, 2, spec); + ImGui::PopID(); + ys[0]--; ys[1]--; + } + // Custom Line Styles + for (int m = 0; m < 2; ++m) { + ImGui::PushID(m); + spec.LineStyle = custom_style[m]; + ImPlot::PlotLine("##Custom", xs, ys, 2, spec); + ImGui::PopID(); + ys[0]--; ys[1]--; + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + void Demo_MarkersAndText() { IMGUI_DEMO_MARKER("Plots/Markers and Text"); static ImPlotSpec spec(ImPlotProp_Marker, ImPlotMarker_Auto); @@ -2499,6 +2553,7 @@ void ShowDemoWindow(bool* p_open) { DemoHeader("Histogram 2D", Demo_Histogram2D); DemoHeader("Digital Plots", Demo_DigitalPlots); DemoHeader("Images", Demo_Images); + DemoHeader("Line Styles", Demo_LineStyles); DemoHeader("Markers and Text", Demo_MarkersAndText); DemoHeader("NaN Values", Demo_NaNValues); ImGui::EndTabItem(); diff --git a/implot_internal.h b/implot_internal.h index da49cedb..f7b413de 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -123,6 +123,8 @@ template static inline T ImRemap01(T x, T x0, T x1) { return (x - x0) / (x1 - x0); } // Returns always positive modulo (assumes r != 0) static inline int ImPosMod(int l, int r) { return (l % r + r) % r; } +// Returns the modulo of x by y (assumes y != 0) +static inline double ImMod(double x, double y) { return fmod(x, y); } // Returns true if val is NAN static inline bool ImNan(double val) { return isnan(val); } // Returns true if val is NAN or INFINITY @@ -285,6 +287,18 @@ typedef void (*ImPlotLocator)(ImPlotTicker& ticker, const ImPlotRange& range, fl // [SECTION] Structs //----------------------------------------------------------------------------- +struct ImPlotLineStyleData { + ImPlotLineStyleData() {} + ImPlotLineStyleData(ImFontAtlasRectId rect_id, float period, float max_length) : + RectID(rect_id), + Period(period), + MaxLength(max_length) {} + ImFontAtlasRectId RectID; // texture atlas rect ID for line style + float Period; // period of line style in pixels + float MaxLength; // maximum length of line style in pixels + mutable ImFontAtlasRect Rect; // cached rectangle for line style (filled in on demand) +}; + // Combined date/time format spec struct ImPlotDateTimeSpec { ImPlotDateTimeSpec() {} @@ -1243,11 +1257,12 @@ struct ImPlotContext { ImPlotTagCollection Tags; // Style and Colormaps - ImPlotStyle Style; - ImVector ColorModifiers; - ImVector StyleModifiers; - ImPlotColormapData ColormapData; - ImVector ColormapModifiers; + ImPlotStyle Style; + ImVector ColorModifiers; + ImVector StyleModifiers; + ImPlotColormapData ColormapData; + ImVector ColormapModifiers; + ImVector LineStyleData; // Time tm Tm; @@ -1508,6 +1523,12 @@ IMPLOT_API ImU32 SampleColormapU32(float t, ImPlotColormap cmap); // Render a colormap bar IMPLOT_API void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous); +// Adds a line style with the given ID to ImPlot context. Set ID to -1 to add a line style with an automatically generated ID. +IMPLOT_API int AddLineStyleInternal(const int ID, const ImU32* keys, const int count); + +// Returns line style data for a given line style. Do not store the returned value between frames as it may be invalidated if texture atlas is rebuilt. +IMPLOT_API const ImPlotLineStyleData& GetLineStyleData(ImPlotLineStyle style); + //----------------------------------------------------------------------------- // [SECTION] Math and Misc Utils //----------------------------------------------------------------------------- diff --git a/implot_items.cpp b/implot_items.cpp index 4df2ea9e..c087bcf8 100644 --- a/implot_items.cpp +++ b/implot_items.cpp @@ -204,6 +204,40 @@ IMPLOT_INLINE void PrimLine(ImDrawList& draw_list, const ImVec2& P1, const ImVec draw_list._VtxCurrentIdx += 4; } +IMPLOT_INLINE void PrimLineUV(ImDrawList& draw_list, const ImVec2& P1, const ImVec2& P2, float half_weight, ImU32 col, const ImVec2& uv_a, const ImVec2 uv_c) { + float dx = P2.x - P1.x; + float dy = P2.y - P1.y; + ImVec2 uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y); + IMPLOT_NORMALIZE2F_OVER_ZERO(dx, dy); + dx *= half_weight; + dy *= half_weight; + draw_list._VtxWritePtr[0].pos.x = P1.x + dy; + draw_list._VtxWritePtr[0].pos.y = P1.y - dx; + draw_list._VtxWritePtr[0].uv = uv_a; + draw_list._VtxWritePtr[0].col = col; + draw_list._VtxWritePtr[1].pos.x = P2.x + dy; + draw_list._VtxWritePtr[1].pos.y = P2.y - dx; + draw_list._VtxWritePtr[1].uv = uv_b; + draw_list._VtxWritePtr[1].col = col; + draw_list._VtxWritePtr[2].pos.x = P2.x - dy; + draw_list._VtxWritePtr[2].pos.y = P2.y + dx; + draw_list._VtxWritePtr[2].uv = uv_c; + draw_list._VtxWritePtr[2].col = col; + draw_list._VtxWritePtr[3].pos.x = P1.x - dy; + draw_list._VtxWritePtr[3].pos.y = P1.y + dx; + draw_list._VtxWritePtr[3].uv = uv_d; + draw_list._VtxWritePtr[3].col = col; + draw_list._VtxWritePtr += 4; + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[3] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[4] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[5] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr += 6; + draw_list._VtxCurrentIdx += 4; +} + IMPLOT_INLINE void PrimRectFill(ImDrawList& draw_list, const ImVec2& Pmin, const ImVec2& Pmax, ImU32 col, const ImVec2& uv) { draw_list._VtxWritePtr[0].pos = Pmin; draw_list._VtxWritePtr[0].uv = uv; @@ -949,6 +983,55 @@ struct RendererLineStrip : RendererBase { mutable ImVec2 UV1; }; +template +struct RendererLineStripUV : RendererBase { + RendererLineStripUV(const _Getter& getter, ImU32 col, ImPlotLineStyleData style_data, float weight) : + RendererBase(getter.Count - 1, 6, 4), + Getter(getter), + Col(col), + StyleData(style_data), + HalfWeight(ImMax(1.0f,weight)*0.5f), + cumLen(0.f) + { + P1 = this->Transformer(Getter[0]); + } + void Init(ImDrawList& draw_list) const { + GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter[prim + 1]); + float segLen; + if (!(ImNan(P1.x) || ImNan(P1.y) || ImNan(P2.x) || ImNan(P2.y))) + segLen = ImSqrt(ImLengthSqr(P2 - P1)); + else + segLen = 0.f; + + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { + cumLen = ImMod(cumLen + segLen, StyleData.Period); + P1 = P2; + return false; + } + UV0.x = ImRemap(cumLen, 0.f, StyleData.MaxLength, StyleData.Rect.uv0.x, StyleData.Rect.uv1.x); + UV1.x = ImRemap(cumLen + segLen, 0.f, StyleData.MaxLength, StyleData.Rect.uv0.x, StyleData.Rect.uv1.x); + UV0.y = StyleData.Rect.uv0.y; + UV1.y = StyleData.Rect.uv1.y; + + PrimLineUV(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + + cumLen = ImMod(cumLen + segLen, StyleData.Period); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + const ImPlotLineStyleData StyleData; + mutable float HalfWeight; + mutable float cumLen; + mutable ImVec2 P1; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + template struct RendererLineStripSkip : RendererBase { RendererLineStripSkip(const _Getter& getter, ImU32 col, float weight) : @@ -1796,14 +1879,22 @@ void PlotLineEx(const char* label_id, const _Getter& getter, const ImPlotSpec& s else if (ImHasFlag(spec.Flags, ImPlotLineFlags_Loop)) { if (ImHasFlag(spec.Flags, ImPlotLineFlags_SkipNaN)) RenderPrimitives1(GetterLoop<_Getter>(getter),col_line,s.Spec.LineWeight); - else - RenderPrimitives1(GetterLoop<_Getter>(getter),col_line,s.Spec.LineWeight); + else { + if (s.Spec.LineStyle == ImPlotLineStyle_Solid) + RenderPrimitives1(GetterLoop<_Getter>(getter),col_line,s.Spec.LineWeight); + else + RenderPrimitives1(GetterLoop<_Getter>(getter),col_line,GetLineStyleData(s.Spec.LineStyle), s.Spec.LineWeight); + } } else { if (ImHasFlag(spec.Flags, ImPlotLineFlags_SkipNaN)) RenderPrimitives1(getter,col_line,s.Spec.LineWeight); - else - RenderPrimitives1(getter,col_line,s.Spec.LineWeight); + else { + if (s.Spec.LineStyle == ImPlotLineStyle_Solid) + RenderPrimitives1(getter,col_line,s.Spec.LineWeight); + else + RenderPrimitives1(getter,col_line,GetLineStyleData(s.Spec.LineStyle),s.Spec.LineWeight); + } } } }