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.




Step 1 : Add a WPF Chart and bind it to a Data Source


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 :

<pre>    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:

<pre>    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

Step 3 : Track Mouse Left Button on the Data Point


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

Step 4: Moving the temporary border object with mouse cursor on the Chart


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

Step 5: Changing the actual DataPoint location after Mouse Left button is released


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