diff --git a/DataGeneratorMVC/Controllers/HomeController.cs b/DataGeneratorMVC/Controllers/HomeController.cs index 1080bc4..e383337 100644 --- a/DataGeneratorMVC/Controllers/HomeController.cs +++ b/DataGeneratorMVC/Controllers/HomeController.cs @@ -1,8 +1,15 @@ -using System.Diagnostics; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Net; using System.Text; +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Mvc; using DataGeneratorMVC.Models; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.VisualBasic; +using Newtonsoft.Json; +using JsonConverter = System.Text.Json.Serialization.JsonConverter; namespace DataGeneratorMVC.Controllers; @@ -47,61 +54,72 @@ public class HomeController : Controller private void GenerateNew(GeneratorDataViewModel model) { var tmp = DataGenerator.Generate(model.Settings); - var labelsSb = new StringBuilder(); - labelsSb.Append("["); - var dataSb = new StringBuilder(); - dataSb.Append("["); - // var allDataSets = new StringBuilder(); - // - // string datasetTemplate = "{{label: '{0}', data: {1}, backgroundColor: [\'rgba({2}, {3}, {4}, 0.2)\'], borderColor: [\'rgba(255, 99, 132, 1)\'], fill: false, tension: 0.4, spangaps: true}}{5} "; - // - // double[,] years = new double[model.Settings.Years, 366]; - // - // foreach (var turnover in tmp) - // { - // years[turnover.Key.Year - model.Settings.StartYear, turnover.Key.DayOfYear - 1] = turnover.Value; - // } - // - // for (int i = 0; i < model.Settings.Years; i++) - // { - // var dataSb = new StringBuilder(); - // dataSb.Append("["); - // for (int j = 0; j < 366; j++) - // { - // if(years[i, j] != 0) - // dataSb.Append($"{years[i, j].ToString("0.00").Replace(',', '.')}, "); - // else - // dataSb.Append($"{years[i, j-1].ToString("0.00").Replace(',', '.')}, "); - // } - // dataSb.Append("]"); - // allDataSets.Append(string.Format(datasetTemplate, model.Settings.StartYear + i, - // dataSb.ToString(), 255, 99 + i, 132 + i, i-1 == model.Settings.Years ? "" : ",")); - // dataSb.Clear(); - // } - // - // for (int i = 0; i < 366; i++) - // { - // labelsSb.Append($"{i}, "); - // } - - int counter = 0; + var labels = new List(); - foreach (var value in tmp) + for (int i = 0; i < tmp.Count; i++) { - counter++; - labelsSb.Append($"{counter}, "); - //labels.Append($"{value.Key}, "); - dataSb.Append($"{value.Value.ToString("0.00").Replace(',', '.')}, "); + labels.Add(i); } - labelsSb.Append("]"); - dataSb.Append("]"); + var datasets = new List(); + datasets.Add(new Dataset("Umsatz", tmp.Values, "rgba(255, 99, 132, 0.2)", "rgba(255, 99, 132, 1)", false, 0.4f, true)); + + var trendline = new Trendline( new List(tmp.Values), labels); + + var average = new Collection(); + var minimum = new Collection(); + var maximum = new Collection(); + var summed = new Collection(); + var trend = new Collection(); + var trendStandardDeviationUp = new Collection(); + var trendStandardDeviationDown = new Collection(); + + + double stdDeviation = StandardDeviation(tmp.Values); + double avg = tmp.Values.Average(); + double min = tmp.Values.Min(); + double max = tmp.Values.Max(); + + for (int i = 0; i < labels.Count; i++) + { + double trendValue = trendline.GetYValue(i); + + average.Add(avg); + minimum.Add(min); + maximum.Add(max); + + trendStandardDeviationUp.Add(trendValue + stdDeviation); + trendStandardDeviationDown.Add(trendValue - stdDeviation); + trend.Add(trendValue); + } + + datasets.Add(new Dataset("Trendlinie", trend){BorderColor = "rgba(11,127,171)"}); + datasets.Add(new Dataset("Standard Deviation Up", trendStandardDeviationUp){BorderColor = "rgba(0,181,204)"}); + datasets.Add(new Dataset("Standard Deviation Down", trendStandardDeviationDown){BorderColor = "rgba(0,181,204)"}); + datasets.Add(new Dataset("Average", average){BorderColor = "rgba(8,14,44)"}); + datasets.Add(new Dataset("Min", minimum){BorderColor = "rgba(8,14,44)"}); + datasets.Add(new Dataset("Max", maximum){BorderColor = "rgba(8,14,44)"}); + + model.Sql = DataGenerator.Save(tmp); - model.DataSet = dataSb.ToString(); - model.LabelSet = labelsSb.ToString(); + model.DataSet = JsonConvert.SerializeObject(datasets, new JsonSerializerSettings(){NullValueHandling = NullValueHandling.Ignore}); + model.LabelSet = JsonConvert.SerializeObject(labels); } + private static double StandardDeviation(IEnumerable sequence) + { + double result = 0; + + if (sequence.Any()) + { + double average = sequence.Average(); + double sum = sequence.Sum(d => Math.Pow(d - average, 2)); + result = Math.Sqrt((sum) / (sequence.Count() - 1)); + } + return result; + } + public IActionResult Privacy() { return View(); diff --git a/DataGeneratorMVC/DataGenerator.cs b/DataGeneratorMVC/DataGenerator.cs index a37a79b..99bfe84 100644 --- a/DataGeneratorMVC/DataGenerator.cs +++ b/DataGeneratorMVC/DataGenerator.cs @@ -5,8 +5,8 @@ using Microsoft.AspNetCore.Mvc; namespace DataGeneratorMVC; -[Bind("StartYear,Years,MinDiff,MaxDiff,HasSin,Smoothing,RSmoothing,MinSmoothing,MaxSmoothing,StartTurnover,Linear,HasPeak,PeakLength,PeakStrength,RPeakInYear,PeakInYear")] -public class GeneratorSettings +//[Bind("StartYear,Years,MinDiff,MaxDiff,HasSin,Smoothing,RSmoothing,MinSmoothing,MaxSmoothing,StartTurnover,Linear,HasPeak,PeakLength,PeakStrength,RPeakInYear,PeakInYear")] +public class GeneratorSettings : IValidatableObject { [Range(1500,3000)] public int StartYear { get; set; } @@ -25,12 +25,14 @@ public class GeneratorSettings public int MaxSmoothing { get; set; } [DataType(DataType.Currency)] public double StartTurnover { get; set; } - + public double Linear { get; set; } public bool HasSin { get; set; } - public int Linear { get; set; } + public double SinStrength { get; set; } + public double SinLength { get; set; } + public bool SinNegative { get; set; } public bool HasPeak { get; set; } - public int PeakLength { get; set; } - public int PeakStrength { get; set; } + public double PeakLength { get; set; } + public double PeakStrength { get; set; } public bool RPeakInYear { get; set; } public int PeakInYear { get; set; } @@ -51,12 +53,24 @@ public class GeneratorSettings MinSmoothing = Clamp(minSmoothing, 1, 100); MaxSmoothing = Clamp(maxSmoothing, 1, 100); StartTurnover = startTurnover; + SinLength = 182.5; + SinStrength = 10; + SinNegative = true; } private static int Clamp(int value, int min, int max) { return (value < min) ? min : (value > max) ? max : value; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (HasPeak && (PeakLength < 1 || PeakStrength < 1 || RPeakInYear == false && 1 > PeakInYear && PeakInYear < 360)) + yield return new ValidationResult("If Peak is enabled, all Peak settings must be set", new []{nameof(HasPeak), nameof(PeakLength), nameof(PeakStrength), nameof(RPeakInYear), nameof(PeakInYear)}); + + if (HasSin && (SinLength < 1 || SinStrength < 1)) + yield return new ValidationResult("If Sin is enabled, all Sin settings must be set", new []{nameof(HasSin), nameof(SinLength), nameof(SinStrength)}); + } } public static class DataGenerator @@ -74,11 +88,11 @@ public static class DataGenerator int smoothing = settings.Smoothing; int counter = 0; - int linear = settings.Linear; + double linear = settings.Linear; int peakInYear = settings.RPeakInYear ? _r.Next(10, 350) : settings.PeakInYear; - int peakLength = settings.PeakLength; - int peakStrength = settings.PeakStrength; + double peakLength = settings.PeakLength; + double peakStrength = settings.PeakStrength; int peakCounter = 0; for (int year = 0; year < settings.Years; year++) @@ -119,7 +133,12 @@ public static class DataGenerator currentCurve += _r.Next(minDiff, maxDiff + 1); if(settings.HasSin) - currentCurve += -(10 * Math.Sin(counter * Math.PI / 182.5)); + currentCurve += settings.SinNegative switch + { + true => -(settings.SinStrength * Math.Sin(counter * Math.PI / settings.SinLength)), + false => (settings.SinStrength * Math.Sin(counter * Math.PI / settings.SinLength)) //182.5 + }; + currentCurve += linear; // If it is the first Day of the Simulation start diff --git a/DataGeneratorMVC/DataGeneratorMVC.csproj b/DataGeneratorMVC/DataGeneratorMVC.csproj index d52487b..697b2c8 100644 --- a/DataGeneratorMVC/DataGeneratorMVC.csproj +++ b/DataGeneratorMVC/DataGeneratorMVC.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/DataGeneratorMVC/Models/Dataset.cs b/DataGeneratorMVC/Models/Dataset.cs new file mode 100644 index 0000000..407a2d6 --- /dev/null +++ b/DataGeneratorMVC/Models/Dataset.cs @@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace DataGeneratorMVC.Models; + +public class Dataset +{ + [JsonProperty("label")] + public string Label { get; set; } + [JsonProperty("data")] + public ICollection Data { get; set; } + [JsonProperty("backgroundColor")] + public string? BackgroundColor { get; set; } + [JsonProperty("borderColor")] + public string? BorderColor { get; set; } + [JsonProperty("fill")] + public bool? Fill { get; set; } + [JsonProperty("tension")] + public float? Tension { get; set; } + [JsonProperty("spangaps")] + public bool? Spangaps { get; set; } + + public Dataset(string label, ICollection data) + { + Label = label; + Data = data; + } + public Dataset(string label, ICollection data, string backgroundColor, string borderColor, bool fill, float tension, bool spangaps) + { + Label = label; + Data = data; + BackgroundColor = backgroundColor; + BorderColor = borderColor; + Fill = fill; + Tension = tension; + Spangaps = spangaps; + } +} \ No newline at end of file diff --git a/DataGeneratorMVC/Trendline.cs b/DataGeneratorMVC/Trendline.cs new file mode 100644 index 0000000..4fe354f --- /dev/null +++ b/DataGeneratorMVC/Trendline.cs @@ -0,0 +1,39 @@ +namespace DataGeneratorMVC; + +public class Trendline +{ + public Trendline(IList yAxisValues, IList xAxisValues) + : this(yAxisValues.Select((t, i) => new Tuple(xAxisValues[i], t))) + { } + public Trendline(IEnumerable> data) + { + var cachedData = data.ToList(); + + int n = cachedData.Count; + double sumX = cachedData.Sum(x => x.Item1); + double sumX2 = cachedData.Sum(x => x.Item1 * x.Item1); + double sumY = cachedData.Sum(x => x.Item2); + double sumXY = cachedData.Sum(x => x.Item1 * x.Item2); + + //b = (sum(x*y) - sum(x)sum(y)/n) + // / (sum(x^2) - sum(x)^2/n) + Slope = (sumXY - ((sumX * sumY) / n)) + / (sumX2 - (sumX * sumX / n)); + + //a = sum(y)/n - b(sum(x)/n) + Intercept = (sumY / n) - (Slope * (sumX / n)); + + Start = GetYValue(cachedData.Min(a => a.Item1)); + End = GetYValue(cachedData.Max(a => a.Item1)); + } + + public double Slope { get; private set; } + public double Intercept { get; private set; } + public double Start { get; private set; } + public double End { get; private set; } + + public double GetYValue(double xValue) + { + return Intercept + Slope * xValue; + } +} \ No newline at end of file diff --git a/DataGeneratorMVC/Views/Home/Index.cshtml b/DataGeneratorMVC/Views/Home/Index.cshtml index 6a7d1eb..0af7809 100644 --- a/DataGeneratorMVC/Views/Home/Index.cshtml +++ b/DataGeneratorMVC/Views/Home/Index.cshtml @@ -22,66 +22,97 @@
@Html.LabelFor(model => model.Settings.StartYear, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.StartYear) + @Html.ValidationMessageFor(model => model.Settings.StartYear, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.Years, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.Years) + @Html.ValidationMessageFor(model => model.Settings.Years, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.StartTurnover, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.StartTurnover) + @Html.ValidationMessageFor(model => model.Settings.StartTurnover, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.MaxDiff, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.MaxDiff) + @Html.ValidationMessageFor(model => model.Settings.MaxDiff, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.MinDiff, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.MinDiff) + @Html.ValidationMessageFor(model => model.Settings.MinDiff, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.RSmoothing, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.RSmoothing) + @Html.ValidationMessageFor(model => model.Settings.RSmoothing, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.MaxSmoothing, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.MaxSmoothing) + @Html.ValidationMessageFor(model => model.Settings.MaxSmoothing, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.MinSmoothing, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.MinSmoothing) + @Html.ValidationMessageFor(model => model.Settings.MinSmoothing, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.Smoothing, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.Smoothing) + @Html.ValidationMessageFor(model => model.Settings.Smoothing, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.Linear, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.Linear) + @Html.ValidationMessageFor(model => model.Settings.Linear, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.HasSin, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.HasSin) + @Html.ValidationMessageFor(model => model.Settings.HasSin, "",new {@class = "text-danger"}) +
+ +
+ @Html.LabelFor(model => model.Settings.SinLength, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.SinLength) + @Html.ValidationMessageFor(model => model.Settings.SinLength, "",new {@class = "text-danger"}) +
+ +
+ @Html.LabelFor(model => model.Settings.SinStrength, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.SinStrength) + @Html.ValidationMessageFor(model => model.Settings.SinStrength, "",new {@class = "text-danger"}) +
+ +
+ @Html.LabelFor(model => model.Settings.SinNegative, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.SinNegative) + @Html.ValidationMessageFor(model => model.Settings.SinNegative, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.HasPeak, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.HasPeak) + @Html.ValidationMessageFor(model => model.Settings.HasPeak, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.PeakLength, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.PeakLength) + @Html.ValidationMessageFor(model => model.Settings.PeakLength, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.PeakStrength, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.PeakStrength) + @Html.ValidationMessageFor(model => model.Settings.PeakStrength, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.RPeakInYear, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.RPeakInYear) + @Html.ValidationMessageFor(model => model.Settings.RPeakInYear, "",new {@class = "text-danger"})
@Html.LabelFor(model => model.Settings.PeakInYear, new {@class = "control-label col-md-1"}) @Html.EditorFor(model => model.Settings.PeakInYear) + @Html.ValidationMessageFor(model => model.Settings.PeakInYear, "",new {@class = "text-danger"})
@@ -103,19 +134,7 @@ const myChart = new Chart(ctx, { type: 'line', data: { labels: @Model.LabelSet, - datasets: [{ - label: 'Umsatz', - data: @Model.DataSet, - backgroundColor: [ - 'rgba(255, 99, 132, 0.2)' - ], - borderColor: [ - 'rgba(255, 99, 132, 1)' - ], - fill: false, - tension: 0.4, - spangaps: true - }] + datasets: @Html.Raw(@Model.DataSet) }, options: { responsive: true,