2

We are trying to move away from Silverlight based chart controls to more plugin-less controls. We like HighCharts and are now testing implementing it into our existing codebase. We have found DotNet.HighCharts as a possible contender to quickly create charts without having to parse out all the javascript.

My question is how do you pass in data to be used in the chart when it is contained within a DataTable? Following along with the answer here I have seen that Linq might be the way to go. However I am having a hard time getting the code to actually build. The documentation for DotNet.HighCharts does not show any examples of pulling non-static data so no luck there. This is a snippet of my code so far:

    Dim da3 As New SqlDataAdapter(sql3, conn3)
    da3.Fill(dt3)
    da3.Dispose()
    conn3.Close()

    Dim chart3 As Highcharts = New Highcharts("chart").InitChart(New Chart() With { _
     .PlotShadow = False _
    }).SetTitle(New Title() With { _
     .Text = "Browser market shares at a specific website, 2010" _
    }).SetTooltip(New Tooltip() With { _
     .Formatter = "function() { return '<b>'+ this.point.name +'</b>: '+ this.percentage +' %'; }" _
    }).SetPlotOptions(New PlotOptions() With { _
     .Column = New PlotOptionsColumn With { _
      .AllowPointSelect = True, _
      .Cursor = Cursors.Pointer, _
      .DataLabels = New PlotOptionsColumnDataLabels() With { _
       .Color = ColorTranslator.FromHtml("#000000"), _
       .Formatter = "function() { return '<b>'+ this.point.name +'</b>: '+ this.percentage +' %'; }" _
      } _
     } _
    }).SetSeries(New Series() With { _
     .Type = ChartTypes.Column, _
     .Name = "Browser share", _
     .Data = New Helpers.Data(dt3.Select(Function(x) New Options.Point() With {.X = x.periodyear, .Y = x.rate}).ToArray()) _
    })
    ltrChart3.Text = chart3.ToHtmlString()

I am importing the following:

Imports DotNet.Highcharts
Imports DotNet.Highcharts.Options
Imports System.Data
Imports System.Data.SqlClient
Imports DotNet.Highcharts.Enums
Imports System.Drawing
Imports System.Collections.Generic
Imports System.Linq

The error I get is on the .Data = New Helpers.Data(dt3.Select(Function(x) New Options.Point() With {.X = x.periodyear, .Y = x.rate}).ToArray()) _ line. I am getting Lambda expression cannot be converted to 'String' because 'String' is not a delegate type as my error on the Function....rate} bit.

EDIT: First attempt at modifying existing code

    Dim chart1 As Highcharts = New DotNet.Highcharts.Highcharts("test")

    Dim series As Series = New Series()
    series.Name = "CO Rates"
    For i As Integer = 0 To dt.Rows.Count - 1
        series.Data = New Helpers.Data(New Object() {dt.Rows(i)("periodyear"), dt.Rows(i)("rate")})
    Next
    chart1.SetSeries(series).InitChart(New Chart() With { _
                                       .Type = ChartTypes.Column})

This just produces 2 columns the first is at x-position 0 and its value is 2011 and the other is at x-position 1 and its value is 8.3. This is supremely odd as it looks to me like it is taking the last point in the dataTable and then make its {x, y} value ({2011, 8.3}) into 2 distinct points with the x and y values both being the y value like {0, x} and {1, y}. I need to get:

  • A single series with {periodyear, rate} taken from the dataTable.
  • In the future I want to have a series for each location with its {periodyear, rate} taken from the dataTable.

EDIT 2: Okay, I have it converting the results to a datadictionary and I am getting the chart to display all points. Only issue now is converting the dt.Rows(i)("periodyear") into a DateTime value. This is the code so far (which fails on the PaseExact method):

Dim series As Series = New Series()
        series.Name = "CO Rates"

        Dim xDate As DateTime
        Dim data As New Dictionary(Of DateTime, Decimal)
        For i As Integer = 0 To dt.Rows.Count - 1
            xDate = DateTime.ParseExact(dt.Rows(i)("periodyear").ToString, "YYYY", Globalization.CultureInfo.InvariantCulture)
            data.Add(xDate, dt.Rows(i)("rate"))
        Next

        Dim chartData As Object(,) = New Object(data.Count - 1, 1) {}
        Dim x As Integer = 0
        For Each pair As KeyValuePair(Of DateTime, Decimal) In data
            chartData.SetValue(pair.Key, x, 0)
            chartData.SetValue(pair.Value, x, 1)
            x += 1
        Next

        Dim chart1 As Highcharts = New Highcharts("chart1").InitChart(New Chart() With { _
         .Type = ChartTypes.Column _
        }).SetTitle(New Title() With { _
         .Text = "Chart 1" _
        }).SetXAxis(New XAxis() With { _
         .Type = AxisTypes.Linear _
        }).SetSeries(New Series() With { _
         .Data = New Helpers.Data(chartData) _
        })

If I change this to just the string value (for example 1980) and then change the AxisTypes to Linear for the x-axis I do display data. It is just off somehow - 1980 looks like 1,980. Baby steps...

EDIT N: This is the final working solution. This seems like it could use some cleaning up. For example I am not sure I need to create a dictionary to put in my X/Y values and then iterate over the dictionary to add the data to my chartData object. This seems like double work.

    Dim stfipsList = (From r In dt.AsEnumerable() Select r("stfips")).Distinct().ToList()
    Dim SeriesList As New List(Of Series)(stfipsList.Count)
    Dim seriesItem(stfipsList.Count) As Series
    Dim xDate As DateTime
    Dim fakeDate As String
    Dim sX As Integer

    sX = 1
    For Each state In stfipsList
        Dim data As New Dictionary(Of DateTime, Decimal)
        Dim stateVal As String = state.ToString
        Dim recCount As Integer = dt.Rows.Count - 1

        For i As Integer = 0 To recCount
            If dt.Rows(i)("stfips").ToString = stateVal Then
                fakeDate = "1/1/" + dt.Rows(i)("periodyear").ToString
                xDate = DateTime.Parse(fakeDate)
                data.Add(xDate.Date, dt.Rows(i)("unemprate"))
            End If
        Next
        Dim chartData As Object(,) = New Object(data.Count - 1, 1) {}
        Dim x As Integer = 0
        For Each pair As KeyValuePair(Of DateTime, Decimal) In data
            chartData.SetValue(pair.Key, x, 0)
            chartData.SetValue(pair.Value, x, 1)
            x += 1
        Next
        seriesItem(sX) = New Series With {
                    .Name = state.ToString, _
                    .Data = New Helpers.Data(chartData)
        }

        SeriesList.Add(seriesItem(sX))

        sX = sX + 1
    Next

    Dim chart1 As Highcharts = New Highcharts("chart1").InitChart(New Chart() With { _
     .Type = ChartTypes.Line _
    }).SetTitle(New Title() With { _
     .Text = "Annual Unemployment Rate" _
    }).SetTooltip(New Tooltip() With { _
     .Formatter = "function() { return '<b>'+ this.series.name + ': ' + Highcharts.dateFormat('%Y', this.x) +'</b>: '+ this.y +' %'; }" _
    }).SetXAxis(New XAxis() With { _
     .Type = AxisTypes.Datetime _
     }).SetYAxis(New YAxis() With { _
       .Min = 0, _
       .Title = New YAxisTitle() With { _
        .Text = "Unemployment Rate", _
        .Align = AxisTitleAligns.High _
      } _
    }).SetSeries(SeriesList.[Select](Function(s) New Series() With { _
                 .Name = s.Name, _
                 .Data = s.Data _
                }).ToArray())

    ltrChart1.Text = chart1.ToHtmlString()
Community
  • 1
  • 1
wergeld
  • 14,332
  • 8
  • 51
  • 81

2 Answers2

3

See below for one of my controller ActionResults that performs what you are asking for. It is written in C#, but can be modified easy enough to VB. There is more information than what you are asking for but it may give visibility to other problems when working with Highcharts in Visual Studio.

What I am doing is creating a list of Series then just passing the list to highcharts. The advantage of doing it this way is that you will not have to create each series individually.

public ActionResult CombinerBarToday(DateTime? utcStartingDate = null,
                                             DateTime? utcEndingDate = null)
        {
            //TEMPORARILY USED TO FORCE A DATE
            //utcStartingDate = new DateTime(2012, 1, 9, 0, 0, 1);
            //utcEndingDate = new DateTime(2012, 1, 9, 23, 59, 59);

            //GET THE GENERATED POWER READINGS FOR THE SPECIFIED DATETIME
            var firstQ = from s in db.PowerCombinerHistorys
                         join u in db.PowerCombiners on s.combiner_id equals u.id
                         where s.recordTime >= utcStartingDate
                         where s.recordTime <= utcEndingDate
                         select new
                         {
                             Combiner = u.name,
                             Current = s.current,
                             RecordTime = s.recordTime,
                             Voltage = s.voltage,
                             Power = s.current * s.voltage
                         };

            //APPLY THE GROUPING
            var groups = firstQ.ToList().GroupBy(q => new
            {
                q.Combiner,
                Date = q.RecordTime.Date,
                Hour = q.RecordTime.Hour
            });

            List<CombinerKwh> stringGroupedKwhlist = new List<CombinerKwh>();

            //CALCULATE THE AVERAGE POWER GENERATED PER HOUR AND ADD TO LIST
            foreach (var group in groups)
            {
                stringGroupedKwhlist.Add(new CombinerKwh(
                                         group.Key.Combiner,
                                         new DateTime(group.Key.Date.Year, group.Key.Date.Month, group.Key.Date.Day, group.Key.Hour, 0, 0),
                                         group.Average(g => g.Power) / 1000d
                                         ));
            }

            //GET A LIST OF THE COMBINERS CONTAINS IN THE QUERY
            var secondQ = (from s in firstQ 
                           orderby s.Combiner 
                           select new
                                {
                                    Combiner = s.Combiner
                                }
                           ).Distinct();

            /* THIS LIST OF SERIES WILL BE USED TO DYNAMICALLY ADD AS MANY SERIES 
             * TO THE HIGHCHARTS AS NEEDEDWITHOUT HAVING TO CREATE EACH SERIES INDIVIUALY */
            List<Series> allSeries = new List<Series>();

            TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

            //LOOP THROUGH EACH COMBINER AND CREATE SERIES
            foreach (var distinctCombiner in secondQ)
            {
                var combinerDetail = from h in stringGroupedKwhlist
                                     where h.CombinerID == distinctCombiner.Combiner
                                     orderby TimeZoneInfo.ConvertTimeFromUtc(h.Interval,easternZone)
                                     select new
                                            {
                                                CombinerID = h.CombinerID,
                                                //CONVERT FROM UTC TIME TO THE LOCAL TIME OF THE SITE
                                                Interval = TimeZoneInfo.ConvertTimeFromUtc(h.Interval,easternZone),
                                                KWH = h.KWH
                                            };

                //REPRESENTS 24 PLOTS FOR EACH HOUR IN DAY
                object[] myData = new object[24];

                foreach (var detailCombiner in combinerDetail)
                {
                    if (detailCombiner.KWH != 0)
                    {
                        myData[detailCombiner.Interval.Hour] = detailCombiner.KWH;
                    }
                }

                allSeries.Add(new Series
                                    {
                                        Name = distinctCombiner.Combiner,
                                        Data = new Data(myData)
                                    });
            }

            Highcharts chart = new Highcharts("chart")
            .InitChart(new Chart { DefaultSeriesType = ChartTypes.Spline })
            .SetTitle(new Title { Text = "Combiner History" })
            .SetXAxis(new XAxis
            {
                Categories = new[] { "0:00 AM", "1:00 AM", "2:00 AM", "3:00 AM", "4:00 AM", "5:00 AM", "6:00 AM", "7:00 AM", "8:00 AM", "9:00 AM", "10:00 AM", "11:00 AM", "12:00 PM", "1:00 PM", "2:00 PM", "3:00 PM", "4:00 PM", "5:00 PM", "6:00 PM", "7:00 PM", "8:00 PM", "9:00 PM", "10:00 PM", "11:00 PM" },
                Labels = new XAxisLabels
                                       {
                                           Rotation = -45,
                                           Align = HorizontalAligns.Right,
                                           Style = "font: 'normal 10px Verdana, sans-serif'"
                                       },
                Title = new XAxisTitle { Text = "Time(Hour)" },
                //Type = AxisTypes.Linear
            })
            .SetYAxis(new YAxis
            {
                //Min = 0,
                Title = new YAxisTitle { Text = "Kwh" }
            })

            .SetSeries(allSeries.Select(s => new Series { Name = s.Name, Data = s.Data }).ToArray());

            return PartialView(chart);
        }
Linger
  • 14,942
  • 23
  • 52
  • 79
2

Here you can find example how you can create series from DataTable: http://dotnethighcharts.codeplex.com/discussions/287106

Also from the sample project you can find how to "Bind Data From Dictionary" and "Bind Data From Object List"

Vangi
  • 586
  • 6
  • 20
  • Looking at the discussion post this kind of what I am doing. Attempting this just leads to x-number of different series for each data point? I get why you would want to do it this way for when you have multiple series to plot but I just want to pass the periodyear and rate value as one set to the chart for every row in my result set. I think what is throwing me is the With {} syntax. I will look deeper at my code. I have updated my question with my non working attempt. – wergeld Apr 06 '12 at 01:31