outcoldman
outcoldman Denis Gladkikh

MS Chart: пример Perfomance Counters c 3D вращением

Chart, WinForms, .NET, and C#

На одной из встреч User Group я рассказывал доклад «Создание диаграмм при помощи MS Chart», для которого сделал пример WinForms приложения performance counter, которое выводит информацию о 3х показателях системы в реальном времени: загруженность процессора, использование физической и виртуальной памяти, и еще имеет возможность 3D вращения. Не знаю, пригодиться ли кому-нибудь данный пример, но все же решил рассказать о нем.

Сначала пару слов о MS Chart: это дополнение к Microsoft .NET Framework 3.5 SP1, которое позволяет создавать диаграммы в приложениях WinForms(что дает нам и WPF) и WebForms. В будущем это будет частью Microsoft .NET Framework 4. MS Chart был создан не с нуля, а позаимствован из библиотеки Dundas (источник).

Для того чтобы начать работу с MS Chart – необходимо установить компоненты MS Chart Controls. Скачать их можно по следующим ссылкам:

Хочется еще сказать, что примеры намного богаче, чем документация по данным контролам, потому лучше сразу смотреть примеры, чем читать саму документацию.

Итак, мы установили MS Chart. Создаем новый WinForms проект. Кладем на него контрол Chart. Выставляем у свойства объекта Area Area3DStyle.Enabled3D в true. Кладем 3 набора Series с типами Spline (это один из типов графиков, который позволяет класть несколько графиков на один Area). В итогге у нас есть некоторое представление, которое мы можем заполнять информацией. Дальше можно подстроить вид графиков, цвета и многое другое, что я и сделал.

Дальше нам необходимо создать 3 PerfomanceCounter объекта из пространства имен System.Diagnostics:

#region Counters
 
private PerformanceCounter pcCpu;
private PerformanceCounter pcRam;
private PerformanceCounter pcPage;
 
/// <summary>
/// Инициализация счетчиков
/// </summary>
private void InitCounters()
{
  pcCpu = new PerformanceCounter("Processor", "% Processor Time", "_Total", Environment.MachineName);
  pcRam = new PerformanceCounter("Memory", "% Committed Bytes In Use", String.Empty, Environment.MachineName);
  pcPage = new PerformanceCounter("Paging File", "% Usage", "_Total", Environment.MachineName);
}
 
/// <summary>
/// Освобождаем ресурсы - PerformanceCounters
/// </summary>
private void DisposeCounters()
{
  try
  {
   if (pcCpu != null)
    pcCpu.Dispose(); 
   if (pcRam != null)
    pcRam.Dispose(); 
   if (pcPage != null)
    pcPage.Dispose(); 
  }
  finally
  {
   PerformanceCounter.CloseSharedResources();
  }
}
 
#endregion

Метод InitCounters вызываем в конструкторе формы. А метод DisposeCounters в методе Dispose. Далее описываем работу потока, который будет добавлять данные в график каждые 0.1 секунды. Данный подход есть в одном из примеров, поставляемых с MS Chart, я взял его оттуда.

#region Thread Sample
 
private Thread addDataRunner;
public delegate void AddDataDelegate();
public AddDataDelegate addDataDel;
 
/// <summary>
/// Инициализируем поток, который будет добавлять нам данные в график
/// </summary>
/// <param name="e"></param>
protected override void OnLoad(EventArgs e)
{
  base.OnLoad(e);
  Application.DoEvents();
 
  // create the Adding Data Thread but do not start until start button clicked
  ThreadStart addDataThreadStart = new ThreadStart(AddDataThreadLoop);
addDataRunner = new Thread(addDataThreadStart);
 
  // create a delegate for adding data
  addDataDel += new AddDataDelegate(AddData);
 
  // start worker threads.
  if (addDataRunner.IsAlive == true)
  {
   addDataRunner.Resume();
  }
  else
  {
   addDataRunner.Start();
  }
}
 
/// <summary>
/// Вызываем работу подсчета каждые 0.1 секунды
/// </summary>
private void AddDataThreadLoop()
{
  while (true)
  {
   if (!chartPerfomance.IsHandleCreated)
    return;
 
   chartPerfomance.Invoke(addDataDel);
 
   Thread.Sleep(100);
  }
}
 
/// <summary>
/// Заканчиваем работу потока
/// </summary>
private void DisposeThread()
{
  if ((addDataRunner.ThreadState & ThreadState.Suspended) == ThreadState.Suspended)
  {
   addDataRunner.Resume();
  }
  addDataRunner.Abort();
}
 
#endregion

Метод DisposeThread необходимо вызвать в методе Dispose формы для окончания работы потока. Дальше описываем методы для добавления точек в графики:

