Pages

Sunday 23 May 2010

Pivot API for .Net

Firstly, I don't know whether this is strictly an API, a wrapper or an object model for Pivot. So, shall we agree to gloss over the semantics and just say that it is a bunch of related classes that will allow you to easily create your Pivot collections in a .Net environment and then save them as .cxml files? OK? Good.

Once you've read through all this there are links to download the source code and binaries at the end of this post.

If you don't already know Microsoft Live Labs is in the process of releasing a pretty cool piece of data "analysis" called Pivot. At the time of writing this you need to download their browser to experience the 'goodness' but by summer 2010 we've been assured a Silverlight plugin.

Let's get another thing straight: you could just go write your own xml files for this; after all, that is the product we ultimately need to create. However, why not let someone else (me) do the hard work so that all you need to do is just new-up some classes?

Finally, I need to assume that you have a reasonable understanding of the architecture and components involved in the Pivot application. You can get all you need from here.

The School Class Collection


The Pivot API contains very little documentation or comments. This is because, I hope, the classes and methods document themselves with their names. So, the best way I could think to demonstrate the behaviour and usage was through a worked example.

For this example let's assume that I have a pre-populated collection of Pupil objects (IList<Pupil>) that represents the raw data that I wish to create a Pivot Collection from.

class Pupil
{
  public long ID { get; set; }
  public string Name { get; set; }
  public DateTime DateOfBirth { get; set; }
  public string Class { get; set; }
  public string ReportCard { get; set; }
  public string Description { get; set; }
  public Pupil BestFriend { get; set; }
}

Our starting object will be an instance of the PivotCollection class. You'll need to supply this with a name, a version and a reference to an already created Deep Zoom Collection. I will not cover how to perform the latter but there's plenty of information out there to help you with this. You can also, optionally, add some copyright information to the collection.

var collection = new PivotCollection("School Collection", "schoolCollection.dzc", "1.0")
{
  Copyright = new CollectionCopyright { Name = "Chris Arnold", Href = "http://goodcoffeegoodcode.blogspot.com/" }
};

Next we have to define what information (Facets) the items in our collection will expose. The API defines a number of different Facet Category classes that encapsulate some inherent functionality.

collection.FacetCategories.Add(new StringFacetCategory("Class"));
collection.FacetCategories.Add(new DateTimeFacetCategory("Date of Birth"));
collection.FacetCategories.Add(new LongStringFacetCategory("Report Card"));
collection.FacetCategories.Add(new LinkFacetCategory("Best Friend"));

You could also encapsulate all of this into your own class to make your code more readable:

class SchoolPivotCollection : PivotCollection
{
  public SchoolPivotCollection()
  : base("School Collection", "schoolCollection.dzc", "1.0")
  {
    Copyright = new CollectionCopyright { Name = "Chris Arnold", Href = "http://goodcoffeegoodcode.blogspot.com/" };

    FacetCategories.Add(new DateTimeFacetCategory("Date of Birth"));
    FacetCategories.Add(new LongStringFacetCategory("Report Card"));
    FacetCategories.Add(new LinkFacetCategory("Best Friend"));
  }
}

We've now set up all of the pre-requisites for our collection. Time to start populating it. To do this we just need to iterate over our collection of Pupils, create a Pivot Item for each one and add it to the Pivot collection...

var pupils = GetPupils();

  foreach (var pupil in pupils)
    AddPupilToCollection(pupil, collection);

void AddPupilToCollection(Pupil pupil, PivotCollection collection)
{
  string PUPIL_URL = "http://www.myschool.com/pupils?id={0}";
  string PUPIL_COLLECTION_URL = "http://www.myschool.com/collection/pupil_{0}.cxml";

  var item = new Item()
  {
    Name = pupil.Name,
    Href = string.Format(PUPIL_URL, pupil.ID),
    Description = pupil.Description,
    Img = "#" + imageCounter,   /* This assumes that the order of the pupils is the same as their images in the deep zoom collection  */
    Id = pupil.ID
  };

  item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Class"], pupil.Class));
  item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Date of Birth"], pupil.DateOfBirth.ToShortDateString()));
  item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Report Card"], pupil.ReportCard));

  var facet = FacetFactory.Create(collection.FacetCategories["Best Friend"], string.Format(PUPIL_COLLECTION_URL, pupil.BestFriend.ID)) as LinkFacet;
  facet.Name = pupil.BestFriend.Name;

  collection.Items.Add(item);

  imageCounter++;
}

The final step in the process is to save the collection to an xml document. This is handled using the classes in the PivotAPI.XmlWriter namespace...

using (var writer = new XmlTextWriter("pupils.cxml", System.Text.Encoding.Default) { Formatting = Formatting.Indented })
{
  var collectionWriter = new PivotAPI.XmlWriters.PivotCollectionXmlWriter(writer, collection);

  collectionWriter.Write();
}

And that's the only client code you'll need to create a collection document (.cxml).

Here's the complete code example. Create a new console application, reference the PivotAPI library and replace the Program.cs with the following...

using System;
using System.Collections.Generic;
using System.Xml;

using PivotAPI;

namespace BlogPivotExample
{
    class Program
    {
        static int imageCounter;

        static void Main()
        {
            var collection = new SchoolPivotCollection();

            var pupils = GetPupils();

            foreach (var pupil in pupils)
                AddPupil(pupil, collection);

            using (var writer = new XmlTextWriter("pupils.cxml", System.Text.Encoding.Default) { Formatting = Formatting.Indented })
            {
                var collectionWriter = new PivotAPI.XmlWriters.PivotCollectionXmlWriter(writer, collection);

                collectionWriter.Write();
            }
        }

        static void AddPupil(Pupil pupil, PivotCollection collection)
        {
            string PUPIL_URL = "http://www.myschool.com/pupils?id={0}";
            string PUPIL_COLLECTION_URL = "http://www.myschool.com/collection/pupil_{0}.cxml";

            var item = new Item()
            {
                Name = pupil.Name,
                Href = string.Format(PUPIL_URL, pupil.ID),
                Description = pupil.Description,
                Img = "#" + imageCounter,   /* This assumes that the order of the pupils is the same as their images in the deep zoom collection  */
                Id = pupil.ID
            };

            item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Class"], pupil.Class));
            item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Date of Birth"], pupil.DateOfBirth.ToShortDateString()));
            item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Report Card"], pupil.ReportCard));

            var facet = FacetFactory.Create(collection.FacetCategories["Best Friend"], string.Format(PUPIL_COLLECTION_URL, pupil.BestFriend.ID)) as LinkFacet;
            facet.Name = pupil.BestFriend.Name;

            collection.Items.Add(item);

            imageCounter++;
        }
        static IList GetPupils()
        {
            var pupils = new List();

            //TODO: this is just a test pupil. You'll have to write your own routine if you want more data than this!
            var testPupil = new Pupil()
            {
                Name = "Chris Arnold",
                ID = 34873,
                Class = "Upper 5A",
                Description = "Lovely child",
                DateOfBirth = DateTime.Parse("1/1/1973"),
                ReportCard = "Chris has worked very hard this year. Well done!"
            };

            pupils.Add(testPupil);

            return pupils;
        }
    }
}

Now create 2 new classes in you application...

using System;
using PivotAPI;

namespace BlogPivotExample
{
    class SchoolPivotCollection : PivotCollection
    {
        public SchoolPivotCollection()
            : base("School Collection", "schoolCollection.dzc", "1.0")
        {
            Copyright = new CollectionCopyright { Name = "Chris Arnold", Href = "http://goodcoffeegoodcode.blogspot.com/" };

            FacetCategories.Add(new DateTimeFacetCategory("Date of Birth"));
            FacetCategories.Add(new LongStringFacetCategory("Report Card"));
            FacetCategories.Add(new LinkFacetCategory("Best Friend"));
        }
    }
}
using System;

namespace BlogPivotExample
{
    internal class Pupil
    {
        public long ID { get; set; }
        public string Name { get; set; }
        public DateTime DateOfBirth { get; set; }
        public string Class { get; set; }
        public string ReportCard { get; set; }
        public string Description { get; set; }
        public Pupil BestFriend { get; set; }
    }
}

Caveats


  • The API uses LINQ and so you'll need version 3.5 or 4.0 of the .Net framework.
  • The API is v1.0.0 and is not complete. Missing functions include the Supplement file, BrandImage, AdditionalSearchText, Icon. Also, some of the extensions have not been implemented yet e.g. DateRange, SortOrder and SortValue. These will all follow very quickly.

Tweet Pivot


Just to demonstrate that this isn't just all smoke and mirrors I wrote a website called Tweet Pivot. This utilises the Pivot API to get Twitter data and create dynamic collections. This is also where I've hosted the API for convenience.

So, if you like Tweet Pivot or the Pivot API please re-tweet them! Thanks.

Download Pivot API Source Code and Binaries

9 comments:

  1. Hi Chris,

    I am trying to figure out if Pivot makes sense for Business Intelligence. My problem is the impossibility (since each item has only one occurrence) to slice and dice through the data. Maybe it is possible to solve the problem by regenerating the cxml file dynamically.

    Best

    fabio.annovazzi@gmail.com

    ReplyDelete
  2. Wow! This is was really helpful.

    To make it work I had to change NumberFacet.cs:
    public override string ToString()
    {
    return Value.ToString(CultureInfo.InvariantCulture);
    }

    And DateTimeFacet.cs:
    public override string ToString()
    {
    return Value.ToString("s");
    }

    ReplyDelete
  3. Having written similar code recently, I noticed that the viewer is intolerant of empty elements and attributes, so you'll probably need to modify the writers. E.g.,

    if(!string.IsNullOrEmpty(item.Description)) Writer.WriteElementString("Description", item.Description);

    ReplyDelete
  4. Where does it write the cxml file to? i do not understand.

    ReplyDelete
  5. Hi hellocole2,

    Once we have build the collection we utilise 2 other classes in order to save the data to an xml file. In the Main() method you can see that we new-up an XmlTextWriter object and pass in the filename to the constructor (I have omitted the full path for brevity). Once we have this writer we can then new up a PivotCollectionXmlWriter object which will correctly serialize the collection to the writer. Hope that helps?

    Chris

    ReplyDelete
  6. thanks so much. next question. :)
    the cxml i get outputed from your code starts with



    encoding="Windows-1252" is breaking my pivot app.

    if i modify the file and change it to
    it works.

    is there a way for me to make this change with opening up your source and manually changing it?

    thanks!!!

    you rawk!!!

    ReplyDelete
  7. sorry. my xml was removed in the above post. encoding="utf-8 works. encoding="Windows-1252" does not. can i modify this? from the consol app? or i do i need to open up the src? thanks cp

    ReplyDelete
  8. sorry i figured it out. thanks. and sorry for all the posts.

    ReplyDelete