From 4f35e9518618ade5b4a7e3c9c3f8d9115a5fa693 Mon Sep 17 00:00:00 2001 From: Mingbo Peng Date: Wed, 20 Mar 2024 00:04:15 -0400 Subject: [PATCH] fix(LegendParameter): support legend with None --- .../ManualAdded/JsonSetting.cs | 3 +- .../ManualAdded/Model/LegendParameters.cs | 56 ++++++++- .../ManualAdded/Model/VisualizationData.cs | 115 ++++++++++++++---- 3 files changed, 144 insertions(+), 30 deletions(-) diff --git a/src/LadybugDisplaySchema/ManualAdded/JsonSetting.cs b/src/LadybugDisplaySchema/ManualAdded/JsonSetting.cs index 4e66480..95ad882 100644 --- a/src/LadybugDisplaySchema/ManualAdded/JsonSetting.cs +++ b/src/LadybugDisplaySchema/ManualAdded/JsonSetting.cs @@ -14,9 +14,10 @@ public static JsonSerializerSettings AnyOfConvertSetting if (_setting == null) { - _setting = new JsonSerializerSettings + _setting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, + FloatFormatHandling = FloatFormatHandling.Symbol, //DefaultValueHandling = DefaultValueHandling., Converters = new List() { new AnyOfJsonConverter() } }; diff --git a/src/LadybugDisplaySchema/ManualAdded/Model/LegendParameters.cs b/src/LadybugDisplaySchema/ManualAdded/Model/LegendParameters.cs index 9a2a31f..c804098 100644 --- a/src/LadybugDisplaySchema/ManualAdded/Model/LegendParameters.cs +++ b/src/LadybugDisplaySchema/ManualAdded/Model/LegendParameters.cs @@ -53,6 +53,11 @@ public double SegmentHeight2D public double Width2D => this.Vertical ? this.SegmentWidth2D : this.SegmentWidth2D * this.SegmentCountValue; public double Height2D => this.Vertical ? this.SegmentHeight2D * this.SegmentCountValue : this.SegmentHeight2D; + public double Height2D_None => this.SegmentHeight2D / 4; + public double Height2D_Full => this.HasNone ? Height2D + this.Height2D_None + 10 : Height2D; + + public double MinMaxRange => this.MaxValue.Equals(this.MinValue)? 0 : this.MaxValue - this.MinValue; // use Equals to catch double.NaN case + public double TextHeight2D { get @@ -61,6 +66,7 @@ public double TextHeight2D return GetPxValue(this.Properties2d.TextHeight); } } + public bool HasNone => this.HasNoneColor(out _); #endregion @@ -133,6 +139,8 @@ public LegendParameters(int x = 50, int y = 100): this() public LegendParameters(double min, double max, int numSegs, List colors = default) : this() { + if (numSegs <= 0) + throw new System.ArgumentException("Segment count cannot be 0"); init2DDefault(); DecimalCount = 2; @@ -140,8 +148,7 @@ public LegendParameters(double min, double max, int numSegs, List colors Max = max; SegmentCount = numSegs; - var c = colors ?? _defaultColorSet.ToList(); - Colors = numSegs > 1 ? c : new List() { c[0], c[0] }; + Colors = _defaultColorSet.ToList(); } private Legend2DParameters init2DDefault() @@ -161,6 +168,43 @@ private Legend3DParameters init3DDefault() //public System.Drawing.Rectangle GetBoundary => new System.Drawing.Rectangle(this.X, this.Y, this.Width, this.Height); + public LegendParameters SetNoneColor(Color color) + { + return this.AddUserData("_noneColor", color); + } + public bool HasNoneColor(out Color noneColor) + { + var ud = this.GetUserData(); + noneColor = null; + if (ud.TryGetValue("_noneColor", out var color)) + { + if (color is Color lbC) + { + noneColor = lbC; + } + try + { + noneColor = Color.FromJson(color?.ToString()); + } + catch (System.Exception) + { + } + + return noneColor!= null; + } + return false; + } + internal Color GetNoneColorWithDefault() + { + if (this.HasNoneColor(out var c)) + return c; + var colorStart = this.ColorsWithDefault.First(); + return new Color( + System.Math.Max(0, colorStart.R - 50), + System.Math.Max(0, colorStart.G - 50), + System.Math.Max(0, colorStart.B - 50) + ); + } private List _colorDomains; private List ColorDomains() @@ -195,12 +239,18 @@ public Color CalColor(double value) var colors = this.ColorsWithDefault.ToList(); var colorStart = colors.First(); var colorEnd = colors.Last(); + + // check if there is none color for legend + if (double.IsNaN(value)) + return GetNoneColorWithDefault(); + + if (value <= this.MinValue) return colorStart; if (value >= this.MaxValue) return colorEnd; - var range_p = this.MaxValue - this.MinValue; + var range_p = this.MinMaxRange; var factor = range_p == 0 ? 0 : (value - this.MinValue) / range_p; var colorDomains = ColorDomains(); diff --git a/src/LadybugDisplaySchema/ManualAdded/Model/VisualizationData.cs b/src/LadybugDisplaySchema/ManualAdded/Model/VisualizationData.cs index 5c1238b..50f3a32 100644 --- a/src/LadybugDisplaySchema/ManualAdded/Model/VisualizationData.cs +++ b/src/LadybugDisplaySchema/ManualAdded/Model/VisualizationData.cs @@ -18,13 +18,12 @@ public VisualizationData(List results, LegendParameters legend = default public VisualizationData(List results, LegendParameters legend = default) : this() { this.LegendParameters = legend; - var isNumber = results.All(_ => double.TryParse(_, out var n)); + var isNumber = IsNumberList(results, out var nums, out var numBounds); // checks for None case var values = new List(results.Count); if (isNumber) { - values = results.Select(_ => double.Parse(_)).ToList(); - this.Values = values; - UpdateLegendWithValues(); + this.Values = nums; + UpdateLegendWithValues(numBounds); } else @@ -63,14 +62,6 @@ public VisualizationData(List results, LegendParameters legend = default legendPar.OrdinalDictionary = keyMapper; legendPar.ContinuousLegend = false; - // reset color if there is only one from the existing legend param - if ((legendPar.Colors?.Distinct()?.Count()).GetValueOrDefault() == 1 && steps > 1) - { - legendPar.Colors = null; - } - - - this.LegendParameters = legendPar; } @@ -84,31 +75,97 @@ public LegendParameters ValidLegendParameters return this.LegendParameters; } } - public void UpdateLegendWithValues() + + /// + /// Checks all values + /// + /// + /// + /// + public static bool IsNumberList(List results, out List values, out (double min, double max)? numBounds) + { + var noneKeys = new string[] { "None", string.Empty, null }; + var gp = results.GroupBy(_ => noneKeys.Contains(_)); + var hasNone = (gp.FirstOrDefault(_ => _.Key)?.Any()).GetValueOrDefault(); + // check if all the rest values are numbers. If an empty list found, consider it as true to the rest value is number. + var allRestNums = (gp.FirstOrDefault(_ => !_.Key)?.All(r => double.TryParse(r, out _))).GetValueOrDefault(true); + values = null; + numBounds = null; + + if (allRestNums) // all non-None values are numbers + { + var noneReplacement = double.NaN; + + // replace all none values with noneReplacement + values = results.Select(r => double.TryParse(r, out var v) ? v : noneReplacement).ToList(); + + // get bounds + // values.Min() will be NaN if there is any NaN found, Max() will return the max value and ignore the NaN + var nonNaNs = values.Where(_ => !double.IsNaN(_)); + numBounds = nonNaNs.Any() ? (nonNaNs.Min(), nonNaNs.Max()): (double.NaN, double.NaN); + return true; + } + else + { + return false; + } + + + } + + public void UpdateLegendWithValues((double min, double max)? numBounds = default) { var legend = this.LegendParameters; + var valueMin = this.Values.Min(); + var valueMax = this.Values.Max(); + var values = this.Values; + + // check None case which is set to double.NaN + var hasNone = double.IsNaN(valueMin); + var allNones = double.IsNaN(valueMax); + if (numBounds.HasValue && !allNones) + { + valueMin = numBounds.Value.min; + valueMax = numBounds.Value.max; + } + + // update the legend with the real number values except NaNs + if (hasNone && !allNones) + values = values.Where(_ => !double.IsNaN(_)).ToList(); + + var distinctCounts = values.Distinct().Count(); + var steps = distinctCounts > 11 ? 11 : distinctCounts; + if (legend == null) { - var values = this.Values; - var min = values.Min(); - var max = values.Max(); - var distinctCounts = values.Distinct().Count(); - var steps = distinctCounts > 11 ? 11 : distinctCounts; - legend = new LegendParameters(min, max, steps) { ContinuousLegend = true}; + legend = new LegendParameters(valueMin, valueMax, steps) { ContinuousLegend = distinctCounts >1}; + } + + var lMin = legend.Min?.Obj; + var lMax = legend.Max?.Obj; + if (lMin == null || lMin is Default || lMin is double.NaN) + legend.Min = valueMin; + if (lMax == null || lMax is Default || lMax is double.NaN) + legend.Max = valueMax; + + //legend.Min = legend.Min == null || legend.Min.Obj is Default || legend.Min.Obj is double.NaN ? valueMin : legend.Min; + //legend.Max = legend.Max == null || legend.Max.Obj is Default || legend.Max.Obj is double.NaN ? valueMax : legend.Max; + + // reset colors, min, max when previous saved legend is only for one value/color + if (legend.MinMaxRange == 0 && valueMax>valueMin) + { + legend.Min = valueMin; + legend.Max = valueMax; + legend.SegmentCount = steps; + legend.Colors = null; + legend.ContinuousLegend = distinctCounts > 1; } - //legend = legend.DuplicateLegendParameters(); - legend.Min = legend.Min == null || legend.Min.Obj is Default ? this.Values.Min() : legend.Min; - legend.Max = legend.Max == null || legend.Max.Obj is Default ? this.Values.Max() : legend.Max; var isNumber = !legend.HasOrdinalDictionary; if (isNumber) { if (legend.SegmentCount == null || legend.SegmentCount.Obj is Default) - { - var distinctCounts = this.Values.Distinct().Count(); - var steps = distinctCounts > 11 ? 11 : distinctCounts; legend.SegmentCount = steps; - } legend.OrdinalDictionary = null; } @@ -129,6 +186,12 @@ public void UpdateLegendWithValues() if (!string.IsNullOrEmpty(this.Unit)) legend = legend.AddUserData("_unit", this.Unit); + + if (hasNone && !allNones) + { + legend = legend.SetNoneColor(legend.GetNoneColorWithDefault()); + } + this.LegendParameters = legend; }