Posts

Displaying dynamic javascript charts using MVC

When creating business solutions there is often a need to display dynamic JavaScript charts using specific entity data that can be easily extended throughout the system. Properly designed system should always provide clear separation between business entities and UI layer. When using JavaScript this separation can be easily neglected.
In this article I will show you how to use entity oriented approach when mixing C# and JavaScript code.

In order to do that we will create JavaScript api that will be used to call web controllers such as clientController, productController, userController etc. Each of these controllers will provide us entity related data that can be displayed on the web pages throughout the system. The data will be sent using json protocol along with the target chart configuration we want.

jschart_line

In order to display above charts within our application, we need to simply create div tag that will serve as a placeholder for our chart, we also need to assign attributes and “chartButton” class name to the element that will trigger chart display. The attributes will contain entity name e.g. data-entity=”client”, chart placeholder e.g. targetdiv=”chart1Div” and optionally data parameters e.g. params=”clientId=?&fullInfo=?”

  <input type="button" value="show client chart" class="chartButton" data-entity="client" data-params="clientId=@ViewBag.ClientId&fullInfo=true" data-targetdiv="chart1Div" />
  <input type="button" value="show product chart" class="chartButton" data-entity="product" data-targetdiv="chart1Div" />
  <input type="button" value="show user chart" class="chartButton" data-entity="user" data-targetdiv="chart1Div" />
  <div id="chart1Div"></div>

We also need to add following script references to our _Layout.cshtml file. In our case we will use HighCharts, hence reference to highcharts.js script (we are not limited only to this control, you can convert it to use other JavaScript chart controls as well if needed). The file chartAPI.js will contain our main logic that connects Web Api controllers and JavaScript code.

   <script src="https://code.highcharts.com/highcharts.js" type="text/javascript"></script>
   <script src="@Url.Content("~/Scripts/chartAPI.js")" type="text/javascript"></script>

Let’s start implementation from creating Web Api entity controllers and chart data dto object that will transfer the chart data and it’s configuration.

The ChartData class will contain chart series and chartConfig object – it can be extended at any time depending on our requirements.

   public class ChartData
   {
        public ChartConfig ChartConfig { get; set; }
        public List<Series> Series { get; set; }
    }

    public class Series
    {
        public string Name { get; set; }
        public string Color { get; set; }
        public string DashStyle { get; set; }      
        public List<PointData> Points { get; set; }
    }

    public class PointData
    {
        public DateTime Date { get; set; }
        public double Value { get; set; }
    }

    public class ChartConfig
    {
        public ChartConfig()
        {
            tooltipPointFormat = "";
        }

        public string chartTitle { get; set; }
        public string defaultSeriesType { get; set; }
        public int width { get; set; }
        public int height { get; set; }
        public string tooltipPointFormat { get; set; }
        public bool pie_allowPointSelect { get; set; }
        public bool pie_dataLabelsEnabled { get; set; }
        public bool pie_showInLegend { get; set; }   
    }

Having above classes created, we can now use it to fill it with dummy data inside our entity controller. We can add as many series as we like, we may also configure desired chart’s width, height, series colors etc. Please note that controller’s method will be mapped based on defined html params (see above html configuration).

 public string Get(int clientId, bool fullInfo = true)
 { 
    var data = new ChartData();

    //configure chart
    data.ChartConfig = new ChartConfig()
    {
        chartTitle = "Clients",
        defaultSeriesType = "line",
        width = 800,
        height = 400,
    };

    #region add series
    data.Series = new List<Series>();

    data.Series.Add(new Series()
    {
        Name = "Series1",
        Color = ColorTranslator.ToHtml(Color.Navy),
        DashStyle = "ShortDash",
        Points = new List<PointData>()
    });

    data.Series.Add(new Series()
    {
        Name = "Series2",
        Color = ColorTranslator.ToHtml(Color.Red),
        Points = new List<PointData>()
    });

    data.Series.Add(new Series()
    {
        Name = "Series3",
        Points = new List<PointData>()
    });

    #endregion

    #region add dummy data
    var random = new Random();
    foreach (var serie in data.Series)
    {
        for (var i = 0; i < 50; i++)
        {
            serie.Points.Add(new PointData()
            {
                Date = DateTime.Now.AddDays(-i),
                Value = random.Next(10, 100)
            });
        }
    }
    #endregion

    var json = JsonHelper.ToJSON<ChartData>(data);

    return json;
}

