2016-09-24 At this point, you have a basic chart up and running because you know how to configure Web.Config to support the Chart object, and because you have configured a basic column chart object. I'd like to continue by showing you a more advanced configuration, and show you the code for a column chart with a few moving parts. In this case, I'm using a column chart that allows different data to be "overlayed" atop the chart using a filter control. CODE FORWARD I like to keep the code forward as plain as possible, preferring to handle the heavier configuration in the compiled codebehind. The basic chart object has two elements with the control tag -- series and chart area. Each of these elements contains a web control (asp:Series and asp:ChartArea). This chart also adds asp:Title controls. I'm also including an asp:Panel control, which contains a dropdownlist control for filtration, and an asp:Label control or two. Finally, a label at bottom to communicate status back to the user in case something goes south (you'll see the code for this in Catch blocks).

Notice that the Series web control points to the ID of the ChartArea control. DATA SOURCE My datasource for this is an XML doc, just like was used in the basic column chart. 10/20/2015 273.5 10/20/2015 - 273.5lbs Doctor's office . . . The two main columns we'll use are the date and weight columns. The location, scale and medication columns contain data we'll present based on a filter control outside of the chart. We'll show the comment column as a tooltip at each data point. One of the ways the data is bound to the control actually requires a group by column. I couldn't find any way around this (the method wouldn't acccept null or empty string as a value) for this binding method, so I figured the next best thing I could do here was to simply add an empty column to my data. I'm sure there are better workarounds out there; I guess I'm lucky that I have the freedom to use the option I did -- not everyone will. We'll talk about this more when we get to data binding. CODE BEHIND (1) Load the Dataset I didn't make any subatantial changes to how the dataset is loaded compared with the basic chart: Private Function LoadDataSet() As DataSet Dim ds As New DataSet Dim nt As New NameTable() Dim objWeight As Object = nt.Add("weight") Dim objDate As Object = nt.Add("date") Dim objComment As Object = nt.Add("comment") Dim objLocation As Object = nt.Add("location") Dim objScale As Object = nt.Add("scale") Dim objMedication As Object = nt.Add("medication") Dim objGroupBy As Object = nt.Add("groupby") 'the databindcrosstable method requires a group by column Dim settings As New XmlReaderSettings() settings.NameTable = nt 'create a reader object and point it to the xml doc Dim rdr As XmlReader = XmlReader.Create(Server.MapPath("~/xml/fitness_graph.xml"), settings) 'disable parsing DTD's -- reduce attack surface vs. XML bombing 'Call ProhibitXmlDtd(rdr) Try 'import the reader's data into the dataset ds.ReadXml(rdr, XmlReadMode.InferSchema) Catch ex As Exception 'build the status message, hide the chart and panel. lblStatus.Text = ex.Message lblStatus.ForeColor = Color.Red lblStatus.Visible = True Chart1.Visible = False pnlLegend.Visible = False Finally 'close the reader If Not rdr.ReadState = ReadState.Closed Then rdr.Close() End If End Try Return ds End Function In my Catch block, I capture the message and display it in a Label control called lblStatus, and hide the chart control and an additional Panel control, in which I'm manufacturing a legend for the chart. More on this in a bit. (2) Yes, I'm Still Harping about XML Bombing An XML bomb is an XML document crafted in such a way that it could overload the parser on the server by causing it to create an "exception"-ally large object, generally through recursion (see what I did there?). This amounts to a denial- of-service attack on your Web server. in the settings object, be sure you set that DtdProcessing property to ignore. (3) Configure the Chart Access the chart object by its ID in the codebehind and set these values: 'create a dataview from the dataset Dim dvDataView As New DataView(DsDataSet.Tables(0)) 'create a sort order Const psoSortOrder As PointSortOrder = PointSortOrder.Ascending 'set the minimum Y value where it crosses the X axis Const dblYAxisMinValue As Double = 230 'set the maximum Y value at the top of the chart. Const dblYAxisMaxValue As Double = 280 With Chart1 'basic dimensions .Height = 350 .Width = 600 'configure the chart axis so Y-axis starts at 220 lbs With .ChartAreas("ChartArea1") .AxisY.Crossing = dblYAxisMinValue .AxisY.Minimum = dblYAxisMinValue .AxisY.Maximum = dblYAxisMaxValue End With 'borders .BorderlineDashStyle = ChartDashStyle.Solid .BorderlineColor = Color.DarkGray .BorderlineWidth = 2 'color .Palette = ChartColorPalette.EarthTones .ForeColor = Color.DarkGray With .Series.Item("Series1") 'values .XValueType = ChartValueType.Date .YValueType = ChartValueType.Int32 .YValueMembers = "weight" .XValueMember = "date" .XAxisType = AxisType.Primary 'chart type .ChartType = SeriesChartType.Column 'sort .Sort(psoSortOrder) End With 'image goop. The handler is in Web.Config. .ImageStorageMode = ImageStorageMode.UseHttpHandler .ImageType = ChartImageType.Png 'databinding .Series("Series1").Points.DataBind(dvDataView, "date", "weight", "Date=date,Weight=weight,Comment=comment, Location=location,Scale=scale,Medication=medication") '.DataBindCrossTable(dvDataView, "groupby", "date", "weight", "Tooltip=comment", psoSortOrder) .Visible = True End With Yes, you CAN nest With...End statements. Who knew? I took care of a couple of housekeeping items before pressing forward with configuring the chart. First, I created a sort order object. This gets consumed later when setting the sort for the series, and in the DataBindCrossTable method of data binding. Next, I set a pair of constants for the Y-values both at the X-axis (the minimum value) and the top of the chart (the maximum value). This value range is auto-set by default, and the minimum value is set to 0. These constants will be used to set a more readable range. Now on to the chart properties. The first two properties I assign are basic image size properties. Followed by decorations (color, border and so on.) Next, I handle all of the series configurations -- setting the data types and data members for the axes. Next come a couple of image storage properties -- one directing use of the handler in our Web.Config, and the next specifying what type of image to generate. Once all of these other settings are made, I specify the data source and bind the chart object to it. I'll talk a little about those data binding methods in just a moment. Lastly, I make sure the completed chart is visible. We wouldn't want all this hard work to go to waste. :-) (4) Data Binding I have two methods included in the example above, and wanted to explain a bit about each. The biggest "pro" I have for using DataBindCrossTable() method is that it gives you an easy way to assign data columns to series properties. Consider this statement: DataBindCrossTable(dvDataView, "groupby", "date", "weight", "Tooltip=comment", psoSortOrder) That fourth argument in the method is assigning the row data in the comment column as the value for tooltip messages in the data points. The biggest "con" I have for using DataBindCrossTable() is its requirement for a group by column. That is, the method wants to know which column is the one your data is grouped by. It is the second argument in the example call above. And there was no way to get around it -- including an empty string or a pair of quotes or "Null" would only create an unhandled exception. So my workaround was to include a blank column in my data (node, really) called "groupby", and specify it as my group by column. In my opinion, the better method to use is to bind the data down at the data points level within the series. The big "pro" here is the ability to create properties on the data points. In the example which follows, I use the fourth argument in the method to create a series of properties named for my data columns: .Series("Series1").Points.DataBind(dvDataView, "date", "weight", "Date=date,Weight=weight,Comment=comment, Location=location,Scale=scale,Medication=medication") Later I can refer to these properties by name in an event handler, like this: . . . For Each point In Chart1.Series("Series1").Points Dim strComment As String = point("Comment") Dim strMedication As String = point("Medication") Dim strLocation As String = point("Location") Dim strScale As String = point("Scale") . . . This greatly surpasses the flexibility offered by the DataBindCrossTable() method in my humble opinion. Plus, there's no requirement for a group-by column. These reasons made this method of data binding my choice for this project. (5) The Filter Control I mentioned at the top that this chart "allows different data to be 'overlayed' atop the chart using a filter control. In the broad strokes, the filter control is a dropdown control that allows the user to select any or none of the available options for additional data to be drawn on the chart. The control is populated with static values and the change in index triggers an event. The code forward is pretty basic: Here's how the control is populated in the code behind: If Not Page.IsPostBack Then With ddlFilter.Items .Add(New ListItem("None", "")) .Add(New ListItem("Location", "Location")) .Add(New ListItem("Medication", "Medication")) .Add(New ListItem("Scale", "Scale")) End With 'set default selected option ViewState("filter") = "None" End If 'If Not Page.IsPostBack The filter's SelectedIndexChanged event handler simply records the new selected item: Sub ddlFilter_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles ddlFilter.SelectedIndexChanged Dim strFilter As String = ddlFilter.SelectedItem.Text 'copy the new filter value into ViewState ViewState("filter") = strFilter End Sub (6) The Chart_DataBound Event Handler The real power behind the advanced chart is showcased in this event handler. This code, based on the selected value in the filter control: -- sets the values for the subtitle control ("Title2") and the legend label control -- iterates through the different points in the series, copies all of the properties into string variables, and then sets values in the various properties (tooltip, label, color). The full content of the event handler is posted below. It's heavily commented to help explain what's happening where. Note that the code in the Catch block mirrors that of the LoadDataSet() function at top. In the event an exception is caught, its message is displayed in red text and the chart is hidden. Sub Chart1_DataBound(ByVal sender As Object, ByVal e As EventArgs) Handles Chart1.DataBound Dim strFilter As String = CType(ViewState("filter"), String).Trim() Dim point As DataPoint pnlLegend.Visible = False Try 'chart With Chart1 'add custom title and legend text based on the filter value. 'the legend is not the chart legend -- it's a legend I made in a panel on the side. 'the chart legend for bar charts can't be manipulated -- only shows the series. If strFilter <> String.Empty Then Select Case strFilter Case "Medication" .Titles("Title2").Text = "With Prescription Weight Loss Medications" lblLegend.Text = " View of progress made with the aid of prescription weight loss medications." Case "Location" .Titles("Title2").Text = "With Measurement Location" lblLegend.Text = "Doctor's office measurements include some added weight for clothing." Case "Scale" .Titles("Title2").Text = "With Scale Descriptions" lblLegend.Text = " The old scale was less accurate than the new when compared to the doctor's office (look at the differences in bar height).

Also, the doctor's office measurements include some added weight for clothing." End Select End If 'strFilter <> String.Empty End With 'points 'set the toolip, label and color information For Each point In Chart1.Series("Series1").Points Dim strComment As String = point("Comment") Dim strMedication As String = point("Medication") Dim strLocation As String = point("Location") Dim strScale As String = point("Scale") 'set default values point.ToolTip = strComment 'the tooltip is set to the Comment property content point.Color = Color.LightGray 'the bars are colored light gray by default 'set values based on filter value If strFilter <> String.Empty Then Select Case strFilter Case "Medication" point.ToolTip += " - " & strMedication If Left(LCase(strMedication), 4) = "qsym" Then point.Color = Color.Purple End If If Left(LCase(strMedication), 4) = "phen" Then point.Color = Color.DarkOrange End If If strMedication Is Nothing Then point.LegendText = "No Medication" End If Case "Location" 'differentiate between measurements taken at the doctor's office 'and at home. If strLocation <> "home" Then point.Color = Color.DarkOrange point.ToolTip += " - " & strLocation End If Case "Scale" 'Differentiate among the old scale, the new scale, and the 'doctor's office scale. If strScale = "old" Then point.Color = Color.Navy point.ToolTip += " - " & strScale End If If strScale = "new" Then point.Color = Color.DarkOrange point.ToolTip += " - " & strScale End If If strScale Is Nothing Then point.Color = Color.DarkGray End If Case "None" point.Color = Color.DarkOrange End Select End If 'strFilter <> string.Empty Next Catch ex As Exception 'build the status message, hide the chart and panel. lblStatus.Text = ex.Message lblStatus.ForeColor = Color.Red lblStatus.Visible = True Chart1.Visible = False pnlLegend.Visible = False End Try pnlLegend.Visible = True End Sub (7) Bonus: Save Despite the configuration, I wanted to know if it was possible to save a copy of the chart into an image file. I ran this on my local machine, but haven't enabled/ attempted it in my production environment. I suspect it wouldn't work because the trust is locked down so hard. Anyway, here's a bonus subroutine for you. Sub SaveChart() '2016-10-01 'called from Chart1_DataBound() event handler 'wrapped in a Boolean variable Try Chart1.SaveImage(Server.MapPath("~/fitness/fitnessGraph.png"), ChartImageFormat.Png) Catch ex As Exception 'build the status message, hide the chart and panel. lblStatus.Text = ex.Message lblStatus.ForeColor = Color.Red lblStatus.Visible = True Chart1.Visible = False pnlLegend.Visible = False End Try End Sub Note that the code in the Catch block mirrors that of the LoadDataSet() function and the Chart1_DataBound() event handler at top. In the event an exception is caught, its message is displayed in red text and the chart is hidden. That should be it for now! Contact me using the site's contact form if you have questions. Feel free to use the code in your projects. A shout out in your project would be thoughtful. Also, drop me a line and let me know how you might have tweaked things to better suit your needs. Finally, I wouldn't profess to be THE expert on matters represented in my code -- so drop me a line if you have constructive suggestions, too. I'd like to hear from you! Best, halfgk Copyright 2016 halfgk.com