Closed mikara89 closed 4 years ago
Could you share your code that is inserting the new time values into the list so I can recreate your problem locally?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ChartJs.Blazor.ChartJS.Common.Properties;
using ChartJs.Blazor.ChartJS.Common.Enums;
using ChartJs.Blazor.ChartJS.Common.Axes;
using ChartJs.Blazor.ChartJS.Common.Axes.Ticks;
using ChartJs.Blazor.ChartJS.Common.Handlers;
using ChartJs.Blazor.ChartJS.Common.Time;
using ChartJs.Blazor.ChartJS.LineChart;
using ChartJs.Blazor.Util;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using TempSensServer.Data;
using Microsoft.EntityFrameworkCore;
using TempSensServer.Data.Models;
using Microsoft.Extensions.Options;
using TempSensServer.WebUI.Data;
using ChartJs.Blazor.Charts;
using ChartJs.Blazor.ChartJS;
namespace TempSensServer.WebUI.Shared
{
partial class TimeChartComponent:IDisposable
{
[Inject]
public IJSRuntime JsRuntime { get; set; }
[Inject]
public INotifierServices NotifierServices { get; set; }
[Inject]
private IOptions<Settings> options { get; set; }
[Inject]
public ApplicationDbContext Context { get; set; }
LineConfig _lineConfig;
public ChartJsLineChart _lineChartJs;
List<LineDataset<TimeTuple<double>>> _tempDataSets;
protected override async Task OnInitializedAsync()
{
_lineConfig = new LineConfig
{
Options = new LineOptions
{
Responsive = true,
Title = new OptionsTitle
{
Display = true,
Text = "Line Chart"
},
Legend = new Legend
{
Position = Position.Right,
Labels = new LegendLabelConfiguration
{
UsePointStyle = true
}
},
Tooltips = new Tooltips
{
Mode = InteractionMode.Nearest,
Intersect = false
},
Scales = new Scales
{
xAxes = new List<CartesianAxis>
{
new TimeAxis
{
Distribution = TimeDistribution.Linear,
Ticks = new TimeTicks
{
Source = TickSource.Data
},
Time = new TimeOptions
{
Unit = TimeMeasurement.Millisecond,
Round = TimeMeasurement.Millisecond,
TooltipFormat = "DD.MM.YYYY HH:mm:ss:SSS",
DisplayFormats = TimeDisplayFormats.DE_CH
},
ScaleLabel = new ScaleLabel
{
LabelString = "Time"
}
}
}
},
Hover = new LineOptionsHover
{
Intersect = true,
Mode = InteractionMode.Y
}
}
};
var records = await Context.Records
.Include(c => c.ConstructionSite)
.Include(v => v.Values)
.Where(x => x.ConstructionSite.Name == options.Value.ConstructionSite)
.OrderByDescending(r => r.CreatedAt)
.ToListAsync();
_tempDataSets = GetDataSets(records);
_tempDataSets.ForEach(tds =>
{
_lineConfig.Data.Datasets.Add(tds);
});
NotifierServices.RecordResived += OnRecordResived;
}
private void OnRecordResived(string recordId)
{
var record = Context.Records.Include(v => v.Values).FirstOrDefault(x => x.Id == recordId);
InvokeAsync(async () =>
{
if (record != null)
{
await AddRecord(record);
}
StateHasChanged();
});
}
private LineDataset<TimeTuple<double>> CreateDataSet(string name,IEnumerable<TimeTuple<double>> data)
{
var _tempDataSet = new LineDataset<TimeTuple<double>>
{
BackgroundColor = ColorUtil.RandomColorString(),
BorderColor = ColorUtil.RandomColorString(),
Label = $"{name} [C°]",
Fill = false,
BorderWidth = 2,
PointRadius = 3,
PointBorderWidth = 1,
SteppedLine = SteppedLine.False
};
_tempDataSet.AddRange(data);
return _tempDataSet;
}
private List<LineDataset<TimeTuple<double>>> GetDataSets(List<Record> records)
{
var lineDatasets = new List<LineDataset<TimeTuple<double>>>();
records.FirstOrDefault().Values.ToList().ForEach(v =>
{
var d = records.Select(r => new TimeTuple<double>(new Moment(r.CreatedAt),r.Values.First(x => x.Name == v.Name).Value));
var lineDataset = CreateDataSet(v.Name, d);
lineDatasets.Add(lineDataset);
});
return lineDatasets;
}
private async Task AddRecord(Record record)
{
if (record is null)
{
throw new ArgumentNullException(nameof(record));
}
record.Values.ToList().ForEach(v =>
{
_tempDataSets.ForEach(ds =>
{
if (ds.Label.StartsWith(v.Name))
{
ds.Add(new TimeTuple<double>(new Moment(record.CreatedAt), v.Value));
}
});
});
await _lineChartJs.Update();
}
public void Dispose()
{
NotifierServices.RecordResived -= OnRecordResived;
}
}
}
I uploaded c# file. OnInitializedAsync I am loading data(class Record) from DB and add that data in chart dataset, then I assigned method to event that receive data from some other source and when receive data add it to dataset and updates chart.
With the code given, I can't reproduce the issue (too many things are missing).
From what I can see in the code you show, there seem to be many general issues. I can give you some general recommendations that should help clean up your code. With cleaner code, you might find the mistake youself. If not, you can post your cleaned code along with the missing pieces (e.g. the Record
) class so we can try to help you.
.ForEach
. Sometimes it's fitting but your code features a lot of lambdas, ToList
and .ForEach
parts that only make things confusing. This blog post is a good read if you want more infos.FirstOrDefault().Whatever()
doesn't make sense. If there's no first element, it will return null and you'll get a null ref exception. If you used First().Whatever()
, the error would at least contain useful infos (e.g. there's no first element).InvokeAsync(async () =>
{
if (record != null)
{
await AddRecord(record);
}
StateHasChanged();
});
You invoke StateHasChanged
every time but the state might not have changed.
Instead, only invoke something if the state needs to be changed.
if (record != null)
{
await InvokeAsync(async () =>
{
await AddRecord(record);
StateHasChanged();
});
}
InvokeAsync
is async, await
it. OnRecordResived
can be marked as async void
since it's used as event handler. More to that here.Once the code is cleaned and the .First()
s, .Values
', .ForEach
s and lambdas aren't as confusing, I will gladly take a look at it again. Please do not take offense in this answer, I have no intent of attacking or hurting you.
Thanks on heads-up for my messy code... sorry if I make confused. As I was trying to make chart work I made huge mess that is very true... when I clean that mess I will post it again. PS of course that your answer is not offensive, and thanks again of time and effort.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ChartJs.Blazor.ChartJS.Common.Properties;
using ChartJs.Blazor.ChartJS.Common.Enums;
using ChartJs.Blazor.ChartJS.Common.Axes;
using ChartJs.Blazor.ChartJS.Common.Axes.Ticks;
using ChartJs.Blazor.ChartJS.Common.Handlers;
using ChartJs.Blazor.ChartJS.Common.Time;
using ChartJs.Blazor.ChartJS.LineChart;
using ChartJs.Blazor.Util;
using ChartJs.Blazor.Charts;
namespace TempSensServer.WebUI.Shared
{
partial class TimeChartComponent:IDisposable
{
private bool _isDisposing;
public event Action<DataValue> RecordResived;
LineConfig _lineConfig;
public ChartJsLineChart _lineChartJs;
LineDataset<TimeTuple<double>> _tempDataSet;
protected override void OnInitialized()
{
///Mock loading data from DB
List<DataValue> data = GetDataFromDbMock();
ConfiguringChart();
_tempDataSet = GetDataSet(data);
_lineConfig.Data.Datasets.Add(_tempDataSet);
RecordResived += async (newDataValue) => await OnRecordResived(newDataValue);
///this method mocks reciving data from other part of application
NewDataRecivingMock();
}
private async Task OnRecordResived(DataValue newDataValue)
{
await InvokeAsync(() =>
{
if (newDataValue != null)
{
_tempDataSet.Add(new TimeTuple<double>(new Moment(newDataValue.Date), newDataValue.Value));
_lineChartJs.Update();
StateHasChanged();
}
});
}
private void ConfiguringChart()
{
_lineConfig = new LineConfig
{
Options = new LineOptions
{
Responsive = true,
Title = new OptionsTitle
{
Display = true,
Text = "Line Chart"
},
Legend = new Legend
{
Position = Position.Right,
Labels = new LegendLabelConfiguration
{
UsePointStyle = true
}
},
Tooltips = new Tooltips
{
Mode = InteractionMode.Nearest,
Intersect = false
},
Scales = new Scales
{
xAxes = new List<CartesianAxis>
{
new TimeAxis
{
Distribution = TimeDistribution.Linear,
Ticks = new TimeTicks
{
Source = TickSource.Data
},
Time = new TimeOptions
{
Unit = TimeMeasurement.Millisecond,
Round = TimeMeasurement.Millisecond,
TooltipFormat = "DD.MM.YYYY HH:mm:ss:SSS",
DisplayFormats = TimeDisplayFormats.DE_CH,
},
ScaleLabel = new ScaleLabel
{
LabelString = "Time"
}
}
}
},
Hover = new LineOptionsHover
{
Intersect = true,
Mode = InteractionMode.Y
}
}
};
}
private List<DataValue> GetDataFromDbMock()
{
var rand = new Random();
var result = new List<DataValue>();
var date = DateTime.Now;
for (int i = 0; i < 10; i++)
{
var d = new DataValue();
d.Value = rand.NextDouble() * 20 + 50;
d.Date = date - (TimeSpan.FromSeconds(10 * (i + 1)));
result.Add(d);
}
return result;
}
private LineDataset<TimeTuple<double>> GetDataSet(List<DataValue> data)
{
var d = data.Select(r => new TimeTuple<double>(new Moment(r.Date),r.Value));
var tempDataSet = new LineDataset<TimeTuple<double>>
{
BackgroundColor = ColorUtil.RandomColorString(),
BorderColor = ColorUtil.RandomColorString(),
Label = $"test",
Fill = false,
BorderWidth = 2,
PointRadius = 3,
PointBorderWidth = 1
};
tempDataSet.AddRange(d);
return tempDataSet;
}
private async void NewDataRecivingMock()
{
var rand = new Random();
while (!_isDisposing)
{
await Task.Delay(3000);
RecordResived?.Invoke(
new DataValue {
Date = DateTime.Now,
Value = rand.NextDouble() * 20 + 50
});
}
}
public void Dispose()
{
_isDisposing = true;
RecordResived -= async (newDataValue) => await OnRecordResived(newDataValue);
}
}
/// <summary>
/// Data model
/// </summary>
public class DataValue
{
public double Value { get; set; }
public DateTime Date { get; set; }
}
}
@Joelius300 Sorry for post formating, can't make it work :)
and in razor
<ChartJsLineChart @ref="_lineChartJs" Config="@_lineConfig" Width="600" Height="300" />
I figured out," result.Reverse(); " I was sorting my data upsidedown...
private List<DataValue> GetDataFromDbMock()
{
var rand = new Random();
var result = new List<DataValue>();
var date = DateTime.Now;
for (int i = 0; i < 10; i++)
{
var d = new DataValue();
d.Value = rand.NextDouble() * 20 + 50;
d.Date = date - (TimeSpan.FromSeconds(10 * (i + 1)));
result.Add(d);
}
result.Reverse(); **///+++++++++++++++THIS IS WHAT I AM MISSING, NOW CHAT IS GREAT+++++++++++**
return result;
}
and in previous code I was using ".OrderByDescending(r => r.CreatedAt)" instead of ".OrderBy(r => r.CreatedAt)".
Describe your question
I am creating chart with line Chart and whan adding a new value (with index n, and because it is timeTuple it will be added on the end of list), but problem is that chart connect with line point on index 0 and n and then connect n with n+1 and so on... video
Which Blazor project type is your question related to?
Which charts is this question related to?
LineChart
JavaScript equivalent
How can I make chart connect new value n with n-1? I think there should be same sort od sorting data whan it is added in LinearDataset <TimeTuple >