Our chartAPI.js file will contain main JavaScript logic encapsulating data retrieval and constructing the chart object. The function getEntityData simply calls appropriate entity controller using $.getJSON function. When data is retrieved, the renderChart function is used to create chart object, fill it with data and configure according to obtained configuration.

 //this function is used by UI to trigger chart display
$(document).ready(function () {
    //process data on button click
    $(".chartButton").click(function (sender) {

        //read button attributes
        var entity = $(this).attr("data-entity");
        var targetDiv = $(this).attr("data-targetDiv");
        var params = $(this).attr("data-params");

        //call encapsulated function
        getEntityData(entity, params, function (data, status, response) {
            if (status === "success") {
                renderChart(data, targetDiv);
            }
            else if (status === "error") {
                //process the error here if needed

                alert('An error occurred: ' + response);
            }
            else if (status === "complete") {
                //attach your load completed events here if needed

                //alert('Data loaded!');
            }
        });
    });
});

//Gets data for the entity
//This function is encapsulated 
function getEntityData(entityName, params, callbackFn) {
    if (typeof callbackFn != 'function') {
        alert('Incorrect callback function attached!'); return;
    }

    //make sure correct path is set
    var root = location.protocol + "//" + location.host;

    $.getJSON(root + '/API/' + entityName + "?" + params, null)
    .success(function (data, status, response) {
        //create object from json
        var resultObject = JSON.parse(data);
        callbackFn(resultObject, status, response);
    })
    .error(function (e, status, response) {
        callbackFn(e, status, response);
    })
    .complete(function (e, status, response) {
        callbackFn(e, "complete", response);
    });
}

//Renders chart
function renderChart(data, renderTo) {
    var chart1 = new Highcharts.Chart({
        chart: {
            renderTo: renderTo,
            defaultSeriesType: data.ChartConfig.defaultSeriesType,
            width: data.ChartConfig.width,
            height: data.ChartConfig.height,
        },
        title: {
            text: data.ChartConfig.chartTitle,
        },
        tooltip: {
         pointFormat: data.ChartConfig.tooltipPointFormat,
        },
        plotOptions: {
                pie: {
                    allowPointSelect: data.ChartConfig.pie_allowPointSelect,
                    cursor: 'pointer',
                    dataLabels: {
                        enabled: data.ChartConfig.pie_dataLabelsEnabled,
                    },
                    showInLegend: data.ChartConfig.pie_showInLegend,
                },
            },
        xAxis: {
            title: {
                text: 'date'
            },
            type: 'datetime',
        },
        yAxis: {
            title: {
                text: 'value'
            }
        },
    });

    $.each(data.Series, function (index, item) {
        //add series
        chart1.addSeries({
            name: item.name,
            dashStyle: item.DashStyle,
            color: item.Color,
            data: []
        }, false);

        //add data points
        $.each(item.Points, function (index, pointData) {
            chart1.series[chart1.series.length - 1].addPoint([
                Date.parse(new Date(parseInt(pointData.Date.substr(6)))),//format date
                pointData.Value,
            ], false);
        });
    });

    chart1.redraw();
}

It is more than certain that you will have to adjust above solution to your specific requirements. At the same time it should give you a good starting point to implement your own solution based on this example.

I have attached project files below for your convenience.

jsChartMVC

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 4.00 out of 5)
Loading...Loading...

Displaying interactive MS Chart objects in ASP.NET MVC

Displaying charts using MS Chart is usually simple in asp.net webform. The problem exists if we want to use it in Asp.net MVC application without putting it on .aspx page. In this article I will show you how to display interactive chart using purely MVC view.

mschart_mvc

