300x250 AD TOP

Search This Blog

Pages

Paling Dilihat

Powered by Blogger.

Tuesday, June 5, 2012

XML

So you want to use XML in your application?


What should you use? there are XmlDocument, XDocument, XmlReader? XmlWriter? Help!


Lets make some order in this mess. 


What do you need to do? 


Are your XML files very big? use XmlReader/XmlWriter, but you'll have to write more code than the other options, think of it as reading a text file with a little help.


Are you used to XPath and pressed for time? you can use XmlDocument, it might make your life easy, just remember its impossible to serialize XmlDocuments.


Do you like LINQ? use XDocument/XElements, they are also serializable so its a good option if you're using WCF and need to pass XML elements.


Personally I prefer XDocument/XElement, I can serialize them when I need to, I'm used to LINQ queries so its very easy for me to query XML documents and it supports explicit casting so I don't need to deal with the whole primitive to string and back conversions.


You can find the demo project at:
https://github.com/drorgl/ForBlog/tree/master/XmlDemo


And you can't really look at these without doing some benchmarking, you may see it in the project code, but I'll just give you the highlights, XmlReader/XmlWriter are the fastest, then XDocument and last is XmlDocument.




I've included some extensions which helped me in the past with XML documents containing enums and byte arrays (or binary data):



/// <summary>
/// Converts XAttribute to enum
/// </summary>
/// <typeparam name="T">Enum type</typeparam>
/// <param name="xattribute">XAttribute object</param>
/// <param name="defaultValue">return this value if conversion fails</param>
/// <returns>defaultValur or converted enum value</returns>
public static T ToEnum<T>(this XAttribute xattribute, Enum defaultValue) where T : struct, IConvertible
{
    if (!typeof(T).IsEnum)
        throw new ArgumentException("T must be an enum");

    return (T)((xattribute == null || (string.IsNullOrEmpty(xattribute.Value))) ? defaultValue : Enum.Parse(typeof(T), xattribute.Value));
}

/// <summary>
/// Converts XElement to enum
/// </summary>
/// <typeparam name="T">Enum type</typeparam>
/// <param name="xelement">XElement object</param>
/// <param name="defaultValue">return this value if conversion fails</param>
/// <returns>defaultValur or converted enum value</returns>
public static T ToEnum<T>(this XElement xelement, Enum defaultValue) where T : struct, IConvertible
{
    if (!typeof(T).IsEnum)
        throw new ArgumentException("T must be an enum");

    return (T)((xelement == null || (string.IsNullOrEmpty(xelement.Value)))  ? defaultValue : Enum.Parse(typeof(T), xelement.Value));
}

/// <summary>
/// Converts XAttibute's value to byte array from base64 encoded string
/// </summary>
/// <param name="xattribute">XAttribute object</param>
/// <returns>byte array if successful, null if not</returns>
public static byte[] ToByteArray(this XAttribute xattribute)
{
    if (string.IsNullOrEmpty(xattribute.Value))
        return null;

    return Convert.FromBase64String(xattribute.Value);
}

/// <summary>
/// Converts XElement's value to byte array from base64 encoded string
/// </summary>
/// <param name="xelement">XElement object</param>
/// <returns>byte array if successful, null if not</returns>
public static byte[] ToByteArray(this XElement xelement)
{
    if (string.IsNullOrEmpty(xelement.Value))
        return null;

    return Convert.FromBase64String(xelement.Value);
}









Tags:

Sunday, June 3, 2012

ILSpy

Since RedGate started charging for .NET Reflector I've been looking for an alternative, I use it mostly to understand how a specific class is implemented, one example is the ConcurrentDictionary I've checked a few weeks ago but I don't use it in my daily tasks so it doesn't really justify purchasing it even if its "only" $70-$190.

Pretty quick I've found ILSpy, I know there are many features it doesn't have over reflector, but its enough for me. Give it a try if you're also looking for an alternative.





Tags:

Friday, June 1, 2012

Explicit Operators

So, you want to be able to cast your class into other classes, but you need to be able to control the casting process.


Good, we have just what the doctor ordered, explicit operators.


I did a quick demo project to explain how it works, I have a CSV file with names, numbers, dates and every time I order the records by a different column, which is a different type: string, int, DateTime.


parser.Rows.OrderBy(i => (string)i.Cell[0])



parser.Rows.OrderBy(i => (int?)i.Cell[1])



parser.Rows.OrderBy(i => (DateTime?)i.Cell[2])


And the class:


        /// <summary>
        /// Parsed Cell
        /// </summary>
        public class Cell
        {
            /// <summary>
            /// Column Number of cell
            /// </summary>
            public int ColumnNumber;

            /// <summary>
            /// Text value of cell
            /// </summary>
            public string Value;

            /// <summary>
            /// Explicit cast to int
            /// </summary>
            /// <param name="cell"></param>
            /// <returns></returns>
            public static explicit operator int(Cell cell)
            {
                return Convert.ToInt32(cell.Value);
            }
            
            /// <summary>
            /// Explicit cast to int?
            /// </summary>
            /// <param name="cell"></param>
            /// <returns></returns>
            public static explicit operator int?(Cell cell)
            {
                int retval;
                if (int.TryParse(cell.Value, out retval))
                    return retval;

                return null;
            }

            /// <summary>
            /// Explicit cast to string
            /// </summary>
            /// <param name="cell"></param>
            /// <returns></returns>
            public static explicit operator string(Cell cell)
            {
                return cell.Value;
            }

            /// <summary>
            /// Explicit cast to DateTime
            /// </summary>
            /// <param name="cell"></param>
            /// <returns></returns>
            public static explicit operator DateTime(Cell cell)
            {
                return DateTime.Parse(cell.Value);
            }

            /// <summary>
            /// Explicit cast to DateTime?
            /// </summary>
            /// <param name="cell"></param>
            /// <returns></returns>
            public static explicit operator DateTime?(Cell cell)
            {
                DateTime retval;
                if (DateTime.TryParse(cell.Value, out retval))
                    return retval;

                return null;
            }
        }


And for the demo project:
https://github.com/drorgl/ForBlog/tree/master/CSVParser

You can generate your own test data with this nice website:
http://www.generatedata.com/#generator


Tags:

Quick Open File for Visual Studio

Have you ever worked on a big solution? sometimes with many projects?

Going back and forth in the solution explorer could waste a lot of your time, if you're a bit tired just before getting a version out its even worse, not to mention getting you out of focus.

I'm a big fan of Quick Open File, I think you will too when you try it.

Just remember Ctrl-K O and type the file you're looking for.


http://visualstudiogallery.msdn.microsoft.com/3eb2f230-2728-4d5f-b448-4c0b64154da7


Update 2012-08-22: You can always use the Navigate To, or Ctrl+, key combination:


I've discovered this while looking for a similar solution in VS2012, its showing too much information, but its better than digging through the solution files and you can always type the extension.
Tags:

Tuples

So what are tuples?


Tuples are a generic data structures, it can contain 1 - 8 data items.


What are they used for? anything you want them to, they are serializable, so you can use them in your WCF projects, they are simple, so you can use them when you want to pass a number of parameters back from a method without creating a special structure just for that method.


Lets say I have a method which returns an ordered and paged list of items, but I also want to know how many items are in the list without going to a different method.


You can do something like this:


public static Tuple<int, List<string>> GetPagedItems(int from, int len)
        {
            var list = GenerateStringList(1000);

            var listcount  = list.Count();
            var subset = list.OrderBy(i => i).Skip(from).Take(len).ToList();

            return new Tuple<int, List<string>>(listcount, subset);
        }


The uses are only limited by your imagination, since .NET supports covariance and contravariance since 4.0, the sky is the limit.

Tags:

MVC Error page linked with Elmah error code

Recently in a lot of projects I've seen the popularity of using elmah error logger, even more so than log4net, its comfortable, easy to configure and very simple to follow errors. 


If you're developing a business application and want to provide serious support to your customers you'll prefer to give your users some way of describing the error, so avoiding generic error pages is a good practice but you don't want to give them a window into your application, it might open a serious security risk, using elmah's error id is the best option in my opinion, since it gives you the ability to see what went wrong, combine that with either asking the user what they tried to do or incorporating into the exception the data passed into the failing method/action and you have a better way of solving problems.


After digging around, I've seen this, which gave me a lead, so thanks for the info! 


Here's the simplest way of doing it (there's a demo project at the end of this post):


1. Add Elmah.Contrib.Mvc, you can do it via the NuGet packages.


2. Change your web.config to persist the errors, disk/sql or what ever you like, this way the id will mean something if the application is restarted, because the default is memory.


3. Set custom errors in web.config, for example: 
<customErrors mode="On" defaultRedirect="~/Demo/ShowError" />


4. If you want all the errors to be caught by elmah, you'll have to comment this line in global.asax -> void RegisterGlobalFilters(GlobalFilterCollection filters):
 filters.Add(new HandleErrorAttribute());


The default error handler doesn't alert elmah when an exception is thrown and custom errors are on.


5. Add this to your global.asax, its for elmah to handle your errors:


protected void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
        {
            var result = ElmahErrorHandling.ProcessError(sender, args);
            if (!string.IsNullOrEmpty(result))
                Response.Redirect(result);
        }


6. Insert this class in your project:


/// <summary>
    /// Elmah Error handling helper class
    /// </summary>
    public class ElmahErrorHandling
    {
        /// <summary>
        /// Configuration
        /// </summary>
        public static Configuration config = WebConfigurationManager.OpenWebConfiguration("~");

        /// <summary>
        /// CustomErrors configuration section
        /// </summary>
        public static CustomErrorsSection customErrorsSection = (CustomErrorsSection)config.GetSection("system.web/customErrors");

        /// <summary>
        /// Handlers configuration section
        /// </summary>
        public static HttpHandlersSection handlers = (HttpHandlersSection)config.GetSection("system.web/httpHandlers");

        /// <summary>
        /// Process errors caught in Global.asax
        /// <para>should be inside protected void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)</para>
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        /// <returns>null if no custom error should be displayed or a link if redirect is required to the specific location</returns>
        public static string ProcessError(object sender, ErrorLoggedEventArgs args)
        {
            if (customErrorsSection != null)
            {
                switch (customErrorsSection.Mode)
                {
                    case CustomErrorsMode.Off:
                        break;
                    case CustomErrorsMode.On:
                        return customErrorsSection.DefaultRedirect + "?id=" + args.Entry.Id;
                    case CustomErrorsMode.RemoteOnly:
                        if (!HttpContext.Current.Request.IsLocal)
                            return customErrorsSection.DefaultRedirect + "?id=" + args.Entry.Id;
                        break;
                }
            }

            return null;
        }

        /// <summary>
        /// Get path to Elmah error handler
        /// <para>e.g. elmah.axd</para>
        /// </summary>
        /// <returns></returns>
        public static string GetElmahHandler()
        {
            if ((handlers != null) && (handlers.Handlers != null))
                for (int i = 0; i < handlers.Handlers.Count; i++)
                    if (handlers.Handlers[i].Type.IndexOf("elmah", StringComparison.InvariantCultureIgnoreCase) != -1)
                        return handlers.Handlers[i].Path;
                
            return null;
        }

        /// <summary>
        /// Gets absolute path to elmah error log
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static string GetHandlerLink(string id)
        {
            return VirtualPathUtility.ToAbsolute(string.Format("~/{0}/detail?id={1}",GetElmahHandler(), id));
        }

        /// <summary>
        /// Gets Error string according to configuration and id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static string GetErrorString(string id)
        {
            if (customErrorsSection != null)
            {
                switch (customErrorsSection.Mode)
                {
                    case CustomErrorsMode.Off:
                        return string.Format("<a href='{0}'>{1}</a>",GetHandlerLink(id),id);
                    case CustomErrorsMode.On:
                    case CustomErrorsMode.RemoteOnly:
                        if (HttpContext.Current.Request.IsLocal)
                            return string.Format("<a href='{0}'>{1}</a>", GetHandlerLink(id),id);
                        else
                            return id;
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// returns MvcHtmlString for error link/text
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static MvcHtmlString GetError(string id)
        {
            return new MvcHtmlString(GetErrorString(id));
        }
    }



7. Create your error page, make sure its the correct path in customErrors section in your web.config and add this:
@ElmahErrorHandling.GetError(ViewBag.id)


It will either display a link to elmah.axd or an id, depending on your configuration.




There's a demo project at:
https://github.com/drorgl/ForBlog/tree/master/MvcElmahErrorDemo

Tags:

Friday, May 25, 2012

Hierarchy Cache



In the past I tried to avoid using hierarchies in SQL, but I couldn't find another simple data structure for a system requirement: all elements must be children of other elements with no depth limit.


I was able to change the depth limit to 100.


So I started out with:


CREATE TABLE [dbo].[Hierarchy](
    [ElementId] [int] IDENTITY(1,1) NOT NULL,
    [ParentId] [int] NULL,
 CONSTRAINT [PK_Hierarchy] PRIMARY KEY CLUSTERED 
(
    [ElementId] ASC
)


at first everything worked fine, I could traverse the tree with CTE without any problem, it was even fast for the current load, then came another system requirement, the tree size will be about 1 million records.


with cte as
(
 select ElementId, ParentId, 0 as Level
 from Hierarchy
 where ParentId is null
 
 union all
 
 select Hierarchy.ElementId, Hierarchy.ParentId, Level + 1
 from Hierarchy
  join cte on cte.ElementId = Hierarchy.ParentId
)

select * from cte


trying to traverse a tree of 1M records with CTE with only 15 levels took more than a production sql should take, even if the system's requirement should be live or near live.


We've been looking for solutions in every corner of the internet, graph databases (we'll still need to use these IDs in the SQL so passing the data back will be a design nightmare or we can move the portions or whole application, I guess these are other options), trying to find all sorts of algorithms (which will be error prune and might be a heavy toll on the development team), CLR functions and of curse scrapping the whole hierarchy idea (no can do, system requirements).


Eventually we came to the conclusion that keeping a cache will not be too costly on the memory, cpu and disk provided that the right indexes will be created and they will provide the best result and the fun part is, its very easy and fast to join the cache table on any other table we'll need. 


But we had to compromise somewhere, we compromised on node parent changes, some of them might cause the entire tree to rebuild.


I built a method to quickly update the tree given an elementid and if elementid was not provided, it will rebuild the whole tree.


I can smell just one little problem, which will be solved when updating records, need to detect circular references.


CREATE TABLE [dbo].[HierarchyCache](
 [OwnerId] [int] NOT NULL,
 [ElementId] [int] NOT NULL
)

And the SQL code updating the HierarchyCache, note that I'm using merge instead of dropping the whole table or writing individual insert/delete.

declare @ElementId int = null


--if no ElementId was supplied, it means we should go over all Hierarchy and rebuild the table
if (@ElementId is null)
begin


    with parentHierarchy (ElementId, ParentId , TopElementId)
    as
    (
        select  ElementId, ParentId, ElementId as TopElementId
        from Hierarchy with (nolock)

        union all

        select  [Hierarchy].ElementId,[Hierarchy].ParentId , pu.TopElementId
        from Hierarchy with (nolock)
        inner join parentHierarchy as pu
            on pu.ParentId = [Hierarchy].ElementId
    )

    --instead of updating the entire table, we're doing a merge, more efficient and easier on the disk
    merge into HierarchyCache as target
    using parentHierarchy
        on (parentHierarchy.TopElementId = target.OwnerId and parentHierarchy.ElementId = target.ElementId)
    when not matched by target then
        insert (ElementId, OwnerId) values (parentHierarchy.ElementId,parentHierarchy.TopElementId)
    when not matched by source then
        delete;

end
else
begin
    --a ElementId was supplied, update only its tree

    with parentHierarchy (ElementId, ParentId  , TopElementId)
    as
    (
        select  ElementId, ParentId , ElementId as TopElementId
        from Hierarchy with (nolock)
        where ElementId = @ElementId

        union all

        select  [Hierarchy].ElementId,(case when (Hierarchy.ParentId = Hierarchy.ElementId) then null else Hierarchy.ParentId end) as ParentId , pu.TopElementId
        from Hierarchy  with (nolock)
        inner join parentHierarchy as pu
            on pu.ParentId = [Hierarchy].ElementId

    )

    --instead of updating the entire table, we're doing a merge, more efficient and easier on the disk
    merge into HierarchyCache as target
    using parentHierarchy
        on (parentHierarchy.TopElementId = target.OwnerId and parentHierarchy.ElementId = target.ElementId)
    when not matched by target then
        insert (ElementId, OwnerId) values (parentHierarchy.ElementId,parentHierarchy.TopElementId);


end



Tags: , , ,

Mikrotik

Many years ago when I was doing some IT work for the company I worked for, I needed routers, I had many PCs laying around so getting a few NICs and installing them was not a problem, I've tried many open source routers but they couldn't handle the IGMP packets properly and after a few seconds the PPTP VPN connections would drop.


Until I met MikroTik, its not a freeware, but it costs very little for what it can do ($45), I've been using it since to help low budget IT departments have professional grade routing.




Level number 0 (Demo mode) 1 (Free) 3 (WISP CPE) 4 (WISP) 5 (WISP) 6 (Controller)
Price no key registration required volume only $45 $95 $250
Upgradable To - no upgrades ROS v6.x ROS v6.x ROS v7.x ROS v7.x
Initial Config Support - - - 15 days 30 days 30 days
Wireless AP 24h trial - - yes yes yes
Wireless Client and Bridge 24h trial - yes yes yes yes
RIP, OSPF, BGP protocols 24h trial - yes(*) yes yes yes
EoIP tunnels 24h trial 1 unlimited unlimited unlimited unlimited
PPPoE tunnels 24h trial 1 200 200 500 unlimited
PPTP tunnels 24h trial 1 200 200 500 unlimited
L2TP tunnels 24h trial 1 200 200 500 unlimited
OVPN tunnels 24h trial 1 200 200 unlimited unlimited
VLAN interfaces 24h trial 1 unlimited unlimited unlimited unlimited
HotSpot active users 24h trial 1 1 200 500 unlimited
RADIUS client 24h trial - yes yes yes yes
Queues 24h trial 1 unlimited unlimited unlimited unlimited
Web proxy 24h trial - yes yes yes yes
User manager active sessions 24h trial 1 10 20 50 Unlimited
Number of KVM guests none 1 Unlimited Unlimited Unlimited Unlimited




They also have appliances, take a look at their website.


I do not get percentages, but I did went through one of their courses in Florida at 2005.



Tags: ,

Thursday, May 24, 2012

Dictionary vs ConcurrentDictionary

Who hasn't seen in almost every best practices guide "cache everything!", but what they forget to tell you is that by that time, the caching you're using is one of the biggest bottlenecks of your program, you try to use as many dictionaries as you can (at least for a particular caching need) and avoid locking and blocking in any way possible.


When Microsoft decided its time for .NET to start using multi-threading heavily, they came out with Parallel Extensions, later on they added ConcurrentDictionary, but I've always wondered what is the price we're paying for the dictionary to work in a thread-safe environment.


I can't stress this enough, Do not use a regular Dictionary with multi-threading!
In some cases when the dictionary is updated by two (or more) threads it will corrupt its internal structures and it will throw exceptions. sometimes items will disappear, sometimes the whole dictionary will stop working.


In the past I've implemented a dictionary with lock keyword, spinlock, read/write locks and even a copying dictionary so whenever you had to make a change, it will copy itself, make the modification and replace the reference, I didn't care about losing information, I just wanted the quickest dictionary.


So how does the ConcurrentDictionary works? well, using ILSpy, we can take a look, I can't post the source code here so you'll have to do your own digging, but apparently its using Monitor.Enter for the writing portion, which is just an alternative of the lock keyword, for the reading portion it seems that its pretty close to the regular Dictionary.


I remembered reading about lock-free hashmaps (PDF) in the past but didn't have the time looking up the algorithm, understanding it and implementing it, luckily someone already did but I can't find the original C# implementation, here's what I've found now https://github.com/hackcraft/Ariadne.




I did some benchmarks and the results are pretty interesting.
The categories (X) are the number of concurrent threads attempting to write and read.
Note: the graphs do not work in IE for now.


You may find the benchmark project at:
https://github.com/drorgl/ForBlog/tree/master/DictionaryBenchmarks


I've taken the readwrite locked dictionary from Lorenz Cuno Klopfenstein, I've implemented a minimal spinlock dictionary just for the test, don't use it.


The ThreadSafeDictionary is the star of this benchmarks, it performs exceptionally well, so if your program relies heavily on dictionaries and threading, use it.

Tags: , , , , ,

Tuesday, May 22, 2012

Showing dynamic graphs in a blog

Lets face it, those static graphs in blogs are sometimes completely useless, sometimes one bar is overpowering the rest, you can't really edit the data easily, and don't even mention changing the graph type on the fly, including a few graph types in the same page? huh! you must be joking, why would I overcrowd the page just for ease of use?


Here's what I came up with, its doesn't have that flashy select box with multiple graph types, but it should be pretty easy to add.


Update: The demo page is not working in IE, the host I'm using is serving javascript files as text/plain instead of text/javascript.


First, create graph data in google spreadsheet, make sure you publish the spreadsheet, otherwise you won't have the url needed (File -> publish to the web -> Start Publishing)


Then create a page in the blog with this code (or similar), make sure you have the correct urls, for the scripts and your spreadsheet.



 <script src="Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
 <script src="Scripts/google-spreadsheet.js" type="text/javascript"></script>
 <script src="Scripts/google-spreadsheet-parser.js" type="text/javascript"></script>
 <script src="http://code.highcharts.com/highcharts.js"></script>

     <div id="readContainer"></div>
     <div id="writeContainer"></div>

    <script type="text/javascript">


        //container: div container
        //type: 'line' for example
        //title: 'Dictionary Benchmarks'
        //categories: array of x-graph
        //ytitle: y graph title
        function DrawChart(container, type, title, categories, ytitle, series)
        {
            var chart = new Highcharts.Chart({
                chart: {
                    renderTo: container,
                    type: type,
                    marginRight: 130,
                    marginBottom: 25
                },
                title: {
                    text: title,
                    x: -20 //center
                },
                //                subtitle: {
                //                    text: 'Source: WorldClimate.com',
                //                    x: -20
                //                },
                xAxis: {
                    categories: categories
                },
                yAxis: {
                    title: {
                        text: ytitle
                    },
                    plotLines: [{
                        value: 0,
                        width: 1,
                        color: '#808080'
                    }]
                },
                tooltip: {
                    formatter: function ()
                    {
                        return '<b>' + this.series.name + '</b><br/>' +
     this.x + ': ' + this.y + '';
                    }
                },
                legend: {
                    layout: 'vertical',
                    align: 'right',
                    verticalAlign: 'top',
                    x: -10,
                    y: 100,
                    borderWidth: 0
                },
                series: series
            });
            return chart;
        }


        function ChartFromDictionaryData(container,sparser, rows, title, ytitle)
        {

            //need to do read / write / only

            //get all dictionary types
            var serieslist = sparser.DistinctByColumn(rows, "Dictionary");
            delete serieslist["Dictionary"];

            //get all threads
            var threads = sparser.DistinctByColumn(rows, "Threads");
            delete threads["Threads"];

            //highcharts categories 
            var categories = [];
            for (var thread in threads)
            {
                categories.push(parseInt(thread));
            }
            categories.sort(function (a, b) { return a - b; });


            //foreach dictionary, get all rows, order by threads, get timing

            var highchartseries = [];
            for (var series in serieslist)
            {
                var newseries = {};
                newseries.name = series;
                newseries.data = [];
                var rowdata = sparser.FilterByColumn(rows, "Dictionary", series);
                for (var thread in threads)
                {
                    var rowvalue = parseInt(sparser.GetValue(sparser.FilterByColumn(rowdata, "Threads", thread), "Time"));
                    newseries.data.push(rowvalue);
                }
                highchartseries.push(newseries);
            }
            DrawChart(container, "line", title, categories, ytitle, highchartseries);

        }



        localStorage.clear();
//replace with your published spreadsheet url
        var url = "https://docs.google.com/spreadsheet/pub?key=0Akqqf0SS9IbJdFVuZ0hzQnNKcFVFeGJkTzRHdWhYRVE&output=html";
        var googleSpreadsheet = new GoogleSpreadsheet();
        googleSpreadsheet.url(url);
        googleSpreadsheet.load(function (result)
        {
            try
            {
                var sparser = new GoogleSpreadsheetParser(result);

                var readrows = sparser.FilterByColumn(sparser.FilterByColumn(sparser.GetAllRows(), "Read", "TRUE"), "Write", "FALSE");
                var writerows = sparser.FilterByColumn(sparser.FilterByColumn(sparser.GetAllRows(), "Read", "FALSE"), "Write", "TRUE");

                ChartFromDictionaryData('readContainer',sparser, readrows, "Dictionary Benchmarks - Read", "Time (ms) per 1 million operations");
                ChartFromDictionaryData('writeContainer',sparser, writerows, "Dictionary Benchmarks - Write", "Time (ms) per 1 million operations");

                //$('#results').html(JSON.stringify(result).replace(/,/g, ",\n"));
            } catch (e)
            {
                alert(e);
            }
        });
    </script>


and voila, you have a dynamic chart.


Take a loot at highcharts, I have yet to see a better charting library, the graph selection is great:


http://www.highcharts.com/


I took the idea from Mike McKay, here, but I've modified his code and added some of mine, there's a test project at   https://github.com/drorgl/ForBlog/tree/master/GoogleSpreadsheetReader and the files are in https://github.com/drorgl/ForBlog/tree/master/GoogleSpreadsheetReader/Scripts feel free to use it.


Here's an example I did today:
http://uhurumkate.blogspot.com/p/graph-test.html


And one last bit of information, this link might help you to understand what google spreadsheets can do for you - as a datasource.
http://www.goopal.org/google-sites-business/google-spreadsheets/spreadsheet-output/publish-spreadsheet



Tags: , , ,