Thursday, July 10, 2008

Radchart in ASP.NET MVC

Telerik has a lot of really nice asp.net controlls, howevery i douby very many of them will work with the mvc framework. One of the nicest controlls is RadChart, that makes charts, and accordint to http://www.telerik.com/community/forums/thread/b311D-badtea.aspx#534484 easy to get to work.

Inspired by http://blog.maartenballiauw.be/category/ASPNET.aspx i wanted to make a more elegant approach, with an action rendering a chart without saving it to disk.


To use an action to render a image, we need to make an url to it. We create a helper extension to help us with that.

   public static class ChartResultHelper
   {
       public static string Chart<T>(this HtmlHelper helper, System.Linq.Expressions.Expression<Action<T>> action, int width, int height, string alt)
           where T : Controller
       {
           string url = helper.BuildUrlFromExpression<T>(action);
           return string.Format("<img src=\"{0}\" width=\"{1}\" height=\"{2}\" alt=\"{3}\" />", url, width, height, alt);
       }
   }

and use it in a regular viewpage

       <%= Html.Chart<ReportController>(c => c.Chart(1, 200, 483), 200,483, "Report")%>

Ofcourse some of this could be a parameterized better but shows the point. As you may see, i have mapped this image to the action chart in report controller, lets look at that.

       public ActionResult Chart(long id, int h, int w)
       {
           Telerik.WebControls.RadChart objChart = new Telerik.WebControls.RadChart();
 
           // the data we want to chart
           Dictionary<string, double> objValues = new Dictionary<string, double>();
 
           objValues.Add("Svar på foruminnlegg", 4);
           objValues.Add("Nye tråder", 100);
           objValues.Add("Bilder i foruminnlegg", 20);
 
           // setting some chart parameters
 
           objChart.Height = h;
           objChart.Width = w;
           objChart.Margins.Right = new Unit(30, UnitType.Percentage);
           objChart.Margins.Bottom = new Unit(1, UnitType.Percentage);
           objChart.Margins.Left = new Unit(1, UnitType.Percentage);
           objChart.Margins.Top = new Unit(1, UnitType.Percentage);
           objChart.Background.MainColor = Color.White;
           objChart.Background.BorderColor = Color.White;
 
           // creating a series and adding it to the chart
           ChartSeries cSeries = new ChartSeries();
           cSeries.Name = "Poeng";
           cSeries.Type = ChartSeriesType.Pie;
           cSeries.LegendDisplayMode = ChartSeriesLegendDisplayMode.ItemLabels;
           String[] colorArray = new String[7] { "#00839C", "#E15D1F", "#2AB1C4", "#FFAB13", "#D0FAFF", "#CF8300", "#397BB2" };
           int k = 0;
           foreach (var entry in objValues)
           {
               cSeries.Items.Add(new ChartSeriesItem() {Name = entry.Key, YValue = entry.Value, MainColor = ColorTranslator.FromHtml(colorArray[k]) });
               k++;
           }
           objChart.Series.Add(cSeries);
 
           // send the chart to the view engine
           return new ChartResult() { Chart = objChart };
       }

Looks easy enough, just need to create the view engine part of the soulution.

   public class ChartResult : ActionResult
   {
       public Telerik.WebControls.RadChart Chart { get; set; }
 
       public override void ExecuteResult(ControllerContext context)
       {
           if (Chart == null)
           {
               throw new ArgumentNullException("Chart");
           }
 
           Image image = Chart.GetBitmap();
           ImageCodecInfo encoder;
           EncoderParameters encoderParams = null;
 
           context.HttpContext.Response.ExpiresAbsolute = DateTime.Now.AddMinutes(5);
 
           encoder = ImageCodecInfo.GetImageEncoders().Single(c => c.FormatID == ImageFormat.Jpeg.Guid);
           encoderParams = new EncoderParameters(1);
           encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
           context.HttpContext.Response.ContentType = encoder.MimeType;
           image.Save(context.HttpContext.Response.OutputStream, encoder, encoderParams);
       }
   }