Skip to content

Commit 9ac9ae7

Browse files
committed
N5.42.7 — SV Injector Phase C Ramp & State Sequence Workspace
1 parent 1b7b168 commit 9ac9ae7

16 files changed

Lines changed: 2500 additions & 332 deletions
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
using System.Collections;
2+
using System.Collections.Specialized;
3+
using System.ComponentModel;
4+
using System.Globalization;
5+
using System.Windows;
6+
using System.Windows.Media;
7+
using AR.Iec61850.SvPublisher.ViewModels;
8+
9+
namespace AR.Iec61850.SvPublisher.Controls;
10+
11+
public sealed class RampTimelinePlot : FrameworkElement
12+
{
13+
public static readonly DependencyProperty RampStatesProperty =
14+
DependencyProperty.Register(
15+
nameof(RampStates),
16+
typeof(IEnumerable),
17+
typeof(RampTimelinePlot),
18+
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, OnRampStatesChanged));
19+
20+
public IEnumerable? RampStates
21+
{
22+
get => (IEnumerable?)GetValue(RampStatesProperty);
23+
set => SetValue(RampStatesProperty, value);
24+
}
25+
26+
private static void OnRampStatesChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
27+
{
28+
var plot = (RampTimelinePlot)dependencyObject;
29+
plot.Detach(e.OldValue as IEnumerable);
30+
plot.Attach(e.NewValue as IEnumerable);
31+
plot.InvalidateVisual();
32+
}
33+
34+
protected override void OnRender(DrawingContext dc)
35+
{
36+
base.OnRender(dc);
37+
var width = ActualWidth;
38+
var height = ActualHeight;
39+
if (width < 80 || height < 80)
40+
return;
41+
42+
dc.DrawRoundedRectangle(new SolidColorBrush(Color.FromRgb(250, 250, 250)), null, new Rect(0, 0, width, height), 6, 6);
43+
44+
var plot = new Rect(58, 28, Math.Max(10, width - 78), Math.Max(10, height - 58));
45+
var axisPen = new Pen(new SolidColorBrush(Color.FromRgb(30, 41, 59)), 1);
46+
var gridPen = new Pen(new SolidColorBrush(Color.FromRgb(226, 232, 240)), 1);
47+
var rampPen = new Pen(new SolidColorBrush(Color.FromRgb(220, 38, 38)), 1.8);
48+
49+
for (var i = 0; i <= 5; i++)
50+
{
51+
var x = plot.Left + (plot.Width * i / 5.0);
52+
dc.DrawLine(gridPen, new Point(x, plot.Top), new Point(x, plot.Bottom));
53+
var y = plot.Top + (plot.Height * i / 5.0);
54+
dc.DrawLine(gridPen, new Point(plot.Left, y), new Point(plot.Right, y));
55+
}
56+
57+
dc.DrawLine(axisPen, new Point(plot.Left, plot.Bottom), new Point(plot.Right, plot.Bottom));
58+
dc.DrawLine(axisPen, new Point(plot.Left, plot.Top), new Point(plot.Left, plot.Bottom));
59+
60+
var states = GetRampStates().ToArray();
61+
if (states.Length == 0)
62+
{
63+
DrawText(dc, "No ramp states", new Point(plot.Left + 12, plot.Top + 16), 12, Color.FromRgb(100, 116, 139));
64+
return;
65+
}
66+
67+
var min = states.SelectMany(s => new[] { s.From, s.To }).Min();
68+
var max = states.SelectMany(s => new[] { s.From, s.To }).Max();
69+
if (Math.Abs(max - min) < 0.000001)
70+
{
71+
max += 1;
72+
min -= 1;
73+
}
74+
75+
var total = states.Sum(s => Math.Max(0.001, s.TimeSeconds));
76+
var cursor = 0.0;
77+
Point? last = null;
78+
foreach (var state in states)
79+
{
80+
var startX = plot.Left + (cursor / total * plot.Width);
81+
var endCursor = cursor + Math.Max(0.001, state.TimeSeconds);
82+
var endX = plot.Left + (endCursor / total * plot.Width);
83+
var y1 = MapY(state.From, min, max, plot);
84+
var y2 = MapY(state.To, min, max, plot);
85+
var p1 = new Point(startX, y1);
86+
var p2 = new Point(endX, y2);
87+
88+
if (last is not null)
89+
dc.DrawLine(rampPen, last.Value, p1);
90+
dc.DrawLine(rampPen, p1, p2);
91+
dc.DrawLine(new Pen(new SolidColorBrush(Color.FromRgb(148, 163, 184)), 0.8), new Point(startX, plot.Top), new Point(startX, plot.Bottom));
92+
DrawText(dc, state.Name, new Point(startX + 4, plot.Top - 22), 11, Color.FromRgb(30, 41, 59));
93+
last = p2;
94+
cursor = endCursor;
95+
}
96+
97+
DrawText(dc, max.ToString("0.000", CultureInfo.InvariantCulture), new Point(10, plot.Top - 6), 11, Color.FromRgb(30, 41, 59));
98+
DrawText(dc, min.ToString("0.000", CultureInfo.InvariantCulture), new Point(10, plot.Bottom - 14), 11, Color.FromRgb(30, 41, 59));
99+
DrawText(dc, total.ToString("0.000", CultureInfo.InvariantCulture) + " s", new Point(plot.Right - 48, plot.Bottom + 8), 11, Color.FromRgb(30, 41, 59));
100+
}
101+
102+
private static double MapY(double value, double min, double max, Rect plot)
103+
=> plot.Bottom - ((value - min) / (max - min) * plot.Height);
104+
105+
private IEnumerable<RampStateViewModel> GetRampStates()
106+
{
107+
if (RampStates is null)
108+
yield break;
109+
110+
foreach (var item in RampStates)
111+
{
112+
if (item is RampStateViewModel state)
113+
yield return state;
114+
}
115+
}
116+
117+
private void Attach(IEnumerable? enumerable)
118+
{
119+
if (enumerable is INotifyCollectionChanged collection)
120+
collection.CollectionChanged += OnCollectionChanged;
121+
122+
foreach (var item in enumerable ?? Array.Empty<object>())
123+
{
124+
if (item is INotifyPropertyChanged propertyChanged)
125+
propertyChanged.PropertyChanged += OnItemChanged;
126+
}
127+
}
128+
129+
private void Detach(IEnumerable? enumerable)
130+
{
131+
if (enumerable is INotifyCollectionChanged collection)
132+
collection.CollectionChanged -= OnCollectionChanged;
133+
134+
foreach (var item in enumerable ?? Array.Empty<object>())
135+
{
136+
if (item is INotifyPropertyChanged propertyChanged)
137+
propertyChanged.PropertyChanged -= OnItemChanged;
138+
}
139+
}
140+
141+
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
142+
{
143+
if (e.OldItems is not null)
144+
{
145+
foreach (var item in e.OldItems)
146+
{
147+
if (item is INotifyPropertyChanged propertyChanged)
148+
propertyChanged.PropertyChanged -= OnItemChanged;
149+
}
150+
}
151+
152+
if (e.NewItems is not null)
153+
{
154+
foreach (var item in e.NewItems)
155+
{
156+
if (item is INotifyPropertyChanged propertyChanged)
157+
propertyChanged.PropertyChanged += OnItemChanged;
158+
}
159+
}
160+
161+
InvalidateVisual();
162+
}
163+
164+
private void OnItemChanged(object? sender, PropertyChangedEventArgs e)
165+
=> InvalidateVisual();
166+
167+
private void DrawText(DrawingContext dc, string text, Point origin, double size, Color color)
168+
{
169+
var dpi = VisualTreeHelper.GetDpi(this);
170+
var formatted = new FormattedText(
171+
text,
172+
CultureInfo.CurrentUICulture,
173+
FlowDirection.LeftToRight,
174+
new Typeface("Segoe UI"),
175+
size,
176+
new SolidColorBrush(color),
177+
dpi.PixelsPerDip);
178+
dc.DrawText(formatted, origin);
179+
}
180+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
using System.Collections;
2+
using System.Collections.Specialized;
3+
using System.ComponentModel;
4+
using System.Globalization;
5+
using System.Windows;
6+
using System.Windows.Media;
7+
using AR.Iec61850.SvPublisher.ViewModels;
8+
9+
namespace AR.Iec61850.SvPublisher.Controls;
10+
11+
public sealed class StateTimelinePlot : FrameworkElement
12+
{
13+
public static readonly DependencyProperty SequenceStatesProperty =
14+
DependencyProperty.Register(
15+
nameof(SequenceStates),
16+
typeof(IEnumerable),
17+
typeof(StateTimelinePlot),
18+
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, OnSequenceStatesChanged));
19+
20+
public IEnumerable? SequenceStates
21+
{
22+
get => (IEnumerable?)GetValue(SequenceStatesProperty);
23+
set => SetValue(SequenceStatesProperty, value);
24+
}
25+
26+
private static void OnSequenceStatesChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
27+
{
28+
var plot = (StateTimelinePlot)dependencyObject;
29+
plot.Detach(e.OldValue as IEnumerable);
30+
plot.Attach(e.NewValue as IEnumerable);
31+
plot.InvalidateVisual();
32+
}
33+
34+
protected override void OnRender(DrawingContext dc)
35+
{
36+
base.OnRender(dc);
37+
var width = ActualWidth;
38+
var height = ActualHeight;
39+
if (width < 100 || height < 80)
40+
return;
41+
42+
dc.DrawRoundedRectangle(new SolidColorBrush(Color.FromRgb(250, 250, 250)), null, new Rect(0, 0, width, height), 6, 6);
43+
var states = GetStates().ToArray();
44+
if (states.Length == 0)
45+
{
46+
DrawText(dc, "No states", new Point(16, 16), 12, Color.FromRgb(100, 116, 139));
47+
return;
48+
}
49+
50+
var total = states.Sum(s => Math.Max(0.001, s.DurationSeconds));
51+
var plot = new Rect(66, 36, Math.Max(10, width - 86), Math.Max(10, height - 72));
52+
var gridPen = new Pen(new SolidColorBrush(Color.FromRgb(226, 232, 240)), 1);
53+
var axisPen = new Pen(new SolidColorBrush(Color.FromRgb(30, 41, 59)), 1);
54+
dc.DrawLine(axisPen, new Point(plot.Left, plot.Bottom), new Point(plot.Right, plot.Bottom));
55+
dc.DrawLine(axisPen, new Point(plot.Left, plot.Top), new Point(plot.Left, plot.Bottom));
56+
for (var i = 1; i <= 4; i++)
57+
{
58+
var y = plot.Top + plot.Height * i / 4.0;
59+
dc.DrawLine(gridPen, new Point(plot.Left, y), new Point(plot.Right, y));
60+
}
61+
62+
var cursor = 0.0;
63+
var phasePens = new[]
64+
{
65+
new Pen(new SolidColorBrush(Color.FromRgb(220, 38, 38)), 1.1),
66+
new Pen(new SolidColorBrush(Color.FromRgb(217, 119, 6)), 1.1),
67+
new Pen(new SolidColorBrush(Color.FromRgb(37, 99, 235)), 1.1)
68+
};
69+
70+
foreach (var state in states)
71+
{
72+
var startX = plot.Left + cursor / total * plot.Width;
73+
cursor += Math.Max(0.001, state.DurationSeconds);
74+
var endX = plot.Left + cursor / total * plot.Width;
75+
dc.DrawRectangle(new SolidColorBrush(Color.FromArgb(26, 59, 130, 246)), null, new Rect(startX, plot.Top, Math.Max(1, endX - startX), plot.Height));
76+
dc.DrawLine(new Pen(new SolidColorBrush(Color.FromRgb(100, 116, 139)), 1), new Point(startX, plot.Top - 18), new Point(startX, plot.Bottom));
77+
DrawText(dc, state.Name, new Point(startX + 4, plot.Top - 30), 11, Color.FromRgb(30, 41, 59));
78+
79+
for (var phase = 0; phase < 3; phase++)
80+
DrawSine(dc, startX, endX, plot, phasePens[phase], state.VoltageScale, phase * 120 + state.AngleShiftDegrees);
81+
}
82+
83+
DrawText(dc, "V / I preview", new Point(10, plot.Top + 4), 11, Color.FromRgb(30, 41, 59));
84+
DrawText(dc, total.ToString("0.000", CultureInfo.InvariantCulture) + " s", new Point(plot.Right - 48, plot.Bottom + 10), 11, Color.FromRgb(30, 41, 59));
85+
}
86+
87+
private static void DrawSine(DrawingContext dc, double startX, double endX, Rect plot, Pen pen, double scale, double phaseDegrees)
88+
{
89+
if (endX - startX < 2)
90+
return;
91+
92+
var geometry = new StreamGeometry();
93+
using (var context = geometry.Open())
94+
{
95+
var amplitude = Math.Clamp(scale, 0.05, 4.0) / 4.0 * plot.Height * 0.42;
96+
var centerY = plot.Top + plot.Height / 2.0;
97+
var phase = phaseDegrees * Math.PI / 180.0;
98+
for (var i = 0; i <= 120; i++)
99+
{
100+
var t = i / 120.0;
101+
var x = startX + (endX - startX) * t;
102+
var y = centerY - Math.Sin((t * Math.PI * 10) + phase) * amplitude;
103+
if (i == 0)
104+
context.BeginFigure(new Point(x, y), false, false);
105+
else
106+
context.LineTo(new Point(x, y), true, false);
107+
}
108+
}
109+
110+
geometry.Freeze();
111+
dc.DrawGeometry(null, pen, geometry);
112+
}
113+
114+
private IEnumerable<SequenceStateViewModel> GetStates()
115+
{
116+
if (SequenceStates is null)
117+
yield break;
118+
119+
foreach (var item in SequenceStates)
120+
{
121+
if (item is SequenceStateViewModel state)
122+
yield return state;
123+
}
124+
}
125+
126+
private void Attach(IEnumerable? enumerable)
127+
{
128+
if (enumerable is INotifyCollectionChanged collection)
129+
collection.CollectionChanged += OnCollectionChanged;
130+
foreach (var item in enumerable ?? Array.Empty<object>())
131+
if (item is INotifyPropertyChanged propertyChanged)
132+
propertyChanged.PropertyChanged += OnItemChanged;
133+
}
134+
135+
private void Detach(IEnumerable? enumerable)
136+
{
137+
if (enumerable is INotifyCollectionChanged collection)
138+
collection.CollectionChanged -= OnCollectionChanged;
139+
foreach (var item in enumerable ?? Array.Empty<object>())
140+
if (item is INotifyPropertyChanged propertyChanged)
141+
propertyChanged.PropertyChanged -= OnItemChanged;
142+
}
143+
144+
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
145+
{
146+
InvalidateVisual();
147+
}
148+
149+
private void OnItemChanged(object? sender, PropertyChangedEventArgs e)
150+
=> InvalidateVisual();
151+
152+
private void DrawText(DrawingContext dc, string text, Point origin, double size, Color color)
153+
{
154+
var dpi = VisualTreeHelper.GetDpi(this);
155+
var formatted = new FormattedText(
156+
text,
157+
CultureInfo.CurrentUICulture,
158+
FlowDirection.LeftToRight,
159+
new Typeface("Segoe UI"),
160+
size,
161+
new SolidColorBrush(color),
162+
dpi.PixelsPerDip);
163+
dc.DrawText(formatted, origin);
164+
}
165+
}

0 commit comments

Comments
 (0)