Skip to main content Skip to footer

Dragging Data Points in WPF Gantt Charts

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 :



    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

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


MESCIUS inc.

comments powered by Disqus