moel@158: /* moel@1: moel@344: This Source Code Form is subject to the terms of the Mozilla Public moel@344: License, v. 2.0. If a copy of the MPL was not distributed with this moel@344: file, You can obtain one at http://mozilla.org/MPL/2.0/. moel@1: moel@395: Copyright (C) 2009-2013 Michael Möller moel@344: moel@1: */ moel@1: moel@1: using System; moel@1: using System.Collections.Generic; moel@1: using System.Drawing; moel@395: using System.Linq; moel@1: using System.Windows.Forms; moel@1: using OpenHardwareMonitor.Hardware; moel@395: using OxyPlot; moel@395: using OxyPlot.Axes; moel@395: using OxyPlot.WindowsForms; moel@395: using OxyPlot.Series; moel@395: using OpenHardwareMonitor.Collections; moel@1: moel@1: namespace OpenHardwareMonitor.GUI { moel@158: public class PlotPanel : UserControl { moel@1: moel@400: private readonly PersistentSettings settings; moel@400: private readonly UnitManager unitManager; moel@326: moel@395: private readonly Plot plot; moel@395: private readonly PlotModel model; moel@395: private readonly TimeSpanAxis timeAxis = new TimeSpanAxis(); moel@395: private readonly SortedDictionary axes = moel@395: new SortedDictionary(); moel@395: moel@397: private UserOption stackedAxes; moel@397: moel@1: private DateTime now; moel@326: moel@400: public PlotPanel(PersistentSettings settings, UnitManager unitManager) { moel@326: this.settings = settings; moel@400: this.unitManager = unitManager; moel@400: moel@395: this.model = CreatePlotModel(); moel@326: moel@395: this.plot = new Plot(); moel@395: this.plot.Dock = DockStyle.Fill; moel@395: this.plot.Model = model; moel@395: this.plot.BackColor = Color.White; moel@397: this.plot.ContextMenu = CreateMenu(); moel@397: moel@397: UpdateAxesPosition(); moel@1: moel@395: this.SuspendLayout(); moel@395: this.Controls.Add(plot); moel@395: this.ResumeLayout(true); moel@1: } moel@1: moel@395: public void SetCurrentSettings() { moel@395: settings.SetValue("plotPanel.MinTimeSpan", (float)timeAxis.ViewMinimum); moel@395: settings.SetValue("plotPanel.MaxTimeSpan", (float)timeAxis.ViewMaximum); moel@395: moel@395: foreach (var axis in axes.Values) { moel@395: settings.SetValue("plotPanel.Min" + axis.Key, (float)axis.ViewMinimum); moel@395: settings.SetValue("plotPanel.Max" + axis.Key, (float)axis.ViewMaximum); moel@395: } moel@395: } moel@395: moel@397: private ContextMenu CreateMenu() { moel@397: ContextMenu menu = new ContextMenu(); moel@397: moel@397: MenuItem stackedAxesMenuItem = new MenuItem("Stacked Axes"); moel@397: stackedAxes = new UserOption("stackedAxes", true, moel@397: stackedAxesMenuItem, settings); moel@397: stackedAxes.Changed += (sender, e) => { moel@397: UpdateAxesPosition(); moel@397: InvalidatePlot(); moel@397: }; moel@397: menu.MenuItems.Add(stackedAxesMenuItem); moel@397: moel@395: MenuItem timeWindow = new MenuItem("Time Window"); moel@395: MenuItem[] timeWindowMenuItems = moel@395: { new MenuItem("Auto", moel@395: (s, e) => { timeAxis.Zoom(0, double.NaN); InvalidatePlot(); }), moel@395: new MenuItem("5 min", moel@395: (s, e) => { timeAxis.Zoom(0, 5 * 60); InvalidatePlot(); }), moel@395: new MenuItem("10 min", moel@395: (s, e) => { timeAxis.Zoom(0, 10 * 60); InvalidatePlot(); }), moel@395: new MenuItem("20 min", moel@395: (s, e) => { timeAxis.Zoom(0, 20 * 60); InvalidatePlot(); }), moel@395: new MenuItem("30 min", moel@395: (s, e) => { timeAxis.Zoom(0, 30 * 60); InvalidatePlot(); }), moel@395: new MenuItem("45 min", moel@395: (s, e) => { timeAxis.Zoom(0, 45 * 60); InvalidatePlot(); }), moel@395: new MenuItem("1 h", moel@395: (s, e) => { timeAxis.Zoom(0, 60 * 60); InvalidatePlot(); }), moel@395: new MenuItem("1.5 h", moel@395: (s, e) => { timeAxis.Zoom(0, 1.5 * 60 * 60); InvalidatePlot(); }), moel@395: new MenuItem("2 h", moel@395: (s, e) => { timeAxis.Zoom(0, 2 * 60 * 60); InvalidatePlot(); }), moel@395: new MenuItem("3 h", moel@395: (s, e) => { timeAxis.Zoom(0, 3 * 60 * 60); InvalidatePlot(); }), moel@395: new MenuItem("6 h", moel@395: (s, e) => { timeAxis.Zoom(0, 6 * 60 * 60); InvalidatePlot(); }), moel@395: new MenuItem("12 h", moel@395: (s, e) => { timeAxis.Zoom(0, 12 * 60 * 60); InvalidatePlot(); }), moel@395: new MenuItem("24 h", moel@395: (s, e) => { timeAxis.Zoom(0, 24 * 60 * 60); InvalidatePlot(); }) }; moel@326: foreach (MenuItem mi in timeWindowMenuItems) moel@326: timeWindow.MenuItems.Add(mi); moel@397: menu.MenuItems.Add(timeWindow); moel@326: moel@397: return menu; moel@326: } moel@326: moel@395: private PlotModel CreatePlotModel() { moel@1: moel@395: timeAxis.Position = AxisPosition.Bottom; moel@395: timeAxis.MajorGridlineStyle = LineStyle.Solid; moel@395: timeAxis.MajorGridlineThickness = 1; moel@395: timeAxis.MajorGridlineColor = OxyColor.FromRgb(192, 192, 192); moel@395: timeAxis.MinorGridlineStyle = LineStyle.Solid; moel@395: timeAxis.MinorGridlineThickness = 1; moel@395: timeAxis.MinorGridlineColor = OxyColor.FromRgb(232, 232, 232); moel@395: timeAxis.StartPosition = 1; moel@395: timeAxis.EndPosition = 0; moel@395: timeAxis.MinimumPadding = 0; moel@395: timeAxis.MaximumPadding = 0; moel@395: timeAxis.AbsoluteMinimum = 0; moel@395: timeAxis.Minimum = 0; moel@395: timeAxis.AbsoluteMaximum = 24 * 60 * 60; moel@395: timeAxis.Zoom( moel@395: settings.GetValue("plotPanel.MinTimeSpan", 0.0f), moel@395: settings.GetValue("plotPanel.MaxTimeSpan", 10.0f * 60)); moel@395: timeAxis.StringFormat = "h:mm"; moel@395: moel@395: var units = new Dictionary(); moel@395: units.Add(SensorType.Voltage, "V"); moel@395: units.Add(SensorType.Clock, "MHz"); moel@395: units.Add(SensorType.Temperature, "°C"); moel@395: units.Add(SensorType.Load, "%"); moel@395: units.Add(SensorType.Fan, "RPM"); moel@395: units.Add(SensorType.Flow, "L/h"); moel@395: units.Add(SensorType.Control, "%"); moel@395: units.Add(SensorType.Level, "%"); moel@395: units.Add(SensorType.Factor, "1"); moel@395: units.Add(SensorType.Power, "W"); moel@395: units.Add(SensorType.Data, "GB"); moel@395: moel@395: foreach (SensorType type in Enum.GetValues(typeof(SensorType))) { moel@395: var axis = new LinearAxis(); moel@395: axis.Position = AxisPosition.Left; moel@395: axis.MajorGridlineStyle = LineStyle.Solid; moel@395: axis.MajorGridlineThickness = 1; moel@395: axis.MajorGridlineColor = timeAxis.MajorGridlineColor; moel@395: axis.MinorGridlineStyle = LineStyle.Solid; moel@395: axis.MinorGridlineThickness = 1; moel@395: axis.MinorGridlineColor = timeAxis.MinorGridlineColor; moel@397: axis.AxislineStyle = LineStyle.Solid; moel@395: axis.Title = type.ToString(); moel@395: axis.Key = type.ToString(); moel@395: moel@395: axis.Zoom( moel@395: settings.GetValue("plotPanel.Min" + axis.Key, float.NaN), moel@395: settings.GetValue("plotPanel.Max" + axis.Key, float.NaN)); moel@395: moel@395: if (units.ContainsKey(type)) moel@395: axis.Unit = units[type]; moel@395: axes.Add(type, axis); moel@1: } moel@1: moel@395: var model = new PlotModel(); moel@395: model.Axes.Add(timeAxis); moel@395: foreach (var axis in axes.Values) moel@395: model.Axes.Add(axis); moel@395: model.PlotMargins = new OxyThickness(0); moel@395: model.IsLegendVisible = false; moel@158: moel@395: return model; moel@1: } moel@1: moel@158: public void SetSensors(List sensors, moel@158: IDictionary colors) { moel@395: this.model.Series.Clear(); moel@395: moel@395: ListSet types = new ListSet(); moel@395: moel@395: foreach (ISensor sensor in sensors) { moel@395: var series = new LineSeries(); moel@400: if (sensor.SensorType == SensorType.Temperature) { moel@400: series.ItemsSource = sensor.Values.Select(value => new DataPoint { moel@400: X = (now - value.Time).TotalSeconds, moel@400: Y = unitManager.TemperatureUnit == TemperatureUnit.Celsius ? moel@400: value.Value : UnitManager.CelsiusToFahrenheit(value.Value).Value moel@400: }); moel@400: } else { moel@400: series.ItemsSource = sensor.Values.Select(value => new DataPoint { moel@400: X = (now - value.Time).TotalSeconds, Y = value.Value moel@400: }); moel@400: } moel@395: series.Color = colors[sensor].ToOxyColor(); moel@395: series.StrokeThickness = 1; moel@395: series.YAxisKey = axes[sensor.SensorType].Key; moel@395: series.Title = sensor.Hardware.Name + " " + sensor.Name; moel@395: this.model.Series.Add(series); moel@395: moel@395: types.Add(sensor.SensorType); moel@395: } moel@395: moel@395: foreach (var pair in axes.Reverse()) { moel@395: var axis = pair.Value; moel@395: var type = pair.Key; moel@400: axis.IsAxisVisible = types.Contains(type); moel@397: } moel@397: moel@397: UpdateAxesPosition(); moel@397: InvalidatePlot(); moel@397: } moel@397: moel@397: private void UpdateAxesPosition() { moel@397: if (stackedAxes.Value) { moel@397: var count = axes.Values.Count(axis => axis.IsAxisVisible); moel@397: var start = 0.0; moel@397: foreach (var pair in axes.Reverse()) { moel@397: var axis = pair.Value; moel@397: var type = pair.Key; moel@397: axis.StartPosition = start; moel@397: var delta = axis.IsAxisVisible ? 1.0 / count : 0; moel@397: start += delta; moel@397: axis.EndPosition = start; moel@397: axis.PositionTier = 0; moel@397: axis.MajorGridlineStyle = LineStyle.Solid; moel@397: axis.MinorGridlineStyle = LineStyle.Solid; moel@397: } moel@397: } else { moel@397: var tier = 0; moel@397: foreach (var pair in axes.Reverse()) { moel@397: var axis = pair.Value; moel@397: var type = pair.Key; moel@400: if (axis.IsAxisVisible) { moel@400: axis.StartPosition = 0; moel@400: axis.EndPosition = 1; moel@400: axis.PositionTier = tier; moel@397: tier++; moel@400: } else { moel@400: axis.StartPosition = 0; moel@400: axis.EndPosition = 0; moel@400: axis.PositionTier = 0; moel@400: } moel@397: axis.MajorGridlineStyle = LineStyle.None; moel@397: axis.MinorGridlineStyle = LineStyle.None; moel@397: } moel@395: } moel@398: moel@395: } moel@395: moel@395: public void InvalidatePlot() { moel@395: this.now = DateTime.UtcNow; moel@400: moel@400: foreach (var pair in axes) { moel@400: var axis = pair.Value; moel@400: var type = pair.Key; moel@400: if (type == SensorType.Temperature) moel@400: axis.Unit = unitManager.TemperatureUnit == TemperatureUnit.Celsius ? moel@400: "°C" : "°F"; moel@400: } moel@400: moel@395: this.plot.InvalidatePlot(true); moel@1: } moel@1: moel@1: } moel@1: }