/// <summary> /// This methods draws the graph for the changeset history grid. /// </summary> public void Draw(ItemCollection commitList) { if (graph == null) return; Console.WriteLine("Drawing graph."); var repo = new LibGit2Sharp.Repository(repositoryViewModel.RepositoryFullPath); commitDotPositions.Clear(); SetBranchColors(repo.Branches); // Loop through all commits and draw the graph, in reverse order. commitList.MoveCurrentToLast(); // Clear the existing graph. graph.Children.Clear(); TotalHeight = 0; while (true) { Commit commit = commitList.CurrentItem as Commit; int rowNumber = commitList.CurrentPosition; if (commit == null) break; // Get a list of branches around this commit. List<Branch> branchesAroundCommit = commit.BranchesAround; // Sort the lists alphabetically. branchesAroundCommit.OrderBy(o => o.Name.ToString()); // Retrieve the index of this commit's branch on the list. This index determines the horizontal positions of dots (commit dots). int indexOfCurrentBranch; if (commit.Branches.Count > 0) indexOfCurrentBranch = branchesAroundCommit.IndexOf(commit.Branches.ElementAt(0)); else indexOfCurrentBranch = 0; int horizontalIndex = indexOfCurrentBranch + commit.VisualPosition; for (var i = indexOfCurrentBranch - 1; i >= 0; i--) horizontalIndex += ((Branch) branchesAroundCommit.ElementAt(i)).RightMostVisualPosition; // Draw the dot/ellipse based on the index of the current branch. byte dotSize = 10; byte horizontalDotSpacing = 12; int dotX = horizontalDotSpacing + dotSize * horizontalIndex + horizontalDotSpacing * horizontalIndex; int dotY = cellHeight * rowNumber + cellHeight / 2 - dotSize / 2; if (TotalHeight == 0) TotalHeight = cellHeight * (rowNumber + 1); // Store the dot position on the dictionary. commitDotPositions.Add(commit.Hash, new int[2] { dotX, dotY }); Ellipse dot = new Ellipse { Fill = Brushes.Black, StrokeThickness = 0, Width = dotSize + 2, Height = dotSize + 2 }; Canvas.SetLeft(dot, dotX - 1); Canvas.SetTop(dot, dotY - 1); Canvas.SetZIndex(dot, 1); graph.Children.Add(dot); // ToolTip for commits. var commitTooltip = new TextBlock { MaxWidth = 320, TextWrapping = TextWrapping.Wrap }; // ToolTip for paths. var pathTooltip = new TextBlock { MaxWidth = 320, TextWrapping = TextWrapping.Wrap }; if (commit.Branches.Count == 1) { pathTooltip.Text = commit.Branches.ElementAt(0).Name; } else { int i = 0, count = commit.Branches.Count; commit.Branches.ForEach(b => { i++; pathTooltip.Inlines.AddRange(new Inline[] { new Run(b.Name + (i < count ? ", " : "")), }); }); } // Regular commits have a white circle inside. if (commit.IsMergeCommit() == false && commit.ParentCount < 2) { Ellipse dotInner = new Ellipse { Fill = Brushes.White, StrokeThickness = 0, Width = dotSize, Height = dotSize }; Canvas.SetLeft(dotInner, dotX); Canvas.SetTop(dotInner, dotY); Canvas.SetZIndex(dotInner, 2); graph.Children.Add(dotInner); // ToolTip. commitTooltip.Inlines.AddRange(new Inline[] { new Run("Commit: ") {FontWeight = FontWeights.Bold}, new Run(commit.HashShort) {Foreground = highlightColor, FontWeight = FontWeights.Bold}, new LineBreak(), new Run("Author: ") {FontWeight = FontWeights.Bold}, new Run(commit.AuthorName) , new LineBreak(), new Run("Date: ") {FontWeight = FontWeights.Bold}, new Run(commit.FormattedDate), new LineBreak(), new LineBreak(), new Run(commit.Description.TrimEnd()) }); dotInner.ToolTip = commitTooltip; ToolTipService.SetShowDuration(dotInner, 60000); ToolTipService.SetInitialShowDelay(dotInner, 1); } else { // Tooltip. commitTooltip.Inlines.AddRange(new Inline[] { new Run("Merge commit: ") {FontWeight = FontWeights.Bold}, new Run(commit.HashShort) {Foreground = highlightColor, FontWeight = FontWeights.Bold}, new LineBreak(), new Run("Author: ") {FontWeight = FontWeights.Bold}, new Run(commit.AuthorName) , new LineBreak(), new Run("Date: ") {FontWeight = FontWeights.Bold}, new Run(commit.FormattedDate), new LineBreak(), new LineBreak(), new Run(commit.Description.TrimEnd()) }); dot.ToolTip = commitTooltip; ToolTipService.SetShowDuration(dot, 60000); ToolTipService.SetInitialShowDelay(dot, 1); } if (commit.Branches.Count > 0) { // Draw the line to the parent dot(s)/commit(s). foreach (string hash in commit.ParentHashes) { // Retrieve the parent commit dot position. var positions = commitDotPositions.Where(o => o.Key == hash); if (positions.Count() > 0) { int[] parentPosition = commitDotPositions.Where(o => o.Key == hash).First().Value; Brush lineColor = BranchColors[commit.Branches.ElementAt(0).Name]; // Calculate line positions. float startLineX1 = dotX + dotSize / 2; float startLineY1 = dotY + dotSize / 2; float endLineX2 = parentPosition[0] + dotSize / 2; float endLineY2 = parentPosition[1] + dotSize / 2; float startLineX2; float startLineY2; float endLineX1; float endLineY1; if (commit.IsMergeCommit()) { startLineX2 = endLineX2; startLineY2 = startLineY1; endLineX1 = endLineX2; endLineY1 = startLineY1; } else { startLineX2 = startLineX1; startLineY2 = parentPosition[1] - cellHeight / 2 + dotSize / 2 + 6; endLineX1 = startLineX1; endLineY1 = parentPosition[1] - cellHeight / 2 + dotSize / 2 + 12; } // Construct and draw the line path. Path path = new Path { Stroke = lineColor, StrokeThickness = 4, Data = new PathGeometry { Figures = new PathFigureCollection { new PathFigure { StartPoint = new Point(startLineX1, startLineY1), Segments = new PathSegmentCollection { new PolyBezierSegment { Points = new PointCollection { new Point(startLineX2, startLineY2), new Point(endLineX1, endLineY1), new Point(endLineX2, endLineY2) } } } } } } }; graph.Children.Add(path); path.ToolTip = pathTooltip; ToolTipService.SetShowDuration(path, 60000); ToolTipService.SetInitialShowDelay(path, 1); } } } commitList.MoveCurrentToPrevious(); if (commitList.IsCurrentBeforeFirst) { break; } } repo.Dispose(); }