#region Add Data 
 
/// <summary>
/// Метод добавляет данные в три графика на данный период времени
/// </summary>
public void AddData()
{
  DateTime timeStamp = DateTime.Now;
 
  AddNewPoint(timeStamp, chartPerfomance.Series[0], pcCpu.NextValue());
  AddNewPoint(timeStamp, chartPerfomance.Series[1], pcRam.NextValue());
  AddNewPoint(timeStamp, chartPerfomance.Series[2], pcPage.NextValue());
}
 
/// <summary>
/// Добавление точки (timeStamp, nexVal) в график ptSeries
/// </summary>
/// <param name="timeStamp"></param>
/// <param name="ptSeries"></param>
/// <param name="nexVal"></param>
public void AddNewPoint( DateTime timeStamp, Series ptSeries, float nexVal )
{
  ptSeries.Points.AddXY(timeStamp.ToOADate(), nexVal);
 
  double removeBefore = timeStamp.AddSeconds( (double)(9) * ( -1 )).ToOADate();
 
  while ( ptSeries.Points[0].XValue < removeBefore )
  {
   ptSeries.Points.RemoveAt(0);
  }
 
  chartPerfomance.ChartAreas[0].AxisX.Minimum = ptSeries.Points[0].XValue;
  chartPerfomance.ChartAreas[0].AxisX.Maximum = DateTime.FromOADate(ptSeries.Points[0].XValue).AddSeconds(10).ToOADate();
 
  chartPerfomance.Invalidate();
}

Теперь добавляем логику вращения:

#region Mouse Events
 
/// <summary>
/// Движение мыши 
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void chartPerfomance_MouseMove(object sender, MouseEventArgs e)
{
  if (e.Button == MouseButtons.Left)
  {
   int x = savedRotation - (savedLocation.X - e.X);
   int y = savedInclination - (savedLocation.Y - e.Y);
   chartPerfomance.ChartAreas[0].Area3DStyle.Rotation = Math.Max(Math.Min(x, 180), -180); 
   chartPerfomance.ChartAreas[0].Area3DStyle.Inclination = Math.Max(Math.Min(y, 90), -90);
}
}
 
/// <summary>
/// Нажатие на кнопку мыши
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void chartPerfomance_MouseDown(object sender, MouseEventArgs e)
{
  if (e.Button == MouseButtons.Left)
  {
   Cursor = Cursors.NoMove2D;
   savedLocation = e.Location;
   savedRotation = chartPerfomance.ChartAreas[0].Area3DStyle.Rotation;
   savedInclination = chartPerfomance.ChartAreas[0].Area3DStyle.Inclination;
}
}
 
/// <summary>
/// Кнопку отпустили
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void chartPerfomance_MouseUp(object sender, MouseEventArgs e)
{
  if (e.Button == MouseButtons.Left)
  {
   Cursor = Cursors.Default;
  }
}
 
private Point savedLocation;
private int savedRotation;
private int savedInclination;
 
#endregion

Угол по оси Y можно выставить только в пределах от -90 до 90 градусов, потому полное 3d вращение сделать не удастся. Перевернуть график вверх ногами не получиться (правда сделать конечно можно, но не так просто). По оси X угол можно выставить от -180 до 180 градусов, потому там можно сделать полное вращение, выставляя на движение мыши свойству Rotation значение x % 360 - 360 * ((x / 180) % 2).

Все три метода нужно прицепить к объекту контрола Chart на события MouseMove, MouseDown и MouseUp соответственно.
В результате у нас должно получиться такой результат:

Вот такой простой пример работы с MS Chart. Хочу сказать, что это достаточно мощная библиотека, которая предоставляет возможность представления данных в виде графиков. И из бесплатных аналогов, наверное, лучшая. Бесплатных аналогов я, правда, знаю всего один - ZedGraph, и до появления MS Chart им только и пользовался. Не скажу что MS Chart принес больше функциональности, но он имеет возможность размещения умных подписей (чтобы они друг на друга не залазили), методов для вычисления финансовых формул (медианы и многое другое), отображения легенд ну и много другого.

По окончании хочу привести несколько ссылок, где можно еще почерпать информации о MS Chart:

Скачать пример: PerfomanceCounterChart.zip

Have feedback or questions? Looking for consultation?

My expertise: MongoDB, ElasticSearch, Splunk, and other databases. Docker, Kubernetes. Logging, Metrics. Performance, memory leaks.

Send me an email to public@denis.gladkikh.email.

The content on this site represents my own personal opinions and thoughts at the time of posting.

Content licensed under the Creative Commons CC BY 4.0.