using Sony.Filtr.Functional; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; namespace Sony.Filtr.Tasks.Helpers { public class ProgressChart : IDisposable { private readonly IEnumerable Items; private const int Width = 1900; private const int Height = 860; private readonly Point ZeroPoint = new Point(30, Height - 30); private readonly Point YAxisTop = new Point(30, 20); private readonly Point YAxisMax = new Point(30, 30); private readonly Point XAxisTop = new Point(Width - 10, Height - 30); private readonly Point XAxisMax = new Point(Width - 50, Height - 30); private readonly Pen TextPen = new Pen(Color.Black, 1); private readonly Pen ScaleLinesPen = new Pen(Color.Black, 0.5f) { DashStyle = DashStyle.Dash }; private readonly Pen CurveLinePen = new Pen(Color.Red, 2f); private readonly Font Font = new Font("Tahoma", 12); private bool disposedValue; private ProgressTracker.ProgressItem StartItem { get { return this.Items.First(); } } private ProgressTracker.ProgressItem LastItem { get { return this.Items.Last(); } } private readonly int TotalItems; private const int YScaleStep = 100_000; private readonly float YPixelShift; private readonly int TotalHours; private readonly float XPixelShift; public ProgressChart(ProgressTracker progress) { this.Items = PrepareItems(progress.Items.Reverse()); this.TotalItems = progress.Total; this.YPixelShift = (ZeroPoint.Y - YAxisMax.Y) / (Math.Max(this.TotalItems, 1) / YScaleStep); this.TotalHours = (int)(this.LastItem.DT - this.StartItem.DT).TotalHours + 1; this.XPixelShift = (XAxisMax.X - ZeroPoint.X) / this.TotalHours; } public void ToChartBitmap(string filePath) { if (this.Items.Count() < 2) { return; } using (Bitmap bmp = new Bitmap(Width, Height)) { using (Graphics graphics = Graphics.FromImage(bmp)) { Result.Success(graphics) .Tap(g => DrawYAxis(g)) .Tap(g => DrawXAxis(g)) .Tap(g => DrawXAxisNumbers(g)) .Tap(g => DrawYAxisNumbers(g)) .Tap(g => DrawCurve(g)) .Tap(g => DrawCurrentProgress(g)) ; } bmp.Save(filePath); } } private static IEnumerable PrepareItems(IEnumerable items) { return items .GroupBy(i => i.DT.ToString("yyyy_MM_dd_HH_mm_ss")) .Select(g => g.OrderBy(v => v.Current).First()) .OrderBy(i => i.DT) .ToList(); } private void DrawCurrentProgress(Graphics g) { var lastItemPoint = this.ItemToPoint(this.LastItem); this.DrawText(g, $"{this.LastItem.Current}{Environment.NewLine}{this.LastItem.ToPercentage(this.TotalItems)}", new RectangleF(lastItemPoint.X, lastItemPoint.Y, 150, 40)); } private void DrawCurve(Graphics g) { g.DrawCurve(this.CurveLinePen, this.Items.Select(i => ItemToPoint(i)).ToArray()); } private Point ItemToPoint(ProgressTracker.ProgressItem item) { double x = item.HourShift(this.StartItem.DT) * this.XPixelShift + ZeroPoint.X; double y = ZeroPoint.Y - ((double)item.Current / YScaleStep) * this.YPixelShift; return new Point((int)x, (int)y); } private void DrawYAxis(Graphics g) { g.DrawLine(this.TextPen, YAxisTop.X, YAxisTop.Y, ZeroPoint.X, Height); g.DrawLine(this.TextPen, YAxisTop.X, YAxisTop.Y, YAxisTop.X - 5, 30); g.DrawLine(this.TextPen, YAxisTop.X, YAxisTop.Y, YAxisTop.X + 5, 30); this.DrawText(g, $"Playlists {this.TotalItems}", new RectangleF(0, 0, 200, 20)); } private void DrawXAxis(Graphics g) { g.DrawLine(this.TextPen, 10, ZeroPoint.Y, Width - 10, ZeroPoint.Y); g.DrawLine(this.TextPen, XAxisTop.X, XAxisTop.Y, XAxisTop.X - 40, XAxisTop.Y - 5); g.DrawLine(this.TextPen, XAxisTop.X, XAxisTop.Y, XAxisTop.X - 40, XAxisTop.Y + 5); this.DrawText(g, "Time (h)", new RectangleF(Width - 70, ZeroPoint.Y - 20, 100, 20)); } private void DrawText(Graphics g, string text, RectangleF textArea) { g.DrawString(text, this.Font, Brushes.Black, textArea); } private void DrawYAxisNumbers(Graphics g) { float currentYShift = ZeroPoint.Y; for (int y = YScaleStep; y < this.TotalItems; y += YScaleStep) { currentYShift -= this.YPixelShift; g.DrawLine(this.ScaleLinesPen, ZeroPoint.X - 5, currentYShift, XAxisTop.X, currentYShift); this.DrawText(g, $"{y/1000}K", new RectangleF(ZeroPoint.X - 20, currentYShift - 10, 100, 20)); } } private void DrawXAxisNumbers(Graphics g) { float currentXShift = ZeroPoint.X; for (int x = 0; x <= this.TotalHours; ++x) { g.DrawLine(this.ScaleLinesPen, currentXShift, ZeroPoint.Y + 5, currentXShift, YAxisTop.Y); this.DrawText(g, $"{this.StartItem.DT.AddHours(x).ToString("HH:mm")}({x})", new RectangleF(currentXShift - 15, ZeroPoint.Y + 6, 60, 20)); currentXShift += this.XPixelShift; } } protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { this.CurveLinePen.Dispose(); this.ScaleLinesPen.Dispose(); this.TextPen.Dispose(); } disposedValue = true; } } public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } } }