Pivot Viewer : L’exploration de données SoSexy 1/2

Aujourd’hui tout le monde a forcément entendu parler de Silverlight, (le flash killer) de Microsoft. Il n’a cessé d’évoluer ces dernières années et en est aujourd’hui à sa version 5. Malgré la perte de vitesse de la techno, un composant mérite d’être sur notre blog : le pivot viewer. Autrefois exclu du SDK Silverlight, il en fait maintenant partie intégrante : http://www.silverlight.net/learn/data-networking/pivot-viewer ou http://www.microsoft.com/silverlight/pivotviewer/.

Il permet l’affichage et l’interaction avec d’assez grandes quantités de données (environ 5000 objets) avec de nombreuses fonctionnalités (tri, recherche, zoom, détails). Il peut être utilisé dans différentes optiques :

  • business intelligence
  • navigation intranet / exploration de documents
  • galerie d’images

Toutefois ce composant Silverlight a été implémenté en HTML5, c’est par ici

Qu’en est-il ?

Aujourd’hui sa version 2.0 change la donne et facilite le travail pour le développeur, mais fera l’objet du deuxième article. En attendant nous allons nous plonger dans la complexité de la V1 qui nécessitait la connaissance de :

  • DeepZoom : composant permettant de zoomer sur des images de haute qualité avec une très bonne fluidité. Ce composant utilise des formats d’images particuliers.
  • Cxml : format de fichier Xml contenant toutes les informations à partir desquelles le contrôle Silverlight construit sa liste d’objets
  • HttpHandler / HttpModule : Classe implémentant IHttpHandler / IHttpModule compilée, définie dans le web.config et permettant d’exécuter du code à l’entrée d’une requête (par filtrage).

Type de collections de données

De ces types de collections en découle une complexité liée à la manière de générer le flux de données cxml et les images pour le rendu.

                Simple             Liées Just in Time
 
Difficulté : facileTaille : 3000 items Difficulté : moyenneTaille : Limitée par le stockage Difficulté : dureTaille : Illimitée
Données : statiques, rendus statiques contenus dans un .DZC Données : statiques mais chaque collection est liée à une autre (navigation), rendus statiques contenus dans un .DZC Données : dynamiques chargées à la volée sur requête, rendus dynamiques contenus dans un .DZC
Mode : statique   Mode : statique Mode : dynamique

Principe de fonctionnement

Ces deux modes ont le même but (renvoyer un flux cxml, xml et images). Toutefois le mode dynamique est le plus complexe mais évite la génération de données à intervalle de temps régulier (hebdomadaire, mensuel, etc…)

Mode : statique Mode : dynamique
  1. Fichier cxml et DZC déjà générés côté serveur
  2. Requête http pour acquisition du fichier .cxml
  3. Une fois parsé, le contrôle demande pour chaque item un .DZC et des .DZI
  • Emprunte le chemin en vert
  1. Requête HTTP pour acquisition d’un fichier cxml
  2. HttpHandler catchant la requête et renvoyant un flux cxml à la demande
  3. Une fois parsé, le contrôle demande pour chaque item un .DZC et des .DZI
  4. HttpHandler catchant les extensions .DZC et .DZI pour génération à la volée
  • Emprunte le chemin vert en passant par les éléments rouges

Côté serveur – mode dynamique

Définition des handlers côté serveur

Implémentation des handlers / factory perso

Implémentation utilisant les objets fournis dans l’archive fournie plus bas.

namespace PivotViewer.Server.HttpHandlers
{
   public class CxmlHandler : IHttpHandler
   {
       public void ProcessRequest(HttpContext context)
       {
          PivotHttpHandlers.ServeCxml(context);
       }

       public bool IsReusable { get { return true; }}

      //Il en est de même pour les autres handlers DzcHandler, ImageTileHandler,
      //DziHandler, DeepZoomImageHandler. Seule le nom de la méthode change
}

Au démarrage on charge toutes les classes héritant de CollectionFactoryBase depuis le bin folder, on peut y rajouter les notres dans des assemblies différentes.

public static IEnumerable<CollectionFactoryBase> Find(string folderPath)
{
   var factories = new List<CollectionFactoryBase>();

   foreach (Type t in EnumerateTypesInAssemblies(folderPath))
   {
      if (t.IsSubclassOf(typeof(CollectionFactoryCollBase))
      || t.IsSubclassOf(typeof(CollectionFactoryCxmlBase)))
      {
         CollectionFactoryBase factory = (CollectionFactoryBase)Activator.CreateInstance(t);
         if (string.IsNullOrEmpty(factory.Name))
            factory.Name = t.Name;

         factories.Add(factory);
      }
   }
   return factories;
}

Exemple d’implémentation d’une factory (chargement simple de données depuis une base)

public class DbFactory : CollectionFactoryCollBase
{
   #region Constructor
   public DbFactory()
   {
      Name = "DbFactory";
      Summary = "Default factory to retrieve data from database";
   }
#endregion

#region Overrides of CollectionFactoryCollBase
public override Collection MakeCollection(CollectionRequestContext context)
{
   var collection = new Collection();
   var dbName = context.Query["db"];
   var dbCommand = context.Query["cmd"];
   var dbTable = context.Query["table"];

   var section = (NameValueCollection) ConfigurationManager.GetSection(dbTable);
   if (section == null)
      return collection;

   using (var sqlConn =
   new SqlConnection(ConfigurationManager.ConnectionStrings[dbName].ConnectionString))
   {
      sqlConn.Open();
      using (var sqlCommand = new SqlCommand(dbCommand))
      {
         sqlCommand.Connection = sqlConn;
         using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
         {
            using(var sqlDataSet = new DataSet())
            {
               sqlDataAdapter.Fill(sqlDataSet);
               var dataTable = sqlDataSet.Tables[0];
               int rowIndex = 1;

               foreach (DataRow row in dataTable.Rows)
               {
                  var rowData = new ReflectiveBusinessObject();
                  ItemImage image = null;

                  foreach (DataColumn column in dataTable.Columns)
                     rowData.Values.Add(column.ColumnName, row[column].ToString().Trim());

                  //Pour chaque élément, ajout d'un item à notre collection
                  collection.AddItem(rowIndex.ToString(), null, null,
                         image, FacetHelper.GetFacets(rowData.Values));
                  //Here you can insert <b>ItemImage</b> object from hard drive disk, memory stream

               rowIndex++;
            }
         }
      }
   }
}
return collection;
}
#endregion
}

Une fois notre collection construite il suffit de la sérialiser en appelant la méthode ToCxml.

public void ServeCxml(HttpContext context, Collection collection, string collectionFileName)
{
   string collectionKey = collection.SetDynamicDzc(collectionFileName);
   _mCollectionCache.Add(collectionKey, collection);

   context.Response.ContentType = "text/xml";
   collection.ToCxml(context.Response.Output);
}

NB : Microsoft avait mis à disposition à l’époque des assemblies pour gérer le Just in Time côté serveur, les classes PivotHttpHandlers ou CollectionFactoryBase dans l’exemple ci-dessus en font parti : Pivot JIT Sample

Côté Silverlight

Dans l’application maintenant, il suffit de charger le fichier CXML désiré, le composant se chargera tout seul de récupérer chaque rendu d’item (image) en fonction du niveau de zoom.

private void buttonLoadData_Click(object sender, RoutedEventArgs e)
{
pivotViewerMain.LoadCollection(<a href="http://localhost:52001/Pivot/Cxml/Stations.cxml">http://localhost:52001/Pivot/Cxml/Stations.cxml</a>, null);
}

Conclusion

Voici des exemples d’utilisation et de ce qu’il est possible de faire avec ce composant :
http://blogs.msdn.com/b/sublimaction/archive/2010/07/30/des-exemples-d-utilisation-de-microsoft-pivot-viewer.aspx

Un dernier qui s’applique au domaine médical :
http://www.physiotherapyexercises.com/Physio.aspx

Cette première version fait appel à des connaissances spécifiques ainsi que beaucoup de préparation côté serveur (développement des handlers, génération de fichiers cxml, génération des images). Et c’est bien là que cette version montre ses limites et freine à un développement efficient. C’est ce à quoi répond la dernière version, bien plus facile, bien plus personnalisable.

Nombre de vue : 130

COMMENTAIRES 2 commentaires

  1. […] l’article précédent, nous avons pu voir la complexité et la quantité de travail à fournir pour  profiter de […]

  2. Alex31 dit :

    Bonjour,
    Pouvez vous nous donnez le code source ?
    merci

AJOUTER UN COMMENTAIRE