Visual Sorting Algorithms part II
In the days before the internet, email and cell phones:
My ice hockey team was in the locker room talking about skating when the topic of a video showing me roller staking in Boston in 1980.
https://www.youtube.com/watch?v=IxlV_lOLmrk
Below is the new version that uses a background thread to do the work and allows multiple length data.
In this screenshot is the QuickSort running across 3 monitors was interrupted when about halfway done. The top half has already been sorted (no bolded items)
I wrote some simple tests to verify the algorithms worked and output the performance results to an Excel pivot chart
The multiple sorting algorithms have huge performance differences. The better performing tests were skewed toward the ones with the fewest screen updates, so the test code can change the visibility of the items when run.
It’s interesting that several of the algorithms were invented since my first implementation in the 1980’s
Start Visual Studio. (most versions)
File->New->Project->c#->Windows->WPF Application
replace the Mainwindow.Xaml.cs code with the code below
<SortingCode>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public static string[] _SortTypes =
{
"Bubble",
"Unknown",
"Stupid",
"Insertion",
"Selection",
"OddEven",
"Cycle",
"Merge",
"MergeIP",
"MergeIP2",
"Quick",
"Shell",
"Heap",
"Super",
};
public MainWindow()
{
InitializeComponent();
this.WindowState = WindowState.Maximized;
this.Title = "Visual Sorter";
this.Loaded += MainWindow_Loaded;
}
public static CancellationTokenSource _cancelTokSrc;
public static bool _ShowSort = true;
internal Canvas _canvas = new Canvas();
public static int _spControlsHeight = 60;
public int _nRows;
public class SortBox : Label, IComparable
{
public struct Stats
{
public DateTime startTime;
public int numItems;
public long numCompares;
public long numReads;
public long numWrites;
public int MaxDepth;
public override string ToString()
{
var elapsed = (DateTime.Now - startTime).TotalSeconds;
var strDepth = string.Empty;
if (MaxDepth >= 0)
{
strDepth = $" MaxDepth= {MaxDepth}";
}
return $"Secs= {elapsed,9:n3} Items= {numItems,8} Compares= {numCompares,13:n0} Reads= {numReads,13:n0} Writes= {numWrites,13:n0}{strDepth}";
}
}
public static Stats stats;
internal static void InitStats(int nTotal)
{
stats = new Stats()
{
numItems = nTotal,
startTime = DateTime.Now,
MaxDepth = -1
};
}
public SortBox()
{
// this.FontSize = 8;
}
public void Swap(SortBox other)
{
if (this != other)
{
var temp = this.Data;
this.Data = other.Data;
other.Data = temp;
}
}
private string _data;
public string Data
{
get
{
stats.numReads++;
return _data;
}
set
{
_data = value;
stats.numWrites++;
this.Update();
}
}
public static bool operator <(SortBox a, SortBox b)
{
stats.numCompares++;
if (string.CompareOrdinal(a.Data, b.Data) < 0)
{
return true;
}
return false;
}
public static bool operator >(SortBox a, SortBox b)
{
stats.numCompares++;
if (string.CompareOrdinal(a.Data, b.Data) > 0)
{
return true;
}
return false;
}
public void Update()
{
if (_ShowSort)
{
bool fCancel = false;
Dispatcher.Invoke(() =>
{
// set the content on the UI thread
this.Content = this.Data;
if (_cancelTokSrc != null)
{
// check input queue for messages
var stat = GetQueueStatus(4);
if (stat != 0)
{
//need to throw on right thread
fCancel = true;
}
}
});
if (fCancel)
{
//need to throw on right thread
_cancelTokSrc.Cancel();
throw new TaskCanceledException("User cancelled");
}
}
}
public override string ToString()
{
return $"{this.Data}";
}
public int CompareTo(object obj)
{
stats.numCompares++;
var other = obj as SortBox;
if (other != null)
{
return string.CompareOrdinal(this.Data, other.Data);
}
var otherAsString = obj as string;
return string.CompareOrdinal(this.Data, otherAsString);
}
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
try
{
_nRows = ((int)this.ActualHeight - _spControlsHeight - 50) / 10;
this.Content = _canvas;
var spControls = new StackPanel()
{
Orientation = Orientation.Horizontal,
MaxHeight = _spControlsHeight
};
_canvas.Children.Add(spControls);
var btnSort = new Button()
{
Content = "Do_Sort",
ToolTip = "Will generate data and sort. Click to cancel. LeftShift-Click to continue an aborted sort, possibly with a different algorithm",
Height = 20,
VerticalAlignment = VerticalAlignment.Top
};
spControls.Children.Add(btnSort);
var cboSortType = new ComboBox()
{
ItemsSource = _SortTypes,
Height = 20,
VerticalAlignment = VerticalAlignment.Top,
Width = 150,
};
cboSortType.SelectedIndex = 8;
spControls.Children.Add(cboSortType);
var txtNumItems = new TextBox()
{
Text = "4000",
ToolTip = "Max Number of items to sort. (limited by display)",
Height = 20,
VerticalAlignment = VerticalAlignment.Top,
Width = 100
};
spControls.Children.Add(txtNumItems);
var spVertControls = new StackPanel()
{
Orientation = Orientation.Vertical
};
spControls.Children.Add(spVertControls);
var chkLettersOnly = new CheckBox()
{
Content = "_Letters only",
ToolTip = "Include just letters or other characters too",
IsChecked = true
};
spVertControls.Children.Add(chkLettersOnly);
var chkShowSort = new CheckBox()
{
Content = "Show ",
ToolTip = "Update display during sort. Turn this off to see performance without updaating",
IsChecked = true
};
chkShowSort.Checked += (os, es) => _ShowSort = true;
chkShowSort.Unchecked += (os, es) => _ShowSort = false;
spVertControls.Children.Add(chkShowSort);
var txtMaxDataLength = new TextBox()
{
Text = "0",
Width = 40,
ToolTip = "Max # of random chars per datum. 0 means use a dictionary of real words"
};
spVertControls.Children.Add(txtMaxDataLength);
var txtStatus = new TextBox()
{
Margin = new Thickness(10, 0, 0, 0),
Width = 900,
Height = _spControlsHeight,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto
};
Action<string> addStatusMsg = (str) =>
{
Dispatcher.BeginInvoke(new Action(
() =>
{
txtStatus.AppendText($"{str}\r\n");
txtStatus.ScrollToEnd();
}
));
};
spControls.Children.Add(txtStatus);
List<SortBox> arrData = null;
int nTotal = 0;
btnSort.Click += (ob, eb) =>
{
_cancelTokSrc = new CancellationTokenSource();
btnSort.IsEnabled = false;
var sortType = (string)cboSortType.SelectedValue;
if (arrData == null ||
!System.Windows.Input.Keyboard.IsKeyDown(System.Windows.Input.Key.LeftShift)
)
{
// lets create controls on main thread
_canvas.Children.Clear();
_canvas.Children.Add(spControls);
nTotal = int.Parse(txtNumItems.Text);
arrData = new List<SortBox>();
var maxDatalength = int.Parse(txtMaxDataLength.Text);
arrData = InitData(ref nTotal, maxDatalength, chkLettersOnly.IsChecked.Value);
addStatusMsg($"Starting {sortType} with {nTotal} items. Click anywhare to stop");
}
else
{// user left shift-click: continue sorting with a possible different algorithm
// note: cancellation can result in slight data errors because exception thrown
for (int i = 1; i < nTotal; i++)
{
if (arrData[i] < arrData[i - 1])
{
arrData[i].FontWeight = FontWeights.Normal;
}
}
addStatusMsg($"Continuing with {sortType} {nTotal} items. Click anywhare to stop");
}
var tsk = Task.Run(() =>
{
// do the sorting on a background thread
try
{
DoTheSorting(arrData, sortType, nTotal);
}
catch (TaskCanceledException)
{
}
catch (Exception ex)
{
addStatusMsg($"Exception {ex.ToString()}");
}
txtStatus.Dispatcher.BeginInvoke(new Action(
() =>
{
// now that we're done, let's verify
var stats = SortBox.stats;
var hasError = ValidateSorted(arrData, nTotal);
if (!_ShowSort)
{
// show sorted results
for (int i = 0; i < nTotal; i++)
{
arrData[i].Content = arrData[i].Data;
}
}
string donemsg = _cancelTokSrc.IsCancellationRequested ? "Aborted" : "Done ";
addStatusMsg($"{sortType,10} {donemsg} {stats} {hasError}");
btnSort.IsEnabled = true;
}));
});
};
btnSort.RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this));
}
catch (Exception ex)
{
this.Content = ex.ToString();
}
}
public string ValidateSorted(List<SortBox> arrData, int nTotal)
{
var hasError = string.Empty;
int nErrors = 0;
for (int i = 1; i < nTotal; i++)
{
if (arrData[i] < arrData[i - 1])
{
nErrors++;
arrData[i].FontWeight = FontWeights.ExtraBold;
}
}
if (nErrors > 0)
{
hasError = $"Error! {nErrors} not sorted";
}
return hasError;
}
public List<SortBox> InitData(ref int nTotal, int maxDatalength, bool ltrsOnly)
{
var arrData = new List<SortBox>();
var rand = new Random(1);
/* to use without the dictionary (using random letters) comment the current line and the new Dictionary line below
dynamic dict = null;
maxDatalength = 5;
/*/
// get the dictionary from my OneDrive:
// https://onedrive.live.com/redir?resid=D69F3552CEFC21!99083&authkey=!AFjyjUlZpH5sQy0&ithint=file%2cdll
// then run the command RegSvr32 dictionary.dll
// then add a reference to COM ->Dictionary 1.0 Type Library
Dictionary.CDict dict = null;
//*/
int colWidth;
// we will try to fill the screen with sort data.
// However, there are times when a particular # of items is desired,
// such as debugging a list of 5 items.
// so we limit by nTotal or screen capacity
if (maxDatalength == 0)
{
dict = new Dictionary.CDict(); // comment out this line if no dictionary
dict.DictNum = 2;
maxDatalength = 11;
colWidth = 8 * (maxDatalength - 1);
}
else
{
colWidth = 20 + 8 * (maxDatalength - 1);
}
int nCols = (int)this.ActualWidth / colWidth;
if (nCols == 0) //tests
{
nCols = 80;
}
for (int i = 0; i < _nRows; i++)
{
for (int j = 0; j < nCols; j++)
{
if (arrData.Count < nTotal)
{
string dat = string.Empty;
switch (arrData.Count)
{
// set the first few items to const so easier to debug algorithms
case 0:
dat = "zero";
break;
case 1:
dat = "one";
break;
case 2:
dat = "two";
break;
case 3:
dat = "three";
break;
case 4:
dat = "four";
break;
case 5:
dat = "five";
break;
default:
var len = 1 + rand.Next(maxDatalength);
var datarray = new char[len];
for (int k = 0; k < len; k++)
{
// "A" is 65,"!" is 33
datarray[k] = (char)(ltrsOnly == true ?
65 + rand.Next(26) :
33 + rand.Next(90));
}
dat = new string(datarray);
break;
}
var box = new SortBox();
if (dict == null)
{
box.Data = dat.Substring(0, Math.Min(maxDatalength, dat.Length));
}
else
{
box.Data = dict.RandWord(0);
}
box.Content = box.Data;
arrData.Add(box);
if (_ShowSort)
{
Canvas.SetTop(box, 3 + _spControlsHeight + i * 10);
Canvas.SetLeft(box, j * colWidth);
_canvas.Children.Add(box);
}
}
}
}
nTotal = arrData.Count; // could be less
if (!_ShowSort)
{
// show initial values
for (int i = 0; i < nTotal; i++)
{
arrData[i].Content = arrData[i].Data;
}
}
SortBox.InitStats(nTotal);
return arrData;
}
public void DoTheSorting(List<SortBox> arrData, string sortType, int nTotal)
{
switch (sortType)
{
case "Bubble":
var nEnd = nTotal;
var newEnd = 0;
do
{
newEnd = 0;
for (int i = 1; i < nEnd; i++)
{
if (arrData[i - 1] > arrData[i])
{
arrData[i - 1].Swap(arrData[i]);
newEnd = i;
}
}
nEnd = newEnd;
} while (newEnd != 0);
break;
case "Unknown":
for (int i = 1; i < nTotal; i++)
{
for (int j = 0; j < i; j++)
{
if (arrData[i] < arrData[j])
{
arrData[i].Swap(arrData[j]);
}
}
}
break;
case "Stupid": // gnomeSort
for (int i = 0; i < nTotal;)
{
if (i == 0 || !(arrData[i - 1] > arrData[i]))
{
i++;
}
else
{
arrData[i - 1].Swap(arrData[i]);
i--;
}
}
break;
case "Insertion":
for (int i = 1; i < nTotal; i++)
{
var t = arrData[i].Data;
int j = i - 1;
for (; j >= 0 && arrData[j].CompareTo(t) > 0; j--)
{
arrData[j + 1].Data = arrData[j].Data;
}
//int j = i - 1;
//while (j >= 0 && arrData[j].CompareTo(t) > 0)
//{
// arrData[j + 1].Data = arrData[j].Data;
// j--;
//}
arrData[j + 1].Data = t;
}
break;
case "Selection":
// scan entire array for minimum, swap with 1st, then repeat for rest
for (int i = 0; i < nTotal - 1; i++)
{
int minimum = i;
for (int j = i + 1; j < nTotal; j++)
{
if (arrData[j] < arrData[minimum])
{
minimum = j;
}
}
if (minimum != i)
{
arrData[i].Swap(arrData[minimum]);
}
}
break;
case "OddEven":
var sorted = false;
while (!sorted)
{
sorted = true;
for (int i = 1; i < nTotal - 1; i += 2)
{
if (arrData[i + 1] < arrData[i])
{
arrData[i + 1].Swap(arrData[i]);
sorted = false;
}
}
for (int i = 0; i < nTotal - 1; i += 2)
{
if (arrData[i + 1] < arrData[i])
{
arrData[i + 1].Swap(arrData[i]);
sorted = false;
}
}
}
break;
case "Cycle":
// deceptively fast because fewer updates,
// which are expensive in this code because of thread
// context switches and drawing updated data
// note the # of updates <= ntotal
for (int cycleStart = 0; cycleStart < nTotal; cycleStart++)
{
// the item to place
var item = arrData[cycleStart].Data;
int pos = cycleStart;
do
{
int nextPos = 0;
// find it's position # in entire array
// by finding how many should precede it
for (int i = 0; i < nTotal; i++)
{
if (i != cycleStart && arrData[i].CompareTo(item) < 0)
{
nextPos++;
}
}
// if it's not in the correct position
if (pos != nextPos)
{
// move past duplicates
while (pos != nextPos && arrData[nextPos].CompareTo(item) == 0)
{
nextPos++;
}
// save the cur value at nextpos
var temp = arrData[nextPos].Data;
// set the value at nextpos to the item to place
arrData[nextPos].Data = item;
// new value for which to seek position
item = temp;
pos = nextPos;
}
} while (pos != cycleStart);
}
break;
case "Merge":
// not in place: uses additional storage
// lets make a recursive lambda
Action<int, int, int> MergeSort = null;
MergeSort = (left, right, depth) =>
{
SortBox.stats.MaxDepth = Math.Max(depth, SortBox.stats.MaxDepth);
if (right > left)
{
int mid = (right + left) / 2;
MergeSort(left, mid, depth + 1);
mid++;
MergeSort(mid, right, depth + 1);
// now we merge 2 sections that are already sorted
int leftNdx = left;
// use extra storage
var temp = new List<string>();
int pivot = mid;
while (leftNdx < pivot && mid <= right)
{
// fill temp from left or right
if (arrData[mid] < arrData[leftNdx])
{
temp.Add(arrData[mid++].Data);
}
else
{
temp.Add(arrData[leftNdx++].Data);
}
}
// deal with leftovers on left or right
while (leftNdx < pivot)
{
temp.Add(arrData[leftNdx++].Data);
}
while (mid <= right)
{
temp.Add(arrData[mid++].Data);
}
// fill the elements with the sorted list
for (int i = 0; i < temp.Count; i++)
{
arrData[left + i].Data = temp[i];
}
}
};
MergeSort(0, nTotal - 1, 0);
break;
case "MergeIP":
Action<int, int, int> MergeSortIp = null;
MergeSortIp = (left, right, depth) =>
{
SortBox.stats.MaxDepth = Math.Max(depth, SortBox.stats.MaxDepth);
if (left >= right)
{
return;
}
int mid = (left + right) / 2;
MergeSortIp(left, mid, depth + 1);
mid++;
MergeSortIp(mid, right, depth + 1);
int leftNdx = left;
int pivot = mid;
int leftEnd = pivot - 1;
while (leftNdx <= leftEnd && mid <= right)
{
if (arrData[leftNdx] < arrData[mid])
{
// left already in place
leftNdx++;
}
else
{
// take from right: shift everyone over to make room
var temp = arrData[mid].Data;
for (int j = mid - 1; j >= leftNdx; j--)
{
arrData[j + 1].Data = arrData[j].Data;
}
arrData[leftNdx].Data = temp;
leftNdx++;
leftEnd++;
mid++;
}
}
};
MergeSortIp(0, nTotal - 1, 0);
break;
case "MergeIP2":
//https://stackoverflow.com/questions/2571049/how-to-sort-in-place-using-the-merge-sort-algorithm/22839426#22839426
Action<int, int> reverse = (a, b) =>
{
for (--b; a < b; a++, b--)
{
arrData[a].Swap(arrData[b]);
}
};
Func<int, int, int, int> rotate = (a, b, c) =>
{
//* swap the sequence [a,b) with [b,c).
if (a != b && b != c)
{
reverse(a, b);
reverse(b, c);
reverse(a, c);
}
return a + c - b;
};
Func<int, int, SortBox, int> lower_bound = (a, b, key) =>
{
//* find first element not less than @p key in sorted sequence or end of
// * sequence (@p b) if not found.
for (int i = b - a; i != 0; i /= 2)
{
int mid = a + i / 2;
if (arrData[mid] < key)
{
a = mid + 1;
i--;
}
}
return a;
};
Func<int, int, SortBox, int> upper_bound = (a, b, key) =>
{
///* find first element greater than @p key in sorted sequence or end of
//* sequence (@p b) if not found.
for (int i = b - a; i != 0; i /= 2)
{
int mid = a + i / 2;
if (arrData[mid].CompareTo(key) <= 0)
{
a = mid + 1;
i--;
}
}
return a;
};
Action<int, int, int, int> mergeInPlace = null;
mergeInPlace = (left, mid, right, depth) =>
{
SortBox.stats.MaxDepth = Math.Max(depth, SortBox.stats.MaxDepth);
int n1 = mid - left;
int n2 = right - mid;
if (n1 == 0 || n2 == 0)
{
return;
}
if (n1 == 1 && n2 == 1)
{
if (arrData[mid] < arrData[left])
{
arrData[mid].Swap(arrData[left]);
}
}
else
{
int p, q;
if (n1 <= n2)
{
q = mid + n2 / 2;
p = upper_bound(left, mid, arrData[q]);
}
else
{
p = left + n1 / 2;
q = lower_bound(mid, right, arrData[p]);
}
mid = rotate(p, mid, q);
mergeInPlace(left, p, mid, depth + 1);
mergeInPlace(mid, q, right, depth + 1);
}
};
Action<int, int, int> inPlaceMergeSort = null;
inPlaceMergeSort = (left, nElem, depth) =>
{
if (nElem > 1)
{
int mid = nElem / 2;
inPlaceMergeSort(left, mid, depth + 1);
inPlaceMergeSort(left + mid, nElem - mid, depth + 1);
mergeInPlace(left, left + mid, left + nElem, depth + 1);
}
};
inPlaceMergeSort(0, nTotal, 0);
break;
case "Quick":
Action<int, int, int> quickSort = null;
quickSort = (left, right, depth) =>
{
SortBox.stats.MaxDepth = Math.Max(depth, SortBox.stats.MaxDepth);
if (left < right)
{
var pivot = arrData[left];
int i = left;
int j = right;
while (i < j)
{
// find the leftmost one that should be on the right
while (i < right && !(arrData[i] > pivot))
{
i++;
}
// set j to the rightmost one that should be on the left
while (arrData[j] > pivot)
{
j--;
}
if (i < j)
{
arrData[i].Swap(arrData[j]);
}
}
// now put pivot into place
pivot.Swap(arrData[j]);
// now recur to sort left, then right sides
quickSort(left, j - 1, depth + 1);
quickSort(j + 1, right, depth + 1);
}
};
// now do the actual sort
quickSort(0, nTotal - 1, 0);
break;
case "Shell":
for (int g = nTotal / 2; g > 0; g /= 2)
{
for (int i = g; i < nTotal; i++)
{
for (int j = i - g; j >= 0 && arrData[j] > arrData[j + g]; j -= g)
{
arrData[j].Swap(arrData[j + g]);
}
}
}
break;
case "Heap":
// https://simpledevcode.wordpress.com/2014/11/25/heapsort-c-tutorial/
int heapSize = 0;
Action<int, int> Heapify = null;
Heapify = (index, depth) =>
{
SortBox.stats.MaxDepth = Math.Max(SortBox.stats.MaxDepth, depth);
int left = 2 * index;
int right = 2 * index + 1;
int largest = index;
if (left <= heapSize && arrData[left] > arrData[index])
{
largest = left;
}
if (right <= heapSize && arrData[right] > arrData[largest])
{
largest = right;
}
if (largest != index)
{
arrData[index].Swap(arrData[largest]);
Heapify(largest, depth + 1);
}
};
heapSize = nTotal - 1;
for (int i = nTotal / 2; i >= 0; i--)
{
Heapify(i, 0);
}
for (int i = nTotal - 1; i >= 0; i--)
{
arrData[0].Swap(arrData[i]);
heapSize--;
Heapify(0, 0);
}
break;
case "Super":
var data = (from dat in arrData
orderby dat
select dat.Data).ToArray();
for (int i = 0; i < nTotal; i++)
{
arrData[i].Data = data[i];
}
break;
}
}
[DllImport("user32.dll")]
static extern uint GetQueueStatus(uint flags);
}
}
</SortingCode>
<TestCode>
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using WpfApplication1;
using System.Linq;
namespace SorterTests
{
[TestClass]
public class UnitTest1
{
public TestContext TestContext { get; set; }
[TestMethod]
public void TestMethod1()
{
MainWindow._ShowSort = false;
TestContext.WriteLine("Start");
foreach (var sortType in MainWindow._SortTypes.Where(
/*
t => true
/*/
t =>
t != "Cycle"
&& t != "Bubble"
&& t != "Unknown"
&& t != "Stupid"
&& t != "Selection"
&& t != "Insertion"
&& t != "MergeIP"
&& t != "OddEven"
//*/
))
{
for (int pow = 0, nTotal = 1; pow < 7; pow++)
{
var mwindow = new MainWindow();
mwindow._nRows = 1000000; // don't limit data by # rows
var arrData = mwindow.InitData(ref nTotal, maxDatalength: 4, ltrsOnly: false);
mwindow.DoTheSorting(arrData, sortType, nTotal);
var errs = mwindow.ValidateSorted(arrData, nTotal);
Assert.IsTrue(string.IsNullOrEmpty(errs), $"{sortType} {errs}");
TestContext.WriteLine($"{sortType,10} {MainWindow.SortBox.stats}");
nTotal *= 10;
}
}
}
/*
ActiveSheet.PasteSpecial Format:="Unicode Text", Link:=False, _
DisplayAsIcon:=False
Range("A1:B5").Select
Selection.EntireRow.Delete
Columns("B:P").Select
Columns("B:P").EntireColumn.AutoFit
Range("C:C,E:E,G:G,I:I,K:K,M:M,O:O").Select
Range("O1").Activate
Selection.EntireColumn.Hidden = True
Range("B1").Select
Selection.EntireRow.Insert , CopyOrigin:=xlFormatFromLeftOrAbove
ActiveCell.FormulaR1C1 = "Sort"
Range("D1").Select
ActiveCell.FormulaR1C1 = "Secs"
Range("F1").Select
ActiveCell.FormulaR1C1 = "Items"
Range("H1").Select
ActiveCell.FormulaR1C1 = "Compares"
Range("J1").Select
ActiveCell.FormulaR1C1 = "Updates"
Range("L1").Select
ActiveCell.FormulaR1C1 = "Reads"
Range("N1").Select
ActiveCell.FormulaR1C1 = "Writes"
Range("P1").Select
ActiveCell.FormulaR1C1 = "Depth"
Range("H10").Select
ActiveSheet.ListObjects.Add(xlSrcRange, Range("$B$1:$P$50"), , xlYes).Name = _
"Table1"
Range("Table1[#All]").Select
Sheets.Add
ActiveWorkbook.PivotCaches.Create(SourceType:=xlDatabase, SourceData:= _
"Table1", Version:=6).CreatePivotTable TableDestination:="Sheet2!R3C1", _
TableName:="PivotTable1", DefaultVersion:=6
Sheets("Sheet2").Select
Cells(3, 1).Select
With ActiveSheet.PivotTables("PivotTable1").PivotFields("Sort")
.Orientation = xlColumnField
.Position = 1
End With
With ActiveSheet.PivotTables("PivotTable1").PivotFields("Items")
.Orientation = xlRowField
.Position = 1
End With
ActiveSheet.PivotTables("PivotTable1").AddDataField ActiveSheet.PivotTables( _
"PivotTable1").PivotFields("Secs"), "Sum of Secs", xlSum
ActiveChart.ClearToMatchStyle
ActiveSheet.Shapes.AddChart2(227, xlLine).Select
ActiveChart.SetSourceData Source:=Range("Sheet2!$A$3:$I$12")
Application.Goto Reference:="Macro1"
*/
}
}
</TestCode>