babsevensix

Il blog di Alberto
posts - 94, comments - 81, trackbacks - 11

Un grafico con visifire un pò più elaborato

Oggi vi parlo di della realizzazione di un effetto alquanto carino fatto con visifire chart. L’idea è quella di mettere un pannello al lato del

tmp8D51

grafico in modo da poter attivare/disattivare le verie serie grafiche.

Purtroppo il codice non funziona proprio correttamente a causa di alcuni bug presenti in visifire (è ancora una beta) però l’idea c’è.

Ovviamente siamo in un’ottica M-V-VM.

Nella figura vediamo il risultato che vogliamo ottenere.

Abbiamo quindi un grafico, e un pannello con una serie di checkbox ognuno dei quali identifica una serie.

La prima classe quindi che ho realizzato è la ChartBehavior ovvero l’estensione al grafico stesso.

   1: static class ChartBehavior
   2:     {
   3:  
   4: #region Line chart view
   5:         public static LineChartViewModel GetLineChartView(Visifire.Charts.Chart chart)
   6:         {
   7:             return (LineChartViewModel)chart.GetValue(LineChartViewProperty);
   8:         }
   9:         public static void SetLineChartView(Visifire.Charts.Chart chart, LineChartViewModel value)
  10:         {
  11:             chart.SetValue(LineChartViewProperty, value);
  12:         }
  13:  
  14:         public static readonly DependencyProperty LineChartViewProperty =
  15:             DependencyProperty.RegisterAttached(
  16:             "LineChartView",
  17:             typeof(LineChartViewModel),
  18:             typeof(ChartBehavior),
  19:             new UIPropertyMetadata(OnAutoSelectedChanged));
  20: #endregion
  21:  
  22:         #region ListCategorie
  23:         public static readonly DependencyProperty ListCategorieProperty =
  24:             DependencyProperty.RegisterAttached(
  25:             "ListCategorie",
  26:             typeof(string),
  27:             typeof(ChartBehavior),
  28:             new UIPropertyMetadata(OnCategorieChanged));
  29:         public static string GetListCategorie(Visifire.Charts.Chart chart)
  30:         {
  31:             return (string)chart.GetValue(ListCategorieProperty);
  32:         }
  33:         public static void SetListCategorie(Visifire.Charts.Chart chart,
  34:             string value)
  35:         {
  36:             chart.SetValue(ListCategorieProperty, value);
  37:         }
  38:         static void OnCategorieChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
  39:         {
  40:             //Ottengo il sorgente
  41:             Visifire.Charts.Chart source = (sender as Visifire.Charts.Chart);
  42:             //Se nullo significa che la property non è attaccata al grafico
  43:             if (source == null) return;
  44:  
  45:             if (source.Tag == null)
  46:                 source.Tag = new DataChart();
  47:  
  48:             DataChart dati = (DataChart)source.Tag;
  49:             dati.Categorie = (string)e.NewValue;
  50:  
  51:             UpdateChart(source);
  52:         }
  53:         #endregion
  54:  
  55:         #region Panel CheckBox
  56:         public static readonly DependencyProperty PanelCheckBoxProperty =
  57:            DependencyProperty.RegisterAttached(
  58:            "PanelCheckBox",
  59:            typeof(FrameworkElement),
  60:            typeof(ChartBehavior),
  61:            new UIPropertyMetadata(OnPanelCheckBoxChanged));
  62:  
  63:         public static FrameworkElement GetPanelCheckBox(Visifire.Charts.Chart chart)
  64:         {
  65:             return (FrameworkElement)chart.GetValue(PanelCheckBoxProperty);
  66:         }
  67:         public static void SetPanelCheckBox(Visifire.Charts.Chart chart,
  68:             FrameworkElement value)
  69:         {
  70:             chart.SetValue(PanelCheckBoxProperty, value);
  71:         }
  72:         static void OnPanelCheckBoxChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
  73:         {
  74:             Visifire.Charts.Chart source = (sender as Visifire.Charts.Chart);
  75:             if (source == null) return;
  76:  
  77:             if (source.Tag == null)
  78:                 source.Tag = new DataChart();
  79:  
  80:             DataChart dati = (DataChart)source.Tag;
  81:             dati.PanelCheckBox = (FrameworkElement)e.NewValue;
  82:  
  83:             UpdateChart(source);
  84:         }
  85:         #endregion
  86:  
  87:         static void UpdateChart(Visifire.Charts.Chart source)
  88:         {
  89:             if (source == null) return;
  90:             if (source.Tag == null) return;
  91:             
  92:             DataChart dati = (DataChart)source.Tag;
  93:             if (dati.View == null) return;
  94:  
  95:             System.Windows.Controls.Panel pnl = null;
  96:             if (dati.PanelCheckBox != null )
  97:             {
  98:                 pnl = (System.Windows.Controls.Panel)dati.PanelCheckBox;
  99:                 if (pnl !=null)
 100:                     pnl.Children.Clear();
 101:  
 102:             }
 103:  
 104:             if (dati.Categorie != null)
 105:             {
 106:                 LineChartViewModel lcvm = dati.View;
 107:  
 108:                 lcvm.Categorie = dati.Categorie;
 109:  
 110:                 foreach (Visifire.Charts.DataSeries ds in lcvm.ChartSeries)
 111:                 {
 112:                     source.Series.Add(ds);
 113:                     foreach (Visifire.Charts.DataPoint dp in ds.DataPoints)
 114:                     {
 115:                         dp.Chart = source;
 116:                         dp.Bind();
 117:                     }
 118:                     if (pnl != null)
 119:                     {
 120:                         System.Windows.Controls.CheckBox cb = new System.Windows.Controls.CheckBox();
 121:                         cb.Content = ds.LegendText;
 122:                         ds.Tag = cb;
 123:                         cb.Tag = ds;
 124:                         cb.Checked += new RoutedEventHandler(cb_Checked);
 125:                         cb.Unchecked += new RoutedEventHandler(cb_Unchecked);
 126:                         cb.IsChecked = true;
 127:                         pnl.Children.Add(cb);
 128:                     }
 129:                     
 130:                 }
 131:             }
 132:         }
 133:  
 134:  
 135:         static void cb_Unchecked(object sender, RoutedEventArgs e)
 136:         {
 137:             System.Windows.Controls.CheckBox cb = (System.Windows.Controls.CheckBox)sender;
 138:             Visifire.Charts.DataSeries ds = (Visifire.Charts.DataSeries)cb.Tag;
 139:             ds.Visibility = Visibility.Collapsed;
 140:  
 141:         }
 142:  
 143:         static void cb_Checked(object sender, RoutedEventArgs e)
 144:         {
 145:             System.Windows.Controls.CheckBox cb = (System.Windows.Controls.CheckBox)sender;
 146:             Visifire.Charts.DataSeries ds = (Visifire.Charts.DataSeries)cb.Tag;
 147:             ds.Visibility = Visibility.Visible;
 148:         }
 149:  
 150:         static void OnAutoSelectedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
 151:         {
 152:             Visifire.Charts.Chart source = (sender as Visifire.Charts.Chart);
 153:             if (source == null) return;
 154:  
 155:             if (source.Tag == null)
 156:                 source.Tag = new DataChart();
 157:  
 158:             DataChart dati = (DataChart)source.Tag;
 159:             LineChartViewModel lcvm = (LineChartViewModel)e.NewValue;
 160:             dati.View = lcvm;
 161:  
 162:             UpdateChart(source);
 163:         }
 164:  
 165:        
 166:  
 167:         public class DataChart
 168:         {
 169:             public string Categorie
 170:             {
 171:                 get;set;
 172:             }
 173:  
 174:             public LineChartViewModel View
 175:             {
 176:                 get;
 177:                 set;
 178:             }
 179:  
 180:             public FrameworkElement PanelCheckBox
 181:             {
 182:                 get;
 183:                 set;
 184:             }
 185:         }
 186:     }

Il PanelCheckBoxProperty è il pannello dove il verranno messi i checkbox.

La routine UpdateChart invece si occupa di attivare/disattivare le varie serie sul grafico. In effetti il problema del codice sta proprio in questo punto, infatti attaccando/staccando le varie series il grafico non si aggiorna. L’unica soluzione è quella di ricreare la serie e di riaggiungere i vari datapoint. Attualmente questa ipotesi l’ho scartata, e attendo fiducioso la soluzione del problema.

La routine OnAutoSelectedChanged serve invece quando viene cambiato il viewmodel del grafico e qui entra in gioco la LineChartViewProperty. Questa proprietà infatti serve ad identificare il viewmodel corretto di visualizzazione del grafico. Infatti potremmo volerci attaccare differenti tipi di grafi, magari selezionati da un combobox (e in effetti questa era la mia esigenza iniziale).

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Collections;
   6: using Visifire.Charts;
   7:  
   8: using Repository;
   9: using Model;
  10: using System.Reflection;
  11: using System.ComponentModel;
  12:  
  13: namespace ViewModel
  14: {
  15:     public abstract class LineChartViewModel : ApplicationViewModel, INotifyPropertyChanged 
  16:     {
  17:         protected object objToChart;
  18:  
  19:         /// <summary>
  20:         /// 
  21:         /// </summary>
  22:         /// <param name="elementToChart">ad esempio pilota</param>
  23:         public LineChartViewModel(object elementToChart)
  24:         {
  25:             this.objToChart = elementToChart;
  26:         }
  27:  
  28:         public string Categorie { get; set; }
  29:  
  30:         public string[] GetArrayOfCategorie()
  31:         {
  32:             return this.Categorie.Split(',');
  33:         }
  34:  
  35:         public abstract DataSeries[] ChartSeries { get; }
  36:  
  37:         public abstract DataPoint[] GetDatas(string cat);
  38:  
  39:         public override abstract string ToString();
  40:         
  41:         public event PropertyChangedEventHandler PropertyChanged;
  42:  
  43:         public void NotifyPropertyChanged(String info)
  44:         {
  45:             if (PropertyChanged != null)
  46:             {
  47:                 PropertyChanged(this, new PropertyChangedEventArgs(info));
  48:             }
  49:         }
  50:     }
  51: }

La classe LineChartViewModel è il gestore del grafico stesso. Come potete notare è una classe astratta. Notate la property Categorie, che dovrà essere definita a designtime con una stringa separata da virgola per indicare ogni categoria. Il nome sui checkbox sarà determinato appunto da questa stringa.

 

Andiamo a vedere quindi l’implementazione della classe LineChartViewModel :

   1: namespace ViewModel.Scuderia
   2: {
   3:     public class LineChartViewModelPilota : LineChartViewModel
   4:     {
   5:         public LineChartViewModelPilota():this(null)
   6:         {
   7:  
   8:         }
   9:         public LineChartViewModelPilota(Pilota obj) : base(obj)
  10:         {
  11:         }
  12:  
  13:         public override DataPoint[] GetDatas(string cat)
  14:         {
  15:             if (this.objToChart.GetType() == typeof(Pilota))
  16:             {
  17:                 Pilota pil = (Pilota)this.objToChart;
  18:                 PilotaRepository pr = new PilotaRepository();
  19:                 var _ps = UnitOfWork.F1Organizer.Pilota.Where<Pilota>(p => p.DriverId == pil.DriverId);
  20:  
  21:                 List<Pilota> lst = _ps.ToList<Pilota>();
  22:  
  23:                 DataPoint[] values = new DataPoint[lst.Count];
  24:  
  25:                 PropertyInfo pi = this.objToChart.GetType().GetProperty("Driver" + cat.Trim());
  26:                 for (int i = 0; i < values.Count(); i++)
  27:                 {
  28:                     DataPoint dp = new DataPoint();
  29:                     try
  30:                     {
  31:                         dp.YValue = Double.Parse((string)pi.GetValue(lst[i], null));
  32:                         dp.XValue = lst[i].Fetchdate;
  33:                     }
  34:                     catch (Exception e)
  35:                     {
  36:                         dp.YValue = 0;
  37:                     }
  38:  
  39:                     values[i] = dp;
  40:                 }
  41:                 return values;
  42:             }
  43:             else throw new ArgumentException("Attenzioe, l'elemento non è di tipo pilota");
  44:         }
  45:  
  46:         public override string ToString()
  47:         {
  48:             return "LineChartViewModelPilota " + this.objToChart == null ? "null" : ((Pilota)this.objToChart).DriverName;
  49:         }
  50:  
  51:         public override Visifire.Charts.DataSeries[] ChartSeries
  52:         {
  53:             get
  54:             {
  55:                 string[] categorie = this.GetArrayOfCategorie();
  56:  
  57:                 DataSeries[] series = new DataSeries[categorie.Length];
  58:                 for (int i = 0; i < categorie.Length; i++)
  59:                 {
  60:                     string cat = categorie[i];
  61:                     Visifire.Charts.DataSeries ds = new Visifire.Charts.DataSeries();
  62:                     ds.LineStyle = LineStyles.Solid;
  63:                     ds.RenderAs = RenderAs.Line;
  64:                     ds.LabelText = cat;
  65:                     ds.LegendText = cat;
  66:  
  67:                     foreach (DataPoint dp in this.GetDatas(cat))
  68:                     {
  69:  
  70:                         ds.DataPoints.Add(dp);
  71:                     }
  72:                     series[i] = ds;
  73:                 }
  74:                 return series;
  75:             }
  76:         }
  77:     }
  78: }

Come potete notare da soli, la funzione GetDatas restituisce un’array di Datapoint, costruiti sfruttando la reflection.

Infatti nel model abbiamo, per quanto riguarda la classe Pilota, la property DriverExperience o DriverForm o DriverBraking. Ovviamente le categorie si dovranno chiamare Experience, Form o Braking.

Questo è comunque un codice d’esempio…

La funzione ChartSeries si occupa invece di ritornare la lista delle serie, determinati appunto dalla lista delle categorie.

Spero vi sia piaciuto l’esempio e l’idea anche se attualmente non completamente funzionante.

Print | posted on sabato 9 gennaio 2010 21:05 | Filed Under [ Codice.NET ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET