mariusmuntean / ChartJs.Blazor

Brings Chart.js charts to Blazor
https://www.iheartblazor.com/
MIT License
691 stars 152 forks source link

Adding new value in LineChart(Time line) #135

Closed mikara89 closed 4 years ago

mikara89 commented 4 years ago

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 >

MindSwipe commented 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?

mikara89 commented 4 years ago
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.

Joelius300 commented 4 years ago

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.

Once the code is cleaned and the .First()s, .Values', .ForEachs 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.

mikara89 commented 4 years ago

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.

mikara89 commented 4 years ago
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" />

mikara89 commented 4 years ago

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)".