Lets start with creating ChartHelper class that will contain basic logic for creating chart objects. In the function CreateDummyChart() we will just create standard MS Chart object with some dummy data.

 public static Chart CreateDummyChart()
 {
    var chart = new Chart() { Width = 600, Height = 400 };
    chart.Palette = ChartColorPalette.Excel;
    chart.Legends.Add(new Legend("legend1") { Docking = Docking.Bottom });

    var title = new Title("Test chart", Docking.Top, new Font("Arial", 15, FontStyle.Bold), Color.Brown);
    chart.Titles.Add(title);
    chart.ChartAreas.Add("Area 1");

    chart.Series.Add("Series 1");
    chart.Series.Add("Series 2");

    chart.BackColor = Color.Azure;
    var random = new Random();
    
    //add random data: series 1
    foreach (int value in new List<int>() { random.Next(100), random.Next(100), random.Next(100), random.Next(100) })
    {
        chart.Series["Series 1"].Points.AddY(value);

        //attach JavaScript events - it can also be ajax call
        chart.Series["Series 1"].Points.Last().MapAreaAttributes = "onclick=\"alert('value: #VAL, series: #SER');\"";
    }

    //add random data: series 2
    foreach (int value in new List<int>() { random.Next(100), random.Next(100), random.Next(100), random.Next(100) })
    {
        chart.Series["Series 2"].Points.AddY(value);

        //attach JavaScript events - it can also be ajax call
        chart.Series["Series 2"].Points.Last().MapAreaAttributes = "onclick=\"alert('value: #VAL, series: #SER');\"";
    }

    return chart;
 }

Next, we need to create function that takes our newly created chart object and saves it to memory stream. After that we only need to convert the stream to byte array and to base64 string afterwards.

Having image string created, we simply construct html “img” tag to be rendered by our view. Please note that “data:image/png;base64” attributes are necessary to tell the browser to render it as image.

 public static string GetChartImageHtml(Chart chart)
 {
    using (var stream = new MemoryStream())
    {
        var img = "<img src='data:image/png;base64,{0}' alt='' usemap='#" + ImageMap + "'>";

        chart.SaveImage(stream, ChartImageFormat.Png);

        var encoded = Convert.ToBase64String(stream.ToArray());

        return string.Format(img, encoded);
    }
 }

The final thing is to create DisplayChart() method in the view to be used by Html.RenderAction. Apart from chart’s image string, we also need to display chart’s image map in order to enable chart JavaScript events when needed.

public ActionResult DisplayChart()
{  
    var chart = ChartHelper.CreateDummyChart();
   
    var result = new StringBuilder();
    result.Append(ChartHelper.GetChartImageHtml(chart));
    result.Append(chart.GetHtmlImageMap(ChartHelper.ImageMap));

    return Content(result.ToString());
}

And finally this is our view

@{
    ViewBag.Title = "Home Page";
}

<h2>@ViewBag.Message</h2>

@{ Html.RenderAction("DisplayChart"); }

I have attached project files to save your time 🙂

mschart-mvc

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Silverlight component with chart

If you ever wanted to create your own Silverlight component, this is a good place to start. In this article I will show you how to create simple component displaying salary data on the chart.

In our example the data are being stored in the application but you can easily call a web service to display the it from the different server.

silverligt-component

After creating new silverlight project in Visual Studio we start from creating basic class structure that we will bind to our chart.

  public class NumericItemPair
    {
        public string Name { get; set; }
        public double Value { get; set; }
    }

    public class ProfessionItem
    {
        public string Name { get; set; }
        public double AVG { get; set; }
        public List<NumericItemPair> DataPoints = new List<NumericItemPair>();
    }

In our example we will get some dummy data based on user selection (drop down branch list). This data can be retrieved from database or web service. Please note that we will display aggregates based on 2 deciles; lower and upper and the median value.

  public class MyDataContext
    {
        public static ProfessionItem getData(int branch)
        {
            ProfessionItem itm = new ProfessionItem();
            if (branch == 0)
            {
                itm.AVG = 2332;
                itm.Name = "IT";
                itm.DataPoints = new List<NumericItemPair>();
                itm.DataPoints.Add(new NumericItemPair() { Name = "Lower decile", Value = 1567 });
                itm.DataPoints.Add(new NumericItemPair() { Name = "Median", Value = 1789 });
                itm.DataPoints.Add(new NumericItemPair() { Name = "Upper decile", Value = 2400 });
            }
            else if (branch == 1)
            {
                itm.AVG = 2132;
                itm.Name = "Production";
                itm.DataPoints = new List<NumericItemPair>();
                itm.DataPoints.Add(new NumericItemPair() { Name = "Lower decile", Value = 1267 });
                itm.DataPoints.Add(new NumericItemPair() { Name = "Median", Value = 1589 });
                itm.DataPoints.Add(new NumericItemPair() { Name = "Upper decile", Value = 2700 });
            }
            else
            {
                itm.AVG = 2532;
                itm.Name = "HR";
                itm.DataPoints = new List<NumericItemPair>();
                itm.DataPoints.Add(new NumericItemPair() { Name = "Lower decile", Value = 1167 });
                itm.DataPoints.Add(new NumericItemPair() { Name = "Median", Value = 1289 });
                itm.DataPoints.Add(new NumericItemPair() { Name = "Upper decile", Value = 1900 });
            }
            return itm;
        }
    }

Having done that we need to style our chart and assign bindings.

    ProfessionItem pit = MyDataContext.getData(cmbBranches.SelectedIndex);

    chart1.Title = "Branch: " + pit.Name;
    lblAVG.Content = pit.AVG.ToString("# ###") + " GBP ";

    LineSeries lineSeries = new LineSeries();
    //assign binding
    lineSeries.SetBinding(LineSeries.ItemsSourceProperty, new Binding());
    lineSeries.DependentValueBinding = new Binding("Value");
    lineSeries.IndependentValueBinding = new Binding("Name");

    // hide the legend 
    Style legendStyle = new Style(typeof(Legend));
    legendStyle.Setters.Add(new Setter(Legend.VisibilityProperty, Visibility.Collapsed));
    legendStyle.Setters.Add(new Setter(Legend.WidthProperty, 0));
    legendStyle.Setters.Add(new Setter(Legend.HeightProperty, 0));
    chart1.LegendStyle = legendStyle;

    Style titleStyle = new Style(typeof(Title));
    titleStyle.Setters.Add(new Setter(Title.ForegroundProperty, "#0004BD"));
    titleStyle.Setters.Add(new Setter(Title.FontSizeProperty, 12));
    titleStyle.Setters.Add(new Setter(Title.FontWeightProperty, "bold"));
    titleStyle.Setters.Add(new Setter(Title.HorizontalAlignmentProperty, "left"));
    chart1.TitleStyle = titleStyle;

    // hide the line series data points 
    Style datapointStyle = new Style(typeof(DataPoint));
    datapointStyle.Setters.Add(new Setter(DataPoint.VisibilityProperty, Visibility.Visible));
    datapointStyle.Setters.Add(new Setter(DataPoint.WidthProperty, 10));
    datapointStyle.Setters.Add(new Setter(DataPoint.HeightProperty, 10));
    datapointStyle.Setters.Add(new Setter(DataPoint.DependentValueStringFormatProperty, "{0:# ###} GBP"));

    lineSeries.DataPointStyle = datapointStyle;

    chart1.Series.Add(lineSeries);
    chart1.DataContext = pit.DataPoints;

Next we need to set up our xaml file to position elements within our component. We can do it manually or using Visual Studio design editor.

    <toolkit:Chart HorizontalAlignment="Left" Margin="15,0,0,346" Name="chart1"  Title="" VerticalAlignment="Bottom" Width="480" Height="370" UseLayoutRounding="True" BorderThickness="0" FontSize="11" Grid.ColumnSpan="2" Padding="10" Grid.RowSpan="2">
            <toolkit:Chart.LegendStyle >
                <Style TargetType="toolkit:Legend">
                    <Setter Property="Visibility" Value="Collapsed"/>
                </Style>
            </toolkit:Chart.LegendStyle>
            
            <!--Set X-Axis Format-->
            <toolkit:Chart.Axes>
                <toolkit:LinearAxis Orientation="Y" ShowGridLines="True" Title="Salary [GBP]" >
                    <toolkit:LinearAxis.AxisLabelStyle>
                        <Style TargetType="toolkit:AxisLabel">
                            <Setter Property="StringFormat" Value="{}{0:# ###} GBP"/>
                        </Style>
                    </toolkit:LinearAxis.AxisLabelStyle>
                </toolkit:LinearAxis>

                <toolkit:LinearAxis Orientation="X" ShowGridLines="False" Title="Salary range [%]" >
                    <toolkit:LinearAxis.AxisLabelStyle>
                        <Style TargetType="toolkit:AxisLabel">
                            <Setter Property="StringFormat" Value="{}{0:0%}"/>
                        </Style>
                    </toolkit:LinearAxis.AxisLabelStyle>
                </toolkit:LinearAxis>
            </toolkit:Chart.Axes>
        </toolkit:Chart>

After that we can test our application. I have included working example below. Happy coding 🙂

Silverlight-component

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...