Added JSON Nuget Package, updated DataGenerator to accept more Inputs, for example for the Sin function. Added A Feature, to create mutliple Datasets to show in the chart. Added Trendline to show the Trendline as a Dataset

This commit is contained in:
Pablu23
2022-02-05 18:33:13 +01:00
parent bd3b14cc1b
commit d948cb7188
6 changed files with 208 additions and 71 deletions

View File

@@ -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<double>();
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<Dataset>();
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<double>(tmp.Values), labels);
var average = new Collection<double>();
var minimum = new Collection<double>();
var maximum = new Collection<double>();
var summed = new Collection<double>();
var trend = new Collection<double>();
var trendStandardDeviationUp = new Collection<double>();
var trendStandardDeviationDown = new Collection<double>();
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<double> 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();

View File

@@ -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<ValidationResult> 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

View File

@@ -6,4 +6,8 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>

View File

@@ -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<double> 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<double> data)
{
Label = label;
Data = data;
}
public Dataset(string label, ICollection<double> 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;
}
}

View File

@@ -0,0 +1,39 @@
namespace DataGeneratorMVC;
public class Trendline
{
public Trendline(IList<double> yAxisValues, IList<double> xAxisValues)
: this(yAxisValues.Select((t, i) => new Tuple<double, double>(xAxisValues[i], t)))
{ }
public Trendline(IEnumerable<Tuple<double, double>> 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;
}
}

View File

@@ -22,66 +22,97 @@
<div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
<div>
@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"})
</div>
</div>
@@ -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,