# HG changeset patch # User Alys Brooks # Date 2022-11-13 01:53:32 # Node ID 0598c83ca3647b30db1e43d44a5b9638c6af039a # Parent 53a92ab6254f88d7e01673e9d4b538e0f7d373d3 Add logarithmic scale. Imperfect but does mostly work so I think it's worth merging this work from the past few weeks. diff --git a/isometric-park-fna/Logging.cs b/isometric-park-fna/Logging.cs --- a/isometric-park-fna/Logging.cs +++ b/isometric-park-fna/Logging.cs @@ -52,12 +52,12 @@ private static Dictionary mappings = new Dictionary { {LogLevel.Critical, (ConsoleColor.White, ConsoleColor.Red)}, - {LogLevel.Error, (ConsoleColor.Red, ConsoleColor.Black)}, - {LogLevel.Warning, (ConsoleColor.Yellow, ConsoleColor.Black)}, - {LogLevel.Success, (ConsoleColor.Green, ConsoleColor.Black)}, - {LogLevel.Info, (ConsoleColor.Blue, ConsoleColor.Black)}, - {LogLevel.Debug, (ConsoleColor.White, ConsoleColor.Cyan)}, - {LogLevel.Trace, (ConsoleColor.White, ConsoleColor.Yellow)}, + {LogLevel.Error, (ConsoleColor.Black, ConsoleColor.Red)}, + {LogLevel.Warning, (ConsoleColor.Black, ConsoleColor.Yellow)}, + {LogLevel.Success, (ConsoleColor.White, ConsoleColor.Green)}, + {LogLevel.Info, (ConsoleColor.White, ConsoleColor.Blue)}, + {LogLevel.Debug, (ConsoleColor.Blue, ConsoleColor.Black)}, + {LogLevel.Trace, (ConsoleColor.Magenta, ConsoleColor.Black)}, {LogLevel.Spy, (ConsoleColor.Black, ConsoleColor.White)}, }; diff --git a/isometric-park-fna/UI/Graph.cs b/isometric-park-fna/UI/Graph.cs --- a/isometric-park-fna/UI/Graph.cs +++ b/isometric-park-fna/UI/Graph.cs @@ -27,6 +27,7 @@ public static bool show_upkeep = false; public static bool always_show_zero = false; + public static bool logarithmic = false; private static string[] money_series = {"Total Funds", "Subsidies", "Upkeep", "Contracts", "Cashflow", "Misc"}; @@ -103,6 +104,38 @@ } } + public static void DrawLogrithmicLabels(ImFontPtr font, ImDrawListPtr draw_list, Num.Vector2 domain, Num.Vector2 range, bool vertical, int labels, Num.Vector2 starting_position) { + //We need to increment by one in order to cover the entire range. + //For example, if our range is 0 - 100, and we want 11 ticks, 100 / 10 gives us a spacing o 10 + //So our 11 labels become 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100. + var tick_spacing = (int)Math.Abs((range.Y - range.X) / (labels - 1)); + var tick_length = 5; + var tick_adjust = vertical ? new Num.Vector2(tick_length, 0) : new Num.Vector2(0, tick_length); + + var tick_position = new Num.Vector2(0, range.Y); + var tick_absolute_position = Num.Vector2.Add(starting_position, tick_position); + + for(int i = 0; i < labels; i++) { + var value = ScaleLogarithmic(range, domain, (int)(vertical ? tick_position.Y : tick_position.X)); + var label = String.Format("{0}", value); + var height_adjust = ImGui.CalcTextSize(label).Y / 2; + var adjusted_position = Num.Vector2.Add(tick_absolute_position, + new Num.Vector2(0, -height_adjust)); + + draw_list.AddText(font, 11.0f, adjusted_position, (uint)ImGuiColor.BLACK, label); + //Logging.Info(String.Format("Drawing {0:} at {1} ({2})", label, tick_absolute_position, tick_position)); + + if (vertical) { + tick_position = new Num.Vector2(0, range.Y + ((i + 1) * tick_spacing)); + } + else { + tick_position = new Num.Vector2(range.X + ((i + 1) * tick_spacing), 0); + } + + tick_absolute_position = Num.Vector2.Add(starting_position, tick_position); + } + } + public static void DrawLine(ImDrawListPtr draw_list, Num.Vector2 c, Num.Vector2[] points, ImGuiColor col) { var p = Num.Vector2.Zero; @@ -125,6 +158,29 @@ return (int) (((num - domain.X) / domain_span) * range_span + range.X); } + public static float Scale(Num.Vector2 domain, Num.Vector2 range, float num) { + //https://stats.stackexchange.com/a/281164 + + if (!float.IsNaN(domain.Y - domain.X)) { + var domain_span = Math.Sign(domain.Y - domain.X) * Math.Max(1, Math.Abs(domain.Y - domain.X)); + var range_span = range.Y - range.X; + + var start = range.X - domain.X; + + return (((num - domain.X) / domain_span) * range_span + range.X); + } + else { + return float.NaN; + } + } + + public static float ScaleLogarithmic(Num.Vector2 domain, Num.Vector2 range, float num) { + var scaled_domain = new Num.Vector2((float)MathUtils.SymmetricLog10(domain.X), (float)MathUtils.SymmetricLog10(domain.Y)); + float scaled_num = num == 0 ? 0.0f : (float)MathUtils.SymmetricLog10(num); + + return Scale(scaled_domain, range, scaled_num); + } + public static Num.Vector2 adjust_domain(int domain_min, int domain_max) { if (domain_max == int.MinValue) { @@ -139,6 +195,8 @@ var domain_max_rounded = Math.Ceiling(domain_max / 50.0) * 50; var domain_min_rounded = Math.Floor(domain_min / 50.0) * 50; + Logging.Info("Standard rounding."); + //Throw out rounding if result is too close together: if (domain_min_rounded != domain_max_rounded) { return new Num.Vector2((float)domain_min_rounded, (float)domain_max_rounded); @@ -147,11 +205,14 @@ return new Num.Vector2(domain_min, domain_max); } + } else { var lower = MathUtils.Clamp(domain_min, 0, 100); var upper = MathUtils.Clamp(domain_min, 10, 100); + Logging.Info("Clamped rounding."); + return new Num.Vector2(domain_min-lower, domain_max+upper); } } @@ -243,8 +304,8 @@ var range = new Num.Vector2(200 - padding, 0 + padding); //Zero - var zero_point = Scale(domain, range, 0); - Logging.Spy(new {zero_point = zero_point}); + var zero_point = logarithmic ? ScaleLogarithmic(domain, range, 0) : Scale(domain, range, 0); + // Logging.Spy(new {zero_point = zero_point}); draw_list.AddLine(Num.Vector2.Add(new Num.Vector2(padding, zero_point), c), Num.Vector2.Add(new Num.Vector2(350, zero_point), c), (uint)ImGuiColor.LIGHTGREY, 1.0f); @@ -261,7 +322,7 @@ if (data.Count() > 0 && show) { - IEnumerable data_array = data_sets[key].Select((p) => Scale(domain, range, (int)p)); + IEnumerable data_array = data_sets[key].Select((p) => (logarithmic ? ScaleLogarithmic(domain, range, (float)p): Scale(domain, range, (float)p))); var data_array2 = data_array.Select((p, i) => new Num.Vector2(Scale(x_domain, x_range, i), p)).ToArray(); DrawLine(draw_list, c, data_array2, color); @@ -270,7 +331,12 @@ } DrawLinearAxis(draw_list, range /*new Num.Vector2(0, 200)*/, true, 11, Num.Vector2.Add(c, new Num.Vector2(padding, padding))); - DrawLinearLabels(font, draw_list, domain, range /*new Num.Vector2(0, 200)*/, true, 11, c); + if (logarithmic) { + DrawLogrithmicLabels(font, draw_list, domain, range, true, 11, c); + } + else { + DrawLinearLabels(font, draw_list, domain, range, true, 11, c); + } DrawLinearAxis(draw_list, new Num.Vector2(0, 350), false, 12, Num.Vector2.Add(c, new Num.Vector2(padding, 200 - padding))); ImGui.Dummy(new Num.Vector2(350, 200)); @@ -314,6 +380,12 @@ ImGui.Text("Always show zero:"); ImGui.SameLine(); ImGui.Checkbox("##AlwaysShowZero", ref always_show_zero); + #if DEBUG + ImGui.SameLine(); + ImGui.Text("Logarithmic: "); + ImGui.SameLine(); + ImGui.Checkbox("##Logarithmic", ref logarithmic); + #endif #if DEBUG diff --git a/isometric-park-fna/Utils/MathUtils.cs b/isometric-park-fna/Utils/MathUtils.cs --- a/isometric-park-fna/Utils/MathUtils.cs +++ b/isometric-park-fna/Utils/MathUtils.cs @@ -185,5 +185,13 @@ throw new EmptyArrayException("Array must not be empty"); } } + + public static float SymmetricLog10(float num) { + return num > 0.0f ? (float)Math.Log10(num) : -(float)Math.Log10(-num); + } + + public static double SymmetricLog10(double num) { + return num > 0.0f ? Math.Log10(num) : -Math.Log10(-num); + } } }