Thursday, July 10, 2008

Radchart in ASP.NET MVC

Telerik has a lot of really nice 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 easy to get to work.

Inspired by 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]) });
           // 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);