using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Media; using CPR.App.Nexus.Interface.DOM; using SciChart.Charting.Visuals.RenderableSeries; using SciChart.Data.Model; using SciChart.Drawing.Common; namespace CPR.App.Nexus.UI.TrafficPlanner.ViewModel.Chart { public class CustomFastLineRenderableSeries : FastLineRenderableSeries { // offset for drawing route points - defines the size of the points const double Offset = 3.5; public CustomFastLineRenderableSeries(Domain.Train train) { Train = train; } public ITrain Train { get; set; } protected override void InternalDraw(IRenderContext2D renderContext, IRenderPassData renderPassData) { base.InternalDraw(renderContext, renderPassData); var originalPointSeries = renderPassData.PointSeries; var pointSeriesIndices = originalPointSeries.Indexes; var routePoints = Train.TrainMileDirection == MileDirection.LOWER ? Train.RoutePoints.ToArray() : Train.RoutePoints.Reverse().ToArray(); foreach (var index in pointSeriesIndices) { //#if DEBUG // Console.WriteLine("Total indexes: " + pointSeriesIndices.Count.ToString() + " Current value: " + index.ToString()); //#endif try { var x = originalPointSeries[index].X; var y = originalPointSeries[index].Y; var validPoint = true; if (validPoint) { var rp = routePoints.ElementAt(index); var fillColor = StatusColor(rp); var fillBrush = renderContext.CreateBrush(fillColor); var pen = renderContext.CreatePen(rp.CanTakeSiding || rp.IsInMultiTrackArea ? fillColor : Colors.Black, AntiAliasing, 1); var orig = GetCoordinatesFor(x, y); // add manual adjustment highlight if(rp.HasManualAdjustments || (rp.NextActualRoutePoint != null && rp.NextActualRoutePoint.HasManualAdjustments) || Train.PresetTrainMeetLocations.Any(m => m.MeetLocationFsac == rp.Fsac_cd) || (rp.IsSubdivisionEntry && rp.SandboxDwellTime != TimeSpan.Zero)) AddManualAdjustmentHighlight(renderContext, orig, rp); if (rp.IsCTCHit) { pen = renderContext.CreatePen(Colors.Black, AntiAliasing, 1); var offset = 3; Point[] points = { orig, orig, orig, orig }; points[0].X -= offset; points[0].Y += offset; points[1].X += offset; points[1].Y -= offset; points[2].X -= offset; points[2].Y -= offset; points[3].X += offset; points[3].Y += offset; renderContext.DrawLine(pen, points[0], points[1]); renderContext.DrawLine(pen, points[2], points[3]); } else if (!rp.IsSubdivisionExit && // no star at terminal (rp.HasTrainWork || (rp.PreviousActualRoutePoint != null && rp.PreviousActualRoutePoint.HasTrainWork) || rp.TrainWork.IsMTP || (rp.PreviousActualRoutePoint != null && rp.PreviousActualRoutePoint.TrainWork.IsMTP))) { var polygon = Calculate5StarPoints(orig, 6, 3); renderContext.FillPolygon(fillBrush, polygon); renderContext.DrawLines(pen, polygon); } else if (rp.IsSubdivisionEntry && (rp.HasDecisionAdjustments || rp.SandboxDwellTime != TimeSpan.Zero)) { var polygon = CalculateSquarePoints(orig); renderContext.FillPolygon(fillBrush, polygon); renderContext.DrawLines(pen, polygon); } else if (rp.HasTrackSwitched) { var polygon = CalculateUpwardTrianglePoints(orig); if (rp.TrackUsed.Name.Contains("SOUTH")) { polygon = CalculateDownwardTrianglePoints(orig); } renderContext.FillPolygon(fillBrush, polygon); renderContext.DrawLines(pen, polygon); } // uncomment if you only want to see lines and no route points else //if(rp.IsSubdivisionEntry || rp.IsSubdivisionExit || rp.HasAdjustmentOrWorkOrSuspend || rp.NextRoutePoint.HasAdjustmentOrWorkOrSuspend) { const int width = 6; renderContext.DrawEllipse(pen, fillBrush, orig, width, width); } // add train labels if ((rp.IsCorridorEntry && Train.TrainMileDirection == MileDirection.LOWER) || (rp.IsCorridorExit && Train.TrainMileDirection == MileDirection.HIGHER)) { renderContext.DrawText(new Rect(orig.X - 55, orig.Y - 8, 50, 15), Colors.Black, 10, Train.TrainName); } else if ((rp.IsCorridorExit && Train.TrainMileDirection == MileDirection.LOWER) || (rp.IsCorridorEntry && Train.TrainMileDirection == MileDirection.HIGHER)) { renderContext.DrawText(new Rect(orig.X, orig.Y - 8, 50, 15), Colors.Black, 10, Train.TrainName); } } } catch (Exception ex) { #if DEBUG Console.WriteLine(ex.Message); #endif } } } private void AddManualAdjustmentHighlight(IRenderContext2D renderContext, Point orig, IRoutePoint rp) { var manualHighLightWidth = 14; var fillColor = Color.FromArgb(255, 255,153,51); var fillBrush = renderContext.CreateBrush(fillColor, 1); var pen = renderContext.CreatePen(fillColor, AntiAliasing, 1); renderContext.DrawEllipse(pen, fillBrush, orig, manualHighLightWidth, manualHighLightWidth); } private static Point[] CalculateSquarePoints(Point orig) { var offset = 3; Point[] points = { orig, orig, orig, orig, orig }; points[0].X -= offset; points[0].Y -= offset; points[1].X += offset; points[1].Y -= offset; points[2].X += offset; points[2].Y += offset; points[3].X -= offset; points[3].Y += offset; points[4].X -= offset; points[4].Y -= offset; return points; } private static Point[] CalculateUpwardTrianglePoints(Point orig) { //var offset = 3.5; Point[] points = {orig, orig, orig, orig}; points[0].Y -= Offset; points[1].X += Offset; points[1].Y += Offset; points[2].X -= Offset; points[2].Y += Offset; points[3].Y -= Offset; return points; } private static Point[] CalculateDownwardTrianglePoints(Point orig) { //var offset = 3.5; Point[] points = { orig, orig, orig, orig }; points[0].Y += Offset; points[1].X += Offset; points[1].Y -= Offset; points[2].X -= Offset; points[2].Y -= Offset; points[3].Y += Offset; return points; } private Point[] Calculate5StarPoints(Point orig, float outerradius, float innerradius) { // Define some variables to avoid as much calculations as possible // conversions to radians const double ang36 = Math.PI / 5.0; // 36° x PI/180 const double ang72 = 2.0 * ang36; // 72° x PI/180 // some sine and cosine values we need var sin36 = (float)Math.Sin(ang36); var sin72 = (float)Math.Sin(ang72); var cos36 = (float)Math.Cos(ang36); var cos72 = (float)Math.Cos(ang72); // Fill array with 10 origin points Point[] pnts = { orig, orig, orig, orig, orig, orig, orig, orig, orig, orig, orig }; pnts[0].Y -= outerradius; // top off the star, or on a clock this is 12:00 or 0:00 hours pnts[1].X += innerradius * sin36; pnts[1].Y -= innerradius * cos36; // 0:06 hours pnts[2].X += outerradius * sin72; pnts[2].Y -= outerradius * cos72; // 0:12 hours pnts[3].X += innerradius * sin72; pnts[3].Y += innerradius * cos72; // 0:18 pnts[4].X += outerradius * sin36; pnts[4].Y += outerradius * cos36; // 0:24 // Phew! Glad I got that trig working. pnts[5].Y += innerradius; // I use the symmetry of the star figure here pnts[6].X += pnts[6].X - pnts[4].X; pnts[6].Y = pnts[4].Y; // mirror point pnts[7].X += pnts[7].X - pnts[3].X; pnts[7].Y = pnts[3].Y; // mirror point pnts[8].X += pnts[8].X - pnts[2].X; pnts[8].Y = pnts[2].Y; // mirror point pnts[9].X += pnts[9].X - pnts[1].X; pnts[9].Y = pnts[1].Y; // mirror point pnts[10].Y -= outerradius; return pnts; } private Point GetCoordinatesFor(double xValue, double yValue) { // Get the coordinateCalculators. See 'Converting Pixel Coordinates to Data Coordinates' documentation for coordinate transforms var xCoord = CurrentRenderPassData.XCoordinateCalculator.GetCoordinate(xValue); var yCoord = CurrentRenderPassData.YCoordinateCalculator.GetCoordinate(yValue); if (CurrentRenderPassData.IsVerticalChart) { Swap(ref xCoord, ref yCoord); } return new Point(xCoord, yCoord); } private void Swap(ref double arg1, ref double arg2) { var tmp = arg2; arg2 = arg1; arg1 = tmp; } private IList ComputeSplineSeries(IPointSeries inputPointSeries, bool isSplineEnabled, int upsampleBy) { IList result = null; if (!isSplineEnabled) { // No spline, just return points. Note: for large datasets, even the copy here causes performance problems! result = new List(inputPointSeries.Count); for (int i = 0; i < inputPointSeries.Count; i++) { result.Add(new Point(inputPointSeries.XValues[i], inputPointSeries.YValues[i])); } return result; } // Spline enabled int n = inputPointSeries.Count * upsampleBy; var x = inputPointSeries.XValues.ToArray(); var y = inputPointSeries.YValues.ToArray(); double[] xs = new double[n]; double stepSize = (x[x.Length - 1] - x[0]) / (n - 1); for (int i = 0; i < n; i++) { xs[i] = x[0] + i * stepSize; } var cubicSpline = new CubicSpline(); double[] ys = cubicSpline.FitAndEval(x, y, xs); result = new List(n); for (int i = 0; i < xs.Length; i++) { result.Add(new Point(xs[i], ys[i])); } return result; } private int FindIndex(IList list, double value) { for (int i = 0; i < list.Count; i++) { if (list[i].X.CompareTo(value) >= 0) return i; } return -1; } private Color? _trainColor; private Color TrainColor { get { if (_trainColor == null) { var trainKind = Train.TrainKind; if (trainKind == null) _trainColor = Colors.Black; var selectedKindColor = Train.Parent.TrainKindColorList.FirstOrDefault( kindColor => (kindColor.Kinds.Contains(trainKind.Trim().ToUpper()))); if (selectedKindColor != null) { var rgbValue = double.Parse(selectedKindColor.Color.ToString()); var blue = (byte)Math.Floor(rgbValue / 65536); var green = (byte)(Math.Floor(rgbValue % 65536) / 256); var red = (byte)Math.Floor((rgbValue % 65536) % 256); _trainColor = Color.FromArgb(255, red, green, blue); } } return _trainColor.GetValueOrDefault(Colors.Aqua); } } private Color StatusColor(IRoutePoint rp) { var statusColor = Colors.Black; var status = rp.Status; if (!rp.IsActive.Value) statusColor = Colors.Gray; else { switch (status) { case TrainScheduleStatus.MoreThanTwoHoursLate: statusColor = Colors.Red; break; case TrainScheduleStatus.OneHourToTwoHoursLate: statusColor = Colors.Gold; break; case TrainScheduleStatus.OneHourEarlyOrOneHourLate: statusColor = Color.FromRgb(63, 159, 63); //statusColor = Color.FromRgb(127, 191, 127); break; case TrainScheduleStatus.MoreThanOneHourEarly: statusColor = Color.FromRgb(100, 100, 255); break; default: statusColor = Colors.DarkGray; break; // newR = currentR + (255 - currentR) * tint_factor //newG = currentG + (255 - currentG) * tint_factor //newB = currentB + (255 - currentB) * tint_factor } } return (statusColor); } } }