moel@158
|
1 |
/*
|
moel@1
|
2 |
|
moel@344
|
3 |
This Source Code Form is subject to the terms of the Mozilla Public
|
moel@344
|
4 |
License, v. 2.0. If a copy of the MPL was not distributed with this
|
moel@344
|
5 |
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
moel@1
|
6 |
|
moel@395
|
7 |
Copyright (C) 2009-2013 Michael Möller <mmoeller@openhardwaremonitor.org>
|
moel@344
|
8 |
|
moel@1
|
9 |
*/
|
moel@1
|
10 |
|
moel@1
|
11 |
using System;
|
moel@1
|
12 |
using System.Collections.Generic;
|
moel@1
|
13 |
using System.Drawing;
|
moel@395
|
14 |
using System.Linq;
|
moel@1
|
15 |
using System.Windows.Forms;
|
moel@1
|
16 |
using OpenHardwareMonitor.Hardware;
|
moel@395
|
17 |
using OxyPlot;
|
moel@395
|
18 |
using OxyPlot.Axes;
|
moel@395
|
19 |
using OxyPlot.WindowsForms;
|
moel@395
|
20 |
using OxyPlot.Series;
|
moel@395
|
21 |
using OpenHardwareMonitor.Collections;
|
moel@1
|
22 |
|
moel@1
|
23 |
namespace OpenHardwareMonitor.GUI {
|
moel@158
|
24 |
public class PlotPanel : UserControl {
|
moel@1
|
25 |
|
moel@400
|
26 |
private readonly PersistentSettings settings;
|
moel@400
|
27 |
private readonly UnitManager unitManager;
|
moel@326
|
28 |
|
moel@395
|
29 |
private readonly Plot plot;
|
moel@395
|
30 |
private readonly PlotModel model;
|
moel@395
|
31 |
private readonly TimeSpanAxis timeAxis = new TimeSpanAxis();
|
moel@395
|
32 |
private readonly SortedDictionary<SensorType, LinearAxis> axes =
|
moel@395
|
33 |
new SortedDictionary<SensorType, LinearAxis>();
|
moel@395
|
34 |
|
moel@397
|
35 |
private UserOption stackedAxes;
|
moel@397
|
36 |
|
moel@1
|
37 |
private DateTime now;
|
moel@326
|
38 |
|
moel@400
|
39 |
public PlotPanel(PersistentSettings settings, UnitManager unitManager) {
|
moel@326
|
40 |
this.settings = settings;
|
moel@400
|
41 |
this.unitManager = unitManager;
|
moel@400
|
42 |
|
moel@395
|
43 |
this.model = CreatePlotModel();
|
moel@326
|
44 |
|
moel@395
|
45 |
this.plot = new Plot();
|
moel@395
|
46 |
this.plot.Dock = DockStyle.Fill;
|
moel@395
|
47 |
this.plot.Model = model;
|
moel@395
|
48 |
this.plot.BackColor = Color.White;
|
moel@397
|
49 |
this.plot.ContextMenu = CreateMenu();
|
moel@397
|
50 |
|
moel@397
|
51 |
UpdateAxesPosition();
|
moel@1
|
52 |
|
moel@395
|
53 |
this.SuspendLayout();
|
moel@395
|
54 |
this.Controls.Add(plot);
|
moel@395
|
55 |
this.ResumeLayout(true);
|
moel@1
|
56 |
}
|
moel@1
|
57 |
|
moel@395
|
58 |
public void SetCurrentSettings() {
|
moel@395
|
59 |
settings.SetValue("plotPanel.MinTimeSpan", (float)timeAxis.ViewMinimum);
|
moel@395
|
60 |
settings.SetValue("plotPanel.MaxTimeSpan", (float)timeAxis.ViewMaximum);
|
moel@395
|
61 |
|
moel@395
|
62 |
foreach (var axis in axes.Values) {
|
moel@395
|
63 |
settings.SetValue("plotPanel.Min" + axis.Key, (float)axis.ViewMinimum);
|
moel@395
|
64 |
settings.SetValue("plotPanel.Max" + axis.Key, (float)axis.ViewMaximum);
|
moel@395
|
65 |
}
|
moel@395
|
66 |
}
|
moel@395
|
67 |
|
moel@397
|
68 |
private ContextMenu CreateMenu() {
|
moel@397
|
69 |
ContextMenu menu = new ContextMenu();
|
moel@397
|
70 |
|
moel@397
|
71 |
MenuItem stackedAxesMenuItem = new MenuItem("Stacked Axes");
|
moel@397
|
72 |
stackedAxes = new UserOption("stackedAxes", true,
|
moel@397
|
73 |
stackedAxesMenuItem, settings);
|
moel@397
|
74 |
stackedAxes.Changed += (sender, e) => {
|
moel@397
|
75 |
UpdateAxesPosition();
|
moel@397
|
76 |
InvalidatePlot();
|
moel@397
|
77 |
};
|
moel@397
|
78 |
menu.MenuItems.Add(stackedAxesMenuItem);
|
moel@397
|
79 |
|
moel@395
|
80 |
MenuItem timeWindow = new MenuItem("Time Window");
|
moel@395
|
81 |
MenuItem[] timeWindowMenuItems =
|
moel@395
|
82 |
{ new MenuItem("Auto",
|
moel@395
|
83 |
(s, e) => { timeAxis.Zoom(0, double.NaN); InvalidatePlot(); }),
|
moel@395
|
84 |
new MenuItem("5 min",
|
moel@395
|
85 |
(s, e) => { timeAxis.Zoom(0, 5 * 60); InvalidatePlot(); }),
|
moel@395
|
86 |
new MenuItem("10 min",
|
moel@395
|
87 |
(s, e) => { timeAxis.Zoom(0, 10 * 60); InvalidatePlot(); }),
|
moel@395
|
88 |
new MenuItem("20 min",
|
moel@395
|
89 |
(s, e) => { timeAxis.Zoom(0, 20 * 60); InvalidatePlot(); }),
|
moel@395
|
90 |
new MenuItem("30 min",
|
moel@395
|
91 |
(s, e) => { timeAxis.Zoom(0, 30 * 60); InvalidatePlot(); }),
|
moel@395
|
92 |
new MenuItem("45 min",
|
moel@395
|
93 |
(s, e) => { timeAxis.Zoom(0, 45 * 60); InvalidatePlot(); }),
|
moel@395
|
94 |
new MenuItem("1 h",
|
moel@395
|
95 |
(s, e) => { timeAxis.Zoom(0, 60 * 60); InvalidatePlot(); }),
|
moel@395
|
96 |
new MenuItem("1.5 h",
|
moel@395
|
97 |
(s, e) => { timeAxis.Zoom(0, 1.5 * 60 * 60); InvalidatePlot(); }),
|
moel@395
|
98 |
new MenuItem("2 h",
|
moel@395
|
99 |
(s, e) => { timeAxis.Zoom(0, 2 * 60 * 60); InvalidatePlot(); }),
|
moel@395
|
100 |
new MenuItem("3 h",
|
moel@395
|
101 |
(s, e) => { timeAxis.Zoom(0, 3 * 60 * 60); InvalidatePlot(); }),
|
moel@395
|
102 |
new MenuItem("6 h",
|
moel@395
|
103 |
(s, e) => { timeAxis.Zoom(0, 6 * 60 * 60); InvalidatePlot(); }),
|
moel@395
|
104 |
new MenuItem("12 h",
|
moel@395
|
105 |
(s, e) => { timeAxis.Zoom(0, 12 * 60 * 60); InvalidatePlot(); }),
|
moel@395
|
106 |
new MenuItem("24 h",
|
moel@395
|
107 |
(s, e) => { timeAxis.Zoom(0, 24 * 60 * 60); InvalidatePlot(); }) };
|
moel@326
|
108 |
foreach (MenuItem mi in timeWindowMenuItems)
|
moel@326
|
109 |
timeWindow.MenuItems.Add(mi);
|
moel@397
|
110 |
menu.MenuItems.Add(timeWindow);
|
moel@326
|
111 |
|
moel@397
|
112 |
return menu;
|
moel@326
|
113 |
}
|
moel@326
|
114 |
|
moel@395
|
115 |
private PlotModel CreatePlotModel() {
|
moel@1
|
116 |
|
moel@395
|
117 |
timeAxis.Position = AxisPosition.Bottom;
|
moel@395
|
118 |
timeAxis.MajorGridlineStyle = LineStyle.Solid;
|
moel@395
|
119 |
timeAxis.MajorGridlineThickness = 1;
|
moel@395
|
120 |
timeAxis.MajorGridlineColor = OxyColor.FromRgb(192, 192, 192);
|
moel@395
|
121 |
timeAxis.MinorGridlineStyle = LineStyle.Solid;
|
moel@395
|
122 |
timeAxis.MinorGridlineThickness = 1;
|
moel@395
|
123 |
timeAxis.MinorGridlineColor = OxyColor.FromRgb(232, 232, 232);
|
moel@395
|
124 |
timeAxis.StartPosition = 1;
|
moel@395
|
125 |
timeAxis.EndPosition = 0;
|
moel@395
|
126 |
timeAxis.MinimumPadding = 0;
|
moel@395
|
127 |
timeAxis.MaximumPadding = 0;
|
moel@395
|
128 |
timeAxis.AbsoluteMinimum = 0;
|
moel@395
|
129 |
timeAxis.Minimum = 0;
|
moel@395
|
130 |
timeAxis.AbsoluteMaximum = 24 * 60 * 60;
|
moel@395
|
131 |
timeAxis.Zoom(
|
moel@395
|
132 |
settings.GetValue("plotPanel.MinTimeSpan", 0.0f),
|
moel@395
|
133 |
settings.GetValue("plotPanel.MaxTimeSpan", 10.0f * 60));
|
moel@395
|
134 |
timeAxis.StringFormat = "h:mm";
|
moel@395
|
135 |
|
moel@395
|
136 |
var units = new Dictionary<SensorType, string>();
|
moel@395
|
137 |
units.Add(SensorType.Voltage, "V");
|
moel@395
|
138 |
units.Add(SensorType.Clock, "MHz");
|
moel@395
|
139 |
units.Add(SensorType.Temperature, "°C");
|
moel@395
|
140 |
units.Add(SensorType.Load, "%");
|
moel@395
|
141 |
units.Add(SensorType.Fan, "RPM");
|
moel@395
|
142 |
units.Add(SensorType.Flow, "L/h");
|
moel@395
|
143 |
units.Add(SensorType.Control, "%");
|
moel@395
|
144 |
units.Add(SensorType.Level, "%");
|
moel@395
|
145 |
units.Add(SensorType.Factor, "1");
|
moel@395
|
146 |
units.Add(SensorType.Power, "W");
|
moel@395
|
147 |
units.Add(SensorType.Data, "GB");
|
moel@395
|
148 |
|
moel@395
|
149 |
foreach (SensorType type in Enum.GetValues(typeof(SensorType))) {
|
moel@395
|
150 |
var axis = new LinearAxis();
|
moel@395
|
151 |
axis.Position = AxisPosition.Left;
|
moel@395
|
152 |
axis.MajorGridlineStyle = LineStyle.Solid;
|
moel@395
|
153 |
axis.MajorGridlineThickness = 1;
|
moel@395
|
154 |
axis.MajorGridlineColor = timeAxis.MajorGridlineColor;
|
moel@395
|
155 |
axis.MinorGridlineStyle = LineStyle.Solid;
|
moel@395
|
156 |
axis.MinorGridlineThickness = 1;
|
moel@395
|
157 |
axis.MinorGridlineColor = timeAxis.MinorGridlineColor;
|
moel@397
|
158 |
axis.AxislineStyle = LineStyle.Solid;
|
moel@395
|
159 |
axis.Title = type.ToString();
|
moel@395
|
160 |
axis.Key = type.ToString();
|
moel@395
|
161 |
|
moel@395
|
162 |
axis.Zoom(
|
moel@395
|
163 |
settings.GetValue("plotPanel.Min" + axis.Key, float.NaN),
|
moel@395
|
164 |
settings.GetValue("plotPanel.Max" + axis.Key, float.NaN));
|
moel@395
|
165 |
|
moel@395
|
166 |
if (units.ContainsKey(type))
|
moel@395
|
167 |
axis.Unit = units[type];
|
moel@395
|
168 |
axes.Add(type, axis);
|
moel@1
|
169 |
}
|
moel@1
|
170 |
|
moel@395
|
171 |
var model = new PlotModel();
|
moel@395
|
172 |
model.Axes.Add(timeAxis);
|
moel@395
|
173 |
foreach (var axis in axes.Values)
|
moel@395
|
174 |
model.Axes.Add(axis);
|
moel@395
|
175 |
model.PlotMargins = new OxyThickness(0);
|
moel@395
|
176 |
model.IsLegendVisible = false;
|
moel@158
|
177 |
|
moel@395
|
178 |
return model;
|
moel@1
|
179 |
}
|
moel@1
|
180 |
|
moel@158
|
181 |
public void SetSensors(List<ISensor> sensors,
|
moel@158
|
182 |
IDictionary<ISensor, Color> colors) {
|
moel@395
|
183 |
this.model.Series.Clear();
|
moel@395
|
184 |
|
moel@395
|
185 |
ListSet<SensorType> types = new ListSet<SensorType>();
|
moel@395
|
186 |
|
moel@395
|
187 |
foreach (ISensor sensor in sensors) {
|
moel@395
|
188 |
var series = new LineSeries();
|
moel@400
|
189 |
if (sensor.SensorType == SensorType.Temperature) {
|
moel@400
|
190 |
series.ItemsSource = sensor.Values.Select(value => new DataPoint {
|
moel@400
|
191 |
X = (now - value.Time).TotalSeconds,
|
moel@400
|
192 |
Y = unitManager.TemperatureUnit == TemperatureUnit.Celsius ?
|
moel@400
|
193 |
value.Value : UnitManager.CelsiusToFahrenheit(value.Value).Value
|
moel@400
|
194 |
});
|
moel@400
|
195 |
} else {
|
moel@400
|
196 |
series.ItemsSource = sensor.Values.Select(value => new DataPoint {
|
moel@400
|
197 |
X = (now - value.Time).TotalSeconds, Y = value.Value
|
moel@400
|
198 |
});
|
moel@400
|
199 |
}
|
moel@395
|
200 |
series.Color = colors[sensor].ToOxyColor();
|
moel@395
|
201 |
series.StrokeThickness = 1;
|
moel@395
|
202 |
series.YAxisKey = axes[sensor.SensorType].Key;
|
moel@395
|
203 |
series.Title = sensor.Hardware.Name + " " + sensor.Name;
|
moel@395
|
204 |
this.model.Series.Add(series);
|
moel@395
|
205 |
|
moel@395
|
206 |
types.Add(sensor.SensorType);
|
moel@395
|
207 |
}
|
moel@395
|
208 |
|
moel@395
|
209 |
foreach (var pair in axes.Reverse()) {
|
moel@395
|
210 |
var axis = pair.Value;
|
moel@395
|
211 |
var type = pair.Key;
|
moel@400
|
212 |
axis.IsAxisVisible = types.Contains(type);
|
moel@397
|
213 |
}
|
moel@397
|
214 |
|
moel@397
|
215 |
UpdateAxesPosition();
|
moel@397
|
216 |
InvalidatePlot();
|
moel@397
|
217 |
}
|
moel@397
|
218 |
|
moel@397
|
219 |
private void UpdateAxesPosition() {
|
moel@397
|
220 |
if (stackedAxes.Value) {
|
moel@397
|
221 |
var count = axes.Values.Count(axis => axis.IsAxisVisible);
|
moel@397
|
222 |
var start = 0.0;
|
moel@397
|
223 |
foreach (var pair in axes.Reverse()) {
|
moel@397
|
224 |
var axis = pair.Value;
|
moel@397
|
225 |
var type = pair.Key;
|
moel@397
|
226 |
axis.StartPosition = start;
|
moel@397
|
227 |
var delta = axis.IsAxisVisible ? 1.0 / count : 0;
|
moel@397
|
228 |
start += delta;
|
moel@397
|
229 |
axis.EndPosition = start;
|
moel@397
|
230 |
axis.PositionTier = 0;
|
moel@397
|
231 |
axis.MajorGridlineStyle = LineStyle.Solid;
|
moel@397
|
232 |
axis.MinorGridlineStyle = LineStyle.Solid;
|
moel@397
|
233 |
}
|
moel@397
|
234 |
} else {
|
moel@397
|
235 |
var tier = 0;
|
moel@397
|
236 |
foreach (var pair in axes.Reverse()) {
|
moel@397
|
237 |
var axis = pair.Value;
|
moel@397
|
238 |
var type = pair.Key;
|
moel@400
|
239 |
if (axis.IsAxisVisible) {
|
moel@400
|
240 |
axis.StartPosition = 0;
|
moel@400
|
241 |
axis.EndPosition = 1;
|
moel@400
|
242 |
axis.PositionTier = tier;
|
moel@397
|
243 |
tier++;
|
moel@400
|
244 |
} else {
|
moel@400
|
245 |
axis.StartPosition = 0;
|
moel@400
|
246 |
axis.EndPosition = 0;
|
moel@400
|
247 |
axis.PositionTier = 0;
|
moel@400
|
248 |
}
|
moel@397
|
249 |
axis.MajorGridlineStyle = LineStyle.None;
|
moel@397
|
250 |
axis.MinorGridlineStyle = LineStyle.None;
|
moel@397
|
251 |
}
|
moel@395
|
252 |
}
|
moel@398
|
253 |
|
moel@395
|
254 |
}
|
moel@395
|
255 |
|
moel@395
|
256 |
public void InvalidatePlot() {
|
moel@395
|
257 |
this.now = DateTime.UtcNow;
|
moel@400
|
258 |
|
moel@400
|
259 |
foreach (var pair in axes) {
|
moel@400
|
260 |
var axis = pair.Value;
|
moel@400
|
261 |
var type = pair.Key;
|
moel@400
|
262 |
if (type == SensorType.Temperature)
|
moel@400
|
263 |
axis.Unit = unitManager.TemperatureUnit == TemperatureUnit.Celsius ?
|
moel@400
|
264 |
"°C" : "°F";
|
moel@400
|
265 |
}
|
moel@400
|
266 |
|
moel@395
|
267 |
this.plot.InvalidatePlot(true);
|
moel@1
|
268 |
}
|
moel@1
|
269 |
|
moel@1
|
270 |
}
|
moel@1
|
271 |
}
|