Considered revolutionary when they were introduced, Gantt Charts have now become a common requirement for any organization when it comes to representing the phases and activities of any of their projects. ComponentOne WPF Chart control provides you with a similar option with one of its Chart type as Gantt Chart. Although it is a common technique nowadays and you may find various ways of using a Gantt Chart, think of a situation where you need to manipulate the Gantt points by dragging them from one location to another or resizing a particular time period. Through this article, you'll be able to understand how to implement moving the points (representing time period) or resizing them. The complete implementation would require 5 steps :
1. Creating a Chart control, changing the Charttype to Gantt and binding to Data source 2. Customize the appearance of Data Points in PlotElementLoaded event 3. Track the Data point start position in MouseLeftButtonDown event 4. Moving the temporary border object with mouse cursor on the Chart in MouseMove event 5. Determine the final position of data point after dragging.
Lets begin with placing ComponentOne Wpf Chart on the page. We will create two date time arrays, which keep track of the Start and End time for an individual time duration, for Chart binding. Set the ChartType to Gantt and set these time arrays as HighLowSeries and add to the ChartData. XAML code: Code :
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
c1Chart1.BeginUpdate()
c1Chart1.Reset(True)
c1Chart1.ChartType = C1.WPF.C1Chart.ChartType.Gantt
Dim lowValueSource(10) As DateTime
For i As Integer = 0 To 10
lowValueSource(i) = Now.AddDays(i).AddHours(1)
Next
Dim highValueSource(10) As DateTime
For i As Integer = 0 To 10
highValueSource(i) = Now.AddDays(i 1).AddHours(2)
Next
Dim mlist As New List(Of String)
For k As Integer = 0 To 10
Dim ds As New C1.WPF.C1Chart.HighLowSeries
Dim lowlist As New ArrayList()
lowlist.Add(lowValueSource(k))
ds.LowValuesSource = lowlist
Dim highlist As New ArrayList()
highlist.Add(highValueSource(k))
ds.HighValuesSource = highlist
c1Chart1.Data.Children.Add(ds)
mlist.Add("Data" k.ToString())
AddHandler ds.PlotElementLoaded, AddressOf c1Chart1_PlotElementLoaded
Next
c1Chart1.Data.ItemNames = mlist
c1Chart1.View.AxisY.Reversed = True
c1Chart1.View.AxisY.MinorGridStroke = New SolidColorBrush(Colors.Red)
c1Chart1.View.AxisY.MajorGridStrokeThickness = 0
c1Chart1.EndUpdate()
AddHandler c1Chart1.MouseMove, AddressOf c1Chart1_MouseMove
AddHandler c1Chart1.MouseLeftButtonUp, AddressOf c1Chart1_MouseLeftButtonUp
End Sub
### Step 2 : Set the Custom Image when mouse moves over Chart points
Once we get through the first step of binding a chart to data source, then we define the PlotElementLoaded event. This event helps in customizing the appearance of each Data point plotted on the chart. **Code:**
Public Sub c1Chart1_PlotElementLoaded(ByVal sender As Object, ByVal e As EventArgs)
Dim ps As PlotElement = CType(sender, PlotElement)
If ps.DataPoint.PointIndex = 0 Then
Dim ds As HighLowSeries = CType(c1Chart1.Data.Children(ps.DataPoint.SeriesIndex), HighLowSeries)
Dim starttime As DateTime = CType(CType(ds.LowValuesSource, ArrayList)(0), DateTime)
Dim endtime As DateTime = CType(CType(ds.HighValuesSource, ArrayList)(0), DateTime)
If endtime.Subtract(starttime).TotalSeconds() = 1 Then
ps.Visibility = Windows.Visibility.Collapsed
End If
End If
Dim pbar As HLBar = DirectCast(sender, C1.WPF.C1Chart.HLBar)
pbar.RadiusX = 2
pbar.RadiusY = 2
AddHandler ps.MouseMove, AddressOf PlotElement_MouseMove
AddHandler ps.MouseLeftButtonDown, AddressOf PlotElement_MouseLeftButtonDown
End Sub
The next part is to set images to be displayed when the mouse is hovered over the chart data points. In the current sample we will customize "Resizing" and "Dragging" cursor images. Code:
Private Sub PlotElement_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs)
If e.GetPosition(sender).X > (CType(sender, PlotElement).ActualWidth - 3) Or e.GetPosition(sender).X < 3 Then
CType(sender, HLBar).Cursor = Cursors.SizeWE
Else
CType(sender, HLBar).Cursor = Cursors.SizeAll
End If
End Sub
This sample involves some Global variables to be declared, which are used to store different values. Code:
' Whether dragging is done on the chart
Dim IsDragging As Boolean = False
' Current DataPoint being dragged or resized
Dim currentPlotElement As PlotElement
' Temporary Rectangle which shows the movememnt of datapoint
Dim tempDraggingRectange As Border
' Direction in which DataPoint is stretched (Right or Left)
Dim draggingDirection As String
' Difference in Final Resized point from Left of DataPoint
Dim xstartdiff As Double = -1
' Difference in Final Resized point from Right of DataPoint
Dim xendDiff As Double = -1
Now comes the point when we handle the actual mouse actions made by the user by subscribing the MouseLeftButtonDown event. Once that is determined correctly, we need to choose between invoking dragging or resizing operations. As a visual enhancement, I would suggest we show a temporary border around the data points so that the user can see the line action. Code:
Private Sub PlotElement_MouseLeftButtonDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
currentPlotElement = CType(sender, PlotElement)
Dim pt As Point = e.GetPosition(sender)
xstartdiff = pt.X - currentPlotElement.RenderedGeometry.Bounds.Left
xendDiff = currentPlotElement.RenderedGeometry.Bounds.Right - pt.X
If pt.X > (CType(sender, PlotElement).ActualWidth - 3) Or pt.X (CType(sender, PlotElement).ActualWidth - 3) Then
draggingDirection = "End"
Else
draggingDirection = "Start"
End If
IsDragging = True
End If
Dim ps As PlotElement = CType(sender, PlotElement)
tempDraggingRectange = New Border()
tempDraggingRectange.Background = ps.Fill
tempDraggingRectange.BorderBrush = ps.Stroke
tempDraggingRectange.Height = ps.ActualHeight
tempDraggingRectange.Width = ps.ActualWidth
tempDraggingRectange.CornerRadius = New CornerRadius(2)
Canvas.SetLeft(tempDraggingRectange, Canvas.GetLeft(ps))
Canvas.SetTop(tempDraggingRectange, Canvas.GetTop(ps))
Dim pnl As Panel = CType(ps.Parent, Panel)
pnl.Children.Add(tempDraggingRectange)
currentPlotElement.Visibility = Windows.Visibility.Hidden
End Sub
Now we need to track the MouseMove event of C1Chart to capture the movement of the data point being dropped. In this, we will set the position of the Cancas as well as capture the dragging direction of the datapoint. Code:
Private Sub c1Chart1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs)
If e.LeftButton Then
Dim pt As Point = e.GetPosition(c1Chart1)
If IsDragging Then
If draggingDirection = "End" Then
If pt.X > Canvas.GetLeft(tempDraggingRectange) Then
tempDraggingRectange.Width = pt.X - Canvas.GetLeft(tempDraggingRectange)
Else
e.Handled = True
End If
ElseIf draggingDirection = "Start" Then
Dim val As Double = currentPlotElement.ActualWidth (Canvas.GetLeft(currentPlotElement) - pt.X)
If val > 0 Then
tempDraggingRectange.Width = currentPlotElement.ActualWidth (Canvas.GetLeft(currentPlotElement) - pt.X)
Canvas.SetLeft(tempDraggingRectange, pt.X)
Else
e.Handled = True
End If
End If
Else
If tempDraggingRectange IsNot Nothing Then
Canvas.SetLeft(tempDraggingRectange, pt.X - xstartdiff)
Canvas.SetTop(tempDraggingRectange, pt.Y - 2)
End If
End If
End If
If IsDragging Then
Me.Cursor = Cursors.SizeWE
Else
Me.Cursor = Nothing
End If
End Sub
Finally, we need to find the the final position of the data point after the dragging in MouseLeftButtonUp event. For this, we require moving the actual DataPoint values from its original DataSeries to the DataSeries for the final location to maintain the consistency in the appearance and Data for the chart. ? We will use PointToData() to determine the Point where new data point will be placed. 'X' value will give us the Time where data will be placed. We will create a new data point at the position and remove the old point. Code:
Private Sub c1Chart1_MouseLeftButtonUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
If IsDragging = True Then
IsDragging = False
Dim ds As HighLowSeries = currentPlotElement.DataPoint.Series
Dim dt As DateTime = DateTime.FromOADate(c1Chart1.View.PointToData(e.GetPosition(CType(currentPlotElement.Parent, Panel))).X)
If draggingDirection = "Start" Then
CType(ds.LowValuesSource, ArrayList).Item(currentPlotElement.DataPoint.PointIndex) = dt
ElseIf draggingDirection = "End" Then
CType(ds.HighValuesSource, ArrayList).Item(currentPlotElement.DataPoint.PointIndex) = dt
End If
Dim lb As ArrayList = CType(ds.LowValuesSource, ArrayList).Clone()
ds.LowValuesSource = lb
Dim hb As ArrayList = CType(ds.HighValuesSource, ArrayList).Clone()
ds.HighValuesSource = hb
CType(currentPlotElement.Parent, Panel).Children.Remove(tempDraggingRectange)
tempDraggingRectange = Nothing
draggingDirection = String.Empty
Me.Cursor = Nothing
currentPlotElement.Visibility = Windows.Visibility.Visible
currentPlotElement = Nothing
Exit Sub
End If
If currentPlotElement IsNot Nothing Then
'Calculate the DateTime value for the new location using 'PointToData()' method
Dim startdt As DateTime = DateTime.FromOADate(c1Chart1.View.PointToData(New Point(e.GetPosition(c1Chart1).X - xstartdiff, e.GetPosition(c1Chart1).Y)).X)
Dim enddt As DateTime = DateTime.FromOADate(c1Chart1.View.PointToData(New Point(e.GetPosition(c1Chart1).X xendDiff, e.GetPosition(c1Chart1).Y)).X)
Dim db As Integer = c1Chart1.View.PointToData(e.GetPosition(c1Chart1)).Y
If db > -1 Then
Dim ds As HighLowSeries = CType(c1Chart1.Data.Children(db), HighLowSeries)
Dim old_series As HighLowSeries = currentPlotElement.DataPoint.Series
CType(ds.LowValuesSource, ArrayList).Add(startdt)
CType(ds.HighValuesSource, ArrayList).Add(enddt)
Dim lb As ArrayList = CType(ds.LowValuesSource, ArrayList).Clone()
ds.LowValuesSource = lb
Dim hb As ArrayList = CType(ds.HighValuesSource, ArrayList).Clone()
ds.HighValuesSource = hb
Else
Dim ds As New C1.WPF.C1Chart.HighLowSeries
Dim lowlist As New ArrayList()
lowlist.Add(startdt)
ds.LowValuesSource = lowlist
Dim highlist As New ArrayList()
highlist.Add(enddt)
ds.HighValuesSource = highlist
AddHandler ds.PlotElementLoaded, AddressOf c1Chart1_PlotElementLoaded
c1Chart1.Data.Children.Add(ds)
End If
'Remove the original GANTT point
Dim oldseries As HighLowSeries = CType(c1Chart1.Data.Children(currentPlotElement.DataPoint.SeriesIndex), HighLowSeries)
CType(oldseries.LowValuesSource, ArrayList).RemoveAt(currentPlotElement.DataPoint.PointIndex)
CType(oldseries.HighValuesSource, ArrayList).RemoveAt(currentPlotElement.DataPoint.PointIndex)
If CType(oldseries.LowValuesSource, ArrayList).Count = 0 Then
CType(oldseries.LowValuesSource, ArrayList).Add(Now.Date())
CType(oldseries.HighValuesSource, ArrayList).Add(Now.Date().AddSeconds(1))
End If
currentPlotElement = Nothing
End If
End Sub
For the complete implementation, refer to the attached sample application. Download Sample