From 4c502f36c30b46786e9050582745b735fbc16f17 Mon Sep 17 00:00:00 2001 From: Ray Wise Date: Fri, 11 Mar 2016 09:16:33 -0500 Subject: [PATCH 1/5] Added files via upload fix int to long --- bUTL.xlam | Bin 0 -> 151786 bytes scripts/BuildFile.bas | 320 ++-- src/code/Chart_Axes.bas | 302 ++-- src/code/Chart_Format.bas | 859 +++++------ src/code/Chart_Helpers.bas | 210 +-- src/code/Chart_Processing.bas | 396 +++-- src/code/Chart_Series.bas | 744 ++++----- src/code/Formatting_Helpers.bas | 1213 +++++++-------- src/code/RandomCode.bas | 770 +++++----- src/code/Ribbon_Callbacks.bas | 462 +++--- src/code/SelectionMgr.bas | 202 +-- src/code/Sheet_Helpers.bas | 337 +++-- src/code/SubsFuncs_Helpers.bas | 242 +-- src/code/Testing.bas | 172 +++ src/code/UDFs.bas | 64 +- src/code/Usability.bas | 1432 ++++++++++-------- src/code/bUTL.cls | 34 +- src/code/bUTLChartSeries.cls | 349 ++--- src/code/form_chtGrid.frm | 88 +- src/code/form_chtGrid.frx | Bin 4120 -> 4120 bytes src/code/form_chtSeries.frm | 327 ++-- src/code/form_chtSeries.frx | Bin 3096 -> 3096 bytes src/code/form_newCommands.frm | 190 +-- src/code/form_newCommands.frx | Bin 4632 -> 5656 bytes src/package/[Content_Types].xml | 2 +- src/package/customUI/_rels/customUI.xml.rels | 2 +- src/package/customUI/customUI.xml | 730 ++++----- src/package/docProps/app.xml | 2 +- src/package/docProps/core.xml | 4 +- src/package/xl/_rels/workbook.xml.rels | 2 +- src/package/xl/styles.xml | 2 +- src/package/xl/theme/theme1.xml | 2 +- src/package/xl/vbaProject.bin | Bin 274432 -> 309760 bytes src/package/xl/workbook.xml | 2 +- src/package/xl/worksheets/sheet1.xml | 2 +- 35 files changed, 5005 insertions(+), 4458 deletions(-) create mode 100644 bUTL.xlam create mode 100644 src/code/Testing.bas diff --git a/bUTL.xlam b/bUTL.xlam new file mode 100644 index 0000000000000000000000000000000000000000..76e3c3b4ac5ea884026a72f701a4da6cd6977a4b GIT binary patch literal 151786 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 literal 0 HcmV?d00001 diff --git a/scripts/BuildFile.bas b/scripts/BuildFile.bas index 4fea2aa..b806de1 100644 --- a/scripts/BuildFile.bas +++ b/scripts/BuildFile.bas @@ -1,160 +1,160 @@ -Attribute VB_Name = "BuildFile" -Option Explicit - -Sub CreateFileFromPackageAndCode() - -'workbook will run in /scripts/, files are in /src/, output to / - - Dim FSO As New FileSystemObject - Dim folRoot As Folder - Set folRoot = FSO.GetFolder(ThisWorkbook.Path).ParentFolder - - Dim strZip As Variant - strZip = FSO.BuildPath(folRoot, "temp.zip") - - Dim strPackage As Variant - strPackage = FSO.BuildPath(folRoot, "src\package\") - - Dim strPath As String - strPath = FSO.BuildPath(folRoot, "src\code\") - - Dim strAddIn As String - strAddIn = Replace(strZip, "temp.zip", "bUTL.xlam") - - 'delete add-in if it exists - If FSO.FileExists(strAddIn) Then - FSO.DeleteFile strAddIn - End If - - NewZip strZip - - Dim oApp As Object - Set oApp = CreateObject("Shell.Application") - 'Copy the files to the compressed folder - oApp.Namespace(strZip).CopyHere oApp.Namespace(strPackage).items - - 'Keep script waiting until Compressing is done - Do Until oApp.Namespace(strZip).items.Count = _ - oApp.Namespace(strPackage).items.Count - Application.Wait (Now + TimeValue("0:00:01")) - Loop - - 'convert zip to xlam - Name strZip As strAddIn - - 'open add-in - Dim wkAddIn As Workbook - Set wkAddIn = Application.Workbooks.Open(strAddIn) - - 'remove all existing code (except for Sheet1 and ThisWorkbook - 'requires a reference to the VBA Extensibility file - With wkAddIn.VBProject - Dim i As Integer - For i = .VBComponents.Count To 1 Step -1 - If .VBComponents(i).Type <> vbext_ct_Document Then - 'Static folder name - .VBComponents.Remove .VBComponents(i) - End If - Next i - End With - - 'iterate through code files and add them in - Dim strFile As Variant - strFile = Dir(strPath) - - While (strFile <> "") - If InStr(strFile, ".frm") > 0 Or InStr(strFile, ".bas") > 0 Or InStr(strFile, ".cls") > 0 Then - wkAddIn.VBProject.VBComponents.Import FSO.BuildPath(strPath, strFile) - End If - strFile = Dir - Wend - - wkAddIn.Save - wkAddIn.Close - -End Sub - -Sub CreatePackageAndCodeFromFile() - - - Dim FSO As New FileSystemObject - Dim folRoot As Folder - Set folRoot = FSO.GetFolder(ThisWorkbook.Path).ParentFolder - - Dim strZip As Variant - strZip = FSO.BuildPath(folRoot, "temp.zip") - - Dim strPackage As Variant - strPackage = FSO.BuildPath(folRoot, "src\package") - - Dim strPath As String - strPath = FSO.BuildPath(folRoot, "src\code") - - Dim strAddIn As String - strAddIn = FSO.BuildPath(folRoot, "bUTL.xlam") - - 'remove all existing files - If FSO.FolderExists(strPackage) Then - FSO.DeleteFolder strPackage - End If - - FSO.CreateFolder strPackage - - If FSO.FolderExists(strPath) Then - FSO.DeleteFolder strPath - End If - - FSO.CreateFolder strPath - - 'open workbook - Dim wkAddIn As Workbook - Set wkAddIn = Application.Workbooks.Open(strAddIn) - - 'export all existing code (except for Sheet1 and ThisWorkbook - 'requires a reference to the VBA Extensibility file - With wkAddIn.VBProject - Dim i As Integer - For i = .VBComponents.Count To 1 Step -1 - If .VBComponents(i).Type <> vbext_ct_Document Then - - 'ensure the output file name is correct - Dim strExt As String - Select Case .VBComponents(i).Type - - Case vbext_ct_StdModule - strExt = ".bas" - Case vbext_ct_ClassModule - strExt = ".cls" - Case vbext_ct_MSForm - strExt = ".frm" - - End Select - - .VBComponents(i).Export FSO.BuildPath(strPath, .VBComponents(i).CodeModule.Name & strExt) - End If - Next i - End With - 'close butl so it can be unzipped - wkAddIn.Close - - Name strAddIn As strZip - - Dim oApp As Object - Set oApp = CreateObject("Shell.Application") - 'Copy the files to the compressed folder - oApp.Namespace(strPackage).CopyHere oApp.Namespace(strZip).items - - 'rename it back - Name strZip As strAddIn - -End Sub - -Sub NewZip(sPath) -'Create empty Zip File -'Changed by keepITcool Dec-12-2005 - If Len(Dir(sPath)) > 0 Then Kill sPath - Open sPath For Output As #1 - Print #1, Chr$(80) & Chr$(75) & Chr$(5) & Chr$(6) & String(18, 0) - Close #1 -End Sub - +Attribute VB_Name = "BuildFile" +Option Explicit + +Sub CreateFileFromPackageAndCode() + +'workbook will run in /scripts/, files are in /src/, output to / + + Dim FSO As New FileSystemObject + Dim folRoot As Folder + Set folRoot = FSO.GetFolder(ThisWorkbook.Path).ParentFolder + + Dim strZip As Variant + strZip = FSO.BuildPath(folRoot, "temp.zip") + + Dim strPackage As Variant + strPackage = FSO.BuildPath(folRoot, "src\package\") + + Dim strPath As String + strPath = FSO.BuildPath(folRoot, "src\code\") + + Dim strAddIn As String + strAddIn = Replace(strZip, "temp.zip", "bUTL.xlam") + + 'delete add-in if it exists + If FSO.FileExists(strAddIn) Then + FSO.DeleteFile strAddIn + End If + + NewZip strZip + + Dim oApp As Object + Set oApp = CreateObject("Shell.Application") + 'Copy the files to the compressed folder + oApp.Namespace(strZip).CopyHere oApp.Namespace(strPackage).items + + 'Keep script waiting until Compressing is done + Do Until oApp.Namespace(strZip).items.Count = _ + oApp.Namespace(strPackage).items.Count + Application.Wait (Now + TimeValue("0:00:01")) + Loop + + 'convert zip to xlam + Name strZip As strAddIn + + 'open add-in + Dim wkAddIn As Workbook + Set wkAddIn = Application.Workbooks.Open(strAddIn) + + 'remove all existing code (except for Sheet1 and ThisWorkbook + 'requires a reference to the VBA Extensibility file + With wkAddIn.VBProject + Dim i As Integer + For i = .VBComponents.Count To 1 Step -1 + If .VBComponents(i).Type <> vbext_ct_Document Then + 'Static folder name + .VBComponents.Remove .VBComponents(i) + End If + Next i + End With + + 'iterate through code files and add them in + Dim strFile As Variant + strFile = Dir(strPath) + + While (strFile <> "") + If InStr(strFile, ".frm") > 0 Or InStr(strFile, ".bas") > 0 Or InStr(strFile, ".cls") > 0 Then + wkAddIn.VBProject.VBComponents.Import FSO.BuildPath(strPath, strFile) + End If + strFile = Dir + Wend + + wkAddIn.Save + wkAddIn.Close + +End Sub + +Sub CreatePackageAndCodeFromFile() + + + Dim FSO As New FileSystemObject + Dim folRoot As Folder + Set folRoot = FSO.GetFolder(ThisWorkbook.Path).ParentFolder + + Dim strZip As Variant + strZip = FSO.BuildPath(folRoot, "temp.zip") + + Dim strPackage As Variant + strPackage = FSO.BuildPath(folRoot, "src\package") + + Dim strPath As String + strPath = FSO.BuildPath(folRoot, "src\code") + + Dim strAddIn As String + strAddIn = FSO.BuildPath(folRoot, "bUTL.xlam") + + 'remove all existing files + If FSO.FolderExists(strPackage) Then + FSO.DeleteFolder strPackage + End If + + FSO.CreateFolder strPackage + + If FSO.FolderExists(strPath) Then + FSO.DeleteFolder strPath + End If + + FSO.CreateFolder strPath + + 'open workbook + Dim wkAddIn As Workbook + Set wkAddIn = Application.Workbooks.Open(strAddIn) + + 'export all existing code (except for Sheet1 and ThisWorkbook + 'requires a reference to the VBA Extensibility file + With wkAddIn.VBProject + Dim i As Integer + For i = .VBComponents.Count To 1 Step -1 + If .VBComponents(i).Type <> vbext_ct_Document Then + + 'ensure the output file name is correct + Dim strExt As String + Select Case .VBComponents(i).Type + + Case vbext_ct_StdModule + strExt = ".bas" + Case vbext_ct_ClassModule + strExt = ".cls" + Case vbext_ct_MSForm + strExt = ".frm" + + End Select + + .VBComponents(i).Export FSO.BuildPath(strPath, .VBComponents(i).CodeModule.Name & strExt) + End If + Next i + End With + 'close butl so it can be unzipped + wkAddIn.Close + + Name strAddIn As strZip + + Dim oApp As Object + Set oApp = CreateObject("Shell.Application") + 'Copy the files to the compressed folder + oApp.Namespace(strPackage).CopyHere oApp.Namespace(strZip).items + + 'rename it back + Name strZip As strAddIn + +End Sub + +Sub NewZip(sPath) +'Create empty Zip File +'Changed by keepITcool Dec-12-2005 + If Len(Dir(sPath)) > 0 Then Kill sPath + Open sPath For Output As #1 + Print #1, Chr$(80) & Chr$(75) & Chr$(5) & Chr$(6) & String(18, 0) + Close #1 +End Sub + diff --git a/src/code/Chart_Axes.bas b/src/code/Chart_Axes.bas index 5b76201..2f27338 100644 --- a/src/code/Chart_Axes.bas +++ b/src/code/Chart_Axes.bas @@ -1,150 +1,152 @@ -Attribute VB_Name = "Chart_Axes" -'--------------------------------------------------------------------------------------- -' Module : Chart_Axes -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains code related to chart axes -'--------------------------------------------------------------------------------------- - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_Axis_AutoX -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Reverts the x axis of a chart back to Auto -'--------------------------------------------------------------------------------------- -' -Sub Chart_Axis_AutoX() - - Dim cht_obj As ChartObject - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - Dim cht As Chart - - Dim ax As Axis - - Set cht = cht_obj.Chart - - Set ax = cht.Axes(xlCategory) - ax.MaximumScaleIsAuto = True - ax.MinimumScaleIsAuto = True - ax.MajorUnitIsAuto = True - ax.MinorUnitIsAuto = True - - Next cht_obj - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_Axis_AutoY -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Reverts the Y axis of a chart back to Auto -'--------------------------------------------------------------------------------------- -' -Sub Chart_Axis_AutoY() - - Dim cht_obj As ChartObject - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - Dim cht As Chart - - Dim ax As Axis - - Set cht = cht_obj.Chart - - Set ax = cht.Axes(xlValue) - ax.MaximumScaleIsAuto = True - ax.MinimumScaleIsAuto = True - ax.MajorUnitIsAuto = True - ax.MinorUnitIsAuto = True - - Next cht_obj - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_FitAxisToMaxAndMin -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Iterates through all series and sets desired axis to max/min of data -'--------------------------------------------------------------------------------------- -' -Sub Chart_FitAxisToMaxAndMin(xlCat As XlAxisType) - - Dim first As Boolean - first = True - - Dim cht_obj As ChartObject - - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - Dim cht As Chart - Set cht = cht_obj.Chart - - Dim ser As series - For Each ser In cht.SeriesCollection - - Dim min_val As Double, max_val As Double - - If xlCat = xlCategory Then - - min_val = Application.Min(ser.XValues) - max_val = Application.Max(ser.XValues) - - ElseIf xlCat = xlValue Then - - min_val = Application.Min(ser.Values) - max_val = Application.Max(ser.Values) - - End If - - - Dim ax As Axis - Set ax = cht.Axes(xlCat) - - Dim bool_max As Boolean, bool_min As Boolean - bool_max = max_val > ax.MaximumScale - bool_min = min_val < ax.MinimumScale - - If first Or bool_min Then - ax.MinimumScale = min_val - End If - If first Or bool_max Then - ax.MaximumScale = max_val - End If - - first = False - Next ser - Next cht_obj - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_YAxisRangeWithAvgAndStdev -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Sets a chart's Y axis to a number of standard deviations -' Flags : not-used -'--------------------------------------------------------------------------------------- -' -Sub Chart_YAxisRangeWithAvgAndStdev() - Dim dbl_std As Double - - dbl_std = CDbl(InputBox("How many standard deviations to include?")) - - Dim cht_obj As ChartObject - - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - - Dim ser As series - Set ser = cht_obj.Chart.SeriesCollection(1) - - Dim avg_val As Double - Dim std_val As Double - - avg_val = WorksheetFunction.Average(ser.Values) - std_val = WorksheetFunction.StDev(ser.Values) - - cht_obj.Chart.Axes(xlValue).MinimumScale = avg_val - std_val * dbl_std - cht_obj.Chart.Axes(xlValue).MaximumScale = avg_val + std_val * dbl_std - - Next - -End Sub +Attribute VB_Name = "Chart_Axes" +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : Chart_Axes +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains code related to chart axes +'--------------------------------------------------------------------------------------- + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_Axis_AutoX +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Reverts the x axis of a chart back to Auto +'--------------------------------------------------------------------------------------- +' +Sub Chart_Axis_AutoX() + + Dim cht_obj As ChartObject + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + Dim cht As Chart + + Dim ax As Axis + + Set cht = cht_obj.Chart + + Set ax = cht.Axes(xlCategory) + ax.MaximumScaleIsAuto = True + ax.MinimumScaleIsAuto = True + ax.MajorUnitIsAuto = True + ax.MinorUnitIsAuto = True + + Next cht_obj + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_Axis_AutoY +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Reverts the Y axis of a chart back to Auto +'--------------------------------------------------------------------------------------- +' +Sub Chart_Axis_AutoY() + + Dim cht_obj As ChartObject + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + Dim cht As Chart + + Dim ax As Axis + + Set cht = cht_obj.Chart + + Set ax = cht.Axes(xlValue) + ax.MaximumScaleIsAuto = True + ax.MinimumScaleIsAuto = True + ax.MajorUnitIsAuto = True + ax.MinorUnitIsAuto = True + + Next cht_obj + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_FitAxisToMaxAndMin +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Iterates through all series and sets desired axis to max/min of data +'--------------------------------------------------------------------------------------- +' +Sub Chart_FitAxisToMaxAndMin(xlCat As XlAxisType) + Dim cht_obj As ChartObject + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + '2015 11 09 moved first inside loop so that it works for multiple charts + Dim first As Boolean + first = True + + Dim cht As Chart + Set cht = cht_obj.Chart + + Dim ser As series + For Each ser In cht.SeriesCollection + + Dim min_val As Double + Dim max_val As Double + + If xlCat = xlCategory Then + + min_val = Application.Min(ser.XValues) + max_val = Application.Max(ser.XValues) + + ElseIf xlCat = xlValue Then + + min_val = Application.Min(ser.Values) + max_val = Application.Max(ser.Values) + + End If + + + Dim ax As Axis + Set ax = cht.Axes(xlCat) + + Dim bool_max As Boolean, bool_min As Boolean + bool_max = max_val > ax.MaximumScale + bool_min = min_val < ax.MinimumScale + + If first Or bool_min Then + ax.MinimumScale = min_val + End If + If first Or bool_max Then + ax.MaximumScale = max_val + End If + + first = False + Next ser + Next cht_obj + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_YAxisRangeWithAvgAndStdev +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Sets a chart's Y axis to a number of standard deviations +' Flags : not-used +'--------------------------------------------------------------------------------------- +' +Public Sub Chart_YAxisRangeWithAvgAndStdev() + Dim dbl_std As Double + + dbl_std = CDbl(InputBox("How many standard deviations to include?")) + + Dim cht_obj As ChartObject + + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + + Dim ser As series + Set ser = cht_obj.Chart.SeriesCollection(1) + + Dim avg_val As Double + Dim std_val As Double + + avg_val = WorksheetFunction.Average(ser.Values) + std_val = WorksheetFunction.StDev(ser.Values) + + cht_obj.Chart.Axes(xlValue).MinimumScale = avg_val - std_val * dbl_std + cht_obj.Chart.Axes(xlValue).MaximumScale = avg_val + std_val * dbl_std + + Next + +End Sub diff --git a/src/code/Chart_Format.bas b/src/code/Chart_Format.bas index 144d1c9..da0756b 100644 --- a/src/code/Chart_Format.bas +++ b/src/code/Chart_Format.bas @@ -1,424 +1,435 @@ -Attribute VB_Name = "Chart_Format" -'--------------------------------------------------------------------------------------- -' Module : Chart_Format -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains code related to formatting charts -'--------------------------------------------------------------------------------------- - - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_AddTitles -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Adds all missing titles to all selected charts -'--------------------------------------------------------------------------------------- -' -Sub Chart_AddTitles() - Dim cht_obj As ChartObject - - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - - If Not cht_obj.Chart.Axes(xlCategory).HasTitle Then - cht_obj.Chart.Axes(xlCategory).HasTitle = True - cht_obj.Chart.Axes(xlCategory).AxisTitle.Text = "x axis" - End If - - If Not cht_obj.Chart.Axes(xlValue).HasTitle Then - cht_obj.Chart.Axes(xlValue).HasTitle = True - cht_obj.Chart.Axes(xlValue).AxisTitle.Text = "y axis" - End If - - If Not cht_obj.Chart.HasTitle Then - cht_obj.Chart.HasTitle = True - cht_obj.Chart.ChartTitle.Text = "chart" - End If - - Next - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_ApplyFormattingToSelected -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Applies a semi-random format to all charts -' Flag : not-used -'--------------------------------------------------------------------------------------- -' -Sub Chart_ApplyFormattingToSelected() - - Dim obj As ChartObject - - For Each obj In Chart_GetObjectsFromObject(Selection) - - Dim ser As series - - For Each ser In obj.Chart.SeriesCollection - ser.MarkerSize = 5 - Next ser - Next obj - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_ApplyTrendColors -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Applies the predetermined chart colors to each series -'--------------------------------------------------------------------------------------- -' -Sub Chart_ApplyTrendColors() - - Dim cht_obj As ChartObject - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - - Dim ser As series - For Each ser In cht_obj.Chart.SeriesCollection - - Dim b_ser As New bUTLChartSeries - b_ser.UpdateFromChartSeries ser - - ser.MarkerForegroundColorIndex = xlColorIndexNone - ser.MarkerBackgroundColor = Chart_GetColor(b_ser.SeriesNumber) - - ser.Format.Line.ForeColor.RGB = ser.MarkerBackgroundColor - - Next ser - Next cht_obj -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_AxisTitleIsSeriesTitle -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Sets the y axis title equal to the series name of the last series -'--------------------------------------------------------------------------------------- -' -Sub Chart_AxisTitleIsSeriesTitle() - - Dim cht_obj As ChartObject - Dim cht As Chart - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - Set cht = cht_obj.Chart - - Dim b_ser As bUTLChartSeries - Dim ser As series - - For Each ser In cht.SeriesCollection - Set b_ser = New bUTLChartSeries - b_ser.UpdateFromChartSeries ser - - cht.Axes(xlValue, ser.AxisGroup).HasTitle = True - cht.Axes(xlValue, ser.AxisGroup).AxisTitle.Text = b_ser.name - - Next ser - Next cht_obj -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_CreateDataLabels -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Adds a data label for each series in the chart -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub Chart_CreateDataLabels() - - Dim chtObj As ChartObject - On Error GoTo Chart_CreateDataLabels_Error - - For Each chtObj In Chart_GetObjectsFromObject(Selection) - - Dim ser As series - For Each ser In chtObj.Chart.SeriesCollection - - Dim p As Point - Set p = ser.Points(2) - - p.HasDataLabel = False - p.DataLabel.Position = xlLabelPositionRight - p.DataLabel.ShowSeriesName = True - p.DataLabel.ShowValue = False - p.DataLabel.ShowCategoryName = False - p.DataLabel.ShowLegendKey = True - - Next ser - Next chtObj - - On Error GoTo 0 - Exit Sub - -Chart_CreateDataLabels_Error: - - MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Chart_CreateDataLabels of Module Chart_Format" - -End Sub - - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_GridOfCharts -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Creates a grid of charts. Used by the form. -'--------------------------------------------------------------------------------------- -' -Sub Chart_GridOfCharts( _ - Optional int_cols As Integer = 3, _ - Optional cht_wid As Double = 400, _ - Optional cht_height As Double = 300, _ - Optional v_off As Double = 80, _ - Optional h_off As Double = 40, _ - Optional chk_down As Boolean = False, _ - Optional bool_zoom As Boolean = False) - - Dim cht_obj As ChartObject - - Dim sht As Worksheet - Set sht = ActiveSheet - - Application.ScreenUpdating = False - - Dim count As Integer - count = 0 - - For Each cht_obj In sht.ChartObjects - Dim left As Double, top As Double - - If chk_down Then - left = (count \ int_cols) * cht_wid + h_off - top = (count Mod int_cols) * cht_height + v_off - Else - left = (count Mod int_cols) * cht_wid + h_off - top = (count \ int_cols) * cht_height + v_off - End If - - cht_obj.top = top - cht_obj.left = left - cht_obj.Width = cht_wid - cht_obj.height = cht_height - - count = count + 1 - - Next cht_obj - - 'loop through columsn to find how far to zoom - If bool_zoom Then - Dim col_zoom As Integer - col_zoom = 1 - Do While sht.Cells(1, col_zoom).left < int_cols * cht_wid - col_zoom = col_zoom + 1 - Loop - - sht.Range("A:A", sht.Cells(1, col_zoom - 1).EntireColumn).Select - ActiveWindow.Zoom = True - sht.Range("A1").Select - End If - - Application.ScreenUpdating = True - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ChartApplyToAll -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Forces all charts to be a XYScatter type -' Flag : not-used -'--------------------------------------------------------------------------------------- -' -Sub ChartApplyToAll() - - Dim cht_obj As ChartObject - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - cht_obj.Chart.SeriesCollection(1).ChartType = xlXYScatter - Next cht_obj - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ChartCreateXYGrid -' Author : @byronwall -' Date : 2015 08 11 -' Purpose : Creates a matrix of charts similar to pairs in R -'--------------------------------------------------------------------------------------- -' -Sub ChartCreateXYGrid() - - On Error GoTo ChartCreateXYGrid_Error - - DeleteAllCharts - - 'rng_data will contain the block of data with titles included - - Dim rng_data As Range - Set rng_data = Application.InputBox("Select data with titles", Type:=8) - - Application.ScreenUpdating = False - - Dim iRow As Integer, iCol As Integer - iRow = 0 - - Dim dHeight As Double, dWidth As Double - dHeight = 300 - dWidth = 400 - - Dim rngColXData As Range, rngColYData As Range - For Each rngColYData In rng_data.Columns - iCol = 0 - - For Each rngColXData In rng_data.Columns - If iRow <> iCol Then - Dim cht As Chart - Set cht = ActiveSheet.ChartObjects.Add(iCol * dWidth, _ - iRow * dHeight + 100, _ - dWidth, _ - dHeight).Chart - - Dim ser As series - Dim b_ser As New bUTLChartSeries - - 'offset allows for the title to be excluded - Set b_ser.XValues = Intersect(rngColXData, rngColXData.Offset(1)) - Set b_ser.Values = Intersect(rngColYData, rngColYData.Offset(1)) - Set b_ser.name = rngColYData.Cells(1) - b_ser.ChartType = xlXYScatter - - Set ser = b_ser.AddSeriesToChart(cht) - - ser.MarkerSize = 3 - ser.MarkerStyle = xlMarkerStyleCircle - - Dim ax As Axis - Set ax = cht.Axes(xlCategory) - ax.HasTitle = True - ax.AxisTitle.Text = rngColXData.Cells(1) - ax.MajorGridlines.Border.Color = RGB(200, 200, 200) - ax.MinorGridlines.Border.Color = RGB(220, 220, 220) - - Set ax = cht.Axes(xlValue) - ax.HasTitle = True - ax.AxisTitle.Text = rngColYData.Cells(1) - ax.MajorGridlines.Border.Color = RGB(200, 200, 200) - ax.MinorGridlines.Border.Color = RGB(220, 220, 220) - - cht.HasTitle = True - cht.ChartTitle.Text = rngColYData.Cells(1) & " vs. " & rngColXData.Cells(1) - 'cht.ChartTitle.Characters.Font.Size = 8 - cht.Legend.Delete - End If - - iCol = iCol + 1 - Next - - iRow = iRow + 1 - Next - - Application.ScreenUpdating = True - - rng_data.Cells(1, 1).Activate - - On Error GoTo 0 - Exit Sub - -ChartCreateXYGrid_Error: - - MsgBox "Error " & Err.Number & " (" & Err.Description & _ - ") in procedure ChartCreateXYGrid of Module Chart_Format" - MsgBox "This is most likely due to Range issues" - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ChartDefaultFormat -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Set the default format for all charts on ActiveSheet -'--------------------------------------------------------------------------------------- -' -Sub ChartDefaultFormat() - - Dim cht_obj As ChartObject - - For Each cht_obj In ActiveSheet.ChartObjects - Dim cht As Chart - - Set cht = cht_obj.Chart - - Dim ser As series - For Each ser In cht.SeriesCollection - - ser.MarkerSize = 3 - ser.MarkerStyle = xlMarkerStyleCircle - - If ser.ChartType = xlXYScatterLines Then - ser.Format.Line.Weight = 1.5 - - End If - - ser.MarkerForegroundColorIndex = xlColorIndexNone - ser.MarkerBackgroundColorIndex = xlColorIndexAutomatic - - Next ser - - - cht.HasLegend = True - cht.Legend.Position = xlLegendPositionBottom - - Dim ax As Axis - Set ax = cht.Axes(xlValue) - - ax.MajorGridlines.Border.Color = RGB(200, 200, 200) - ax.MinorGridlines.Border.Color = RGB(230, 230, 230) - ax.Crosses = xlAxisCrossesMinimum - - If cht.HasTitle Then - cht.ChartTitle.Characters.Font.Size = 12 - cht.ChartTitle.Characters.Font.Bold = True - End If - - Set ax = cht.Axes(xlCategory) - - Next cht_obj - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ChartPropMove -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Sets the "move or size" setting for all charts -' Flag : not-used -'--------------------------------------------------------------------------------------- -' -Sub ChartPropMove() - - Dim obj As ChartObject - - For Each obj In Chart_GetObjectsFromObject(Selection) - obj.Placement = xlFreeFloating - Next obj - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ChartTitleEqualsSeriesSelection -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Sets the chart title equal to the name of the first series -' Flag : not-used -'--------------------------------------------------------------------------------------- -' -Sub ChartTitleEqualsSeriesSelection() - - Dim cht_obj As ChartObject - - - For Each cht_obj In Selection - cht_obj.Chart.ChartTitle.Text = cht_obj.Chart.SeriesCollection(1).name - Next cht_obj - - -End Sub - +Attribute VB_Name = "Chart_Format" +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : Chart_Format +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains code related to formatting charts +'--------------------------------------------------------------------------------------- + + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_AddTitles +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Adds all missing titles to all selected charts +'--------------------------------------------------------------------------------------- +' +Sub Chart_AddTitles() + Dim cht_obj As ChartObject + + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + + If Not cht_obj.Chart.Axes(xlCategory).HasTitle Then + cht_obj.Chart.Axes(xlCategory).HasTitle = True + cht_obj.Chart.Axes(xlCategory).AxisTitle.Text = "x axis" + End If + + If Not cht_obj.Chart.Axes(xlValue).HasTitle Then + cht_obj.Chart.Axes(xlValue).HasTitle = True + cht_obj.Chart.Axes(xlValue).AxisTitle.Text = "y axis" + End If + + If Not cht_obj.Chart.HasTitle Then + cht_obj.Chart.HasTitle = True + cht_obj.Chart.ChartTitle.Text = "chart" + End If + + Next + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_ApplyFormattingToSelected +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Applies a semi-random format to all charts +' Flag : not-used +'--------------------------------------------------------------------------------------- +' +Sub Chart_ApplyFormattingToSelected() + + Dim obj As ChartObject + + For Each obj In Chart_GetObjectsFromObject(Selection) + + Dim ser As series + + For Each ser In obj.Chart.SeriesCollection + ser.MarkerSize = 5 + Next ser + Next obj + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_ApplyTrendColors +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Applies the predetermined chart colors to each series +'--------------------------------------------------------------------------------------- +' +Sub Chart_ApplyTrendColors() + + Dim cht_obj As ChartObject + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + + Dim ser As series + For Each ser In cht_obj.Chart.SeriesCollection + + Dim b_ser As New bUTLChartSeries + b_ser.UpdateFromChartSeries ser + + ser.MarkerForegroundColorIndex = xlColorIndexNone + ser.MarkerBackgroundColor = Chart_GetColor(b_ser.SeriesNumber) + + ser.Format.Line.ForeColor.RGB = ser.MarkerBackgroundColor + + Next ser + Next cht_obj +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_AxisTitleIsSeriesTitle +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Sets the y axis title equal to the series name of the last series +'--------------------------------------------------------------------------------------- +' +Sub Chart_AxisTitleIsSeriesTitle() + + Dim cht_obj As ChartObject + Dim cht As Chart + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + Set cht = cht_obj.Chart + + Dim b_ser As bUTLChartSeries + Dim ser As series + + For Each ser In cht.SeriesCollection + Set b_ser = New bUTLChartSeries + b_ser.UpdateFromChartSeries ser + + cht.Axes(xlValue, ser.AxisGroup).HasTitle = True + cht.Axes(xlValue, ser.AxisGroup).AxisTitle.Text = b_ser.name + + '2015 11 11, adds the x-title assuming that the name is one cell above the data + cht.Axes(xlCategory).HasTitle = True + cht.Axes(xlCategory).AxisTitle.Text = b_ser.XValues.Cells(1, 1).Offset(-1).Value + + Next ser + Next cht_obj +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_CreateDataLabels +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Adds a data label for each series in the chart +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub Chart_CreateDataLabels() + + Dim chtObj As ChartObject + On Error GoTo Chart_CreateDataLabels_Error + + For Each chtObj In Chart_GetObjectsFromObject(Selection) + + Dim ser As series + For Each ser In chtObj.Chart.SeriesCollection + + Dim p As Point + Set p = ser.Points(2) + + p.HasDataLabel = False + p.DataLabel.Position = xlLabelPositionRight + p.DataLabel.ShowSeriesName = True + p.DataLabel.ShowValue = False + p.DataLabel.ShowCategoryName = False + p.DataLabel.ShowLegendKey = True + + Next ser + Next chtObj + + On Error GoTo 0 + Exit Sub + +Chart_CreateDataLabels_Error: + + MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Chart_CreateDataLabels of Module Chart_Format" + +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_GridOfCharts +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Creates a grid of charts. Used by the form. +'--------------------------------------------------------------------------------------- +' +Sub Chart_GridOfCharts( _ + Optional int_cols As Long = 3, _ + Optional cht_wid As Double = 400, _ + Optional cht_height As Double = 300, _ + Optional v_off As Double = 80, _ + Optional h_off As Double = 40, _ + Optional chk_down As Boolean = False, _ + Optional bool_zoom As Boolean = False) + + Dim cht_obj As ChartObject + + Dim sht As Worksheet + Set sht = ActiveSheet + + Application.ScreenUpdating = False + + Dim count As Long + count = 0 + + For Each cht_obj In sht.ChartObjects + Dim left As Double, top As Double + + If chk_down Then + left = (count \ int_cols) * cht_wid + h_off + top = (count Mod int_cols) * cht_height + v_off + Else + left = (count Mod int_cols) * cht_wid + h_off + top = (count \ int_cols) * cht_height + v_off + End If + + cht_obj.top = top + cht_obj.left = left + cht_obj.Width = cht_wid + cht_obj.Height = cht_height + + count = count + 1 + + Next cht_obj + + 'loop through columsn to find how far to zoom + If bool_zoom Then + Dim col_zoom As Long + col_zoom = 1 + Do While sht.Cells(1, col_zoom).left < int_cols * cht_wid + col_zoom = col_zoom + 1 + Loop + + sht.Range("A:A", sht.Cells(1, col_zoom - 1).EntireColumn).Select + ActiveWindow.Zoom = True + sht.Range("A1").Select + End If + + Application.ScreenUpdating = True + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ChartApplyToAll +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Forces all charts to be a XYScatter type +' Flag : not-used +'--------------------------------------------------------------------------------------- +' +Sub ChartApplyToAll() + + Dim cht_obj As ChartObject + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + cht_obj.Chart.SeriesCollection(1).ChartType = xlXYScatter + Next cht_obj + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ChartCreateXYGrid +' Author : @byronwall +' Date : 2015 08 11 +' Purpose : Creates a matrix of charts similar to pairs in R +'--------------------------------------------------------------------------------------- +' +Sub ChartCreateXYGrid() + + On Error GoTo ChartCreateXYGrid_Error + + DeleteAllCharts + + 'rng_data will contain the block of data with titles included + + Dim rng_data As Range + Set rng_data = Application.InputBox("Select data with titles", Type:=8) + + Application.ScreenUpdating = False + + Dim iRow As Long, iCol As Long + iRow = 0 + + Dim dHeight As Double, dWidth As Double + dHeight = 300 + dWidth = 400 + + Dim rngColXData As Range, rngColYData As Range + For Each rngColYData In rng_data.Columns + iCol = 0 + + For Each rngColXData In rng_data.Columns + If iRow <> iCol Then + Dim cht As Chart + Set cht = ActiveSheet.ChartObjects.Add(iCol * dWidth, _ + iRow * dHeight + 100, _ + dWidth, _ + dHeight).Chart + + Dim ser As series + Dim b_ser As New bUTLChartSeries + + 'offset allows for the title to be excluded + Set b_ser.XValues = Intersect(rngColXData, rngColXData.Offset(1)) + Set b_ser.Values = Intersect(rngColYData, rngColYData.Offset(1)) + Set b_ser.name = rngColYData.Cells(1) + b_ser.ChartType = xlXYScatter + + Set ser = b_ser.AddSeriesToChart(cht) + + ser.MarkerSize = 3 + ser.MarkerStyle = xlMarkerStyleCircle + + Dim ax As Axis + Set ax = cht.Axes(xlCategory) + ax.HasTitle = True + ax.AxisTitle.Text = rngColXData.Cells(1) + ax.MajorGridlines.Border.Color = RGB(200, 200, 200) + ax.MinorGridlines.Border.Color = RGB(220, 220, 220) + + Set ax = cht.Axes(xlValue) + ax.HasTitle = True + ax.AxisTitle.Text = rngColYData.Cells(1) + ax.MajorGridlines.Border.Color = RGB(200, 200, 200) + ax.MinorGridlines.Border.Color = RGB(220, 220, 220) + + cht.HasTitle = True + cht.ChartTitle.Text = rngColYData.Cells(1) & " vs. " & rngColXData.Cells(1) + 'cht.ChartTitle.Characters.Font.Size = 8 + cht.Legend.Delete + End If + + iCol = iCol + 1 + Next + + iRow = iRow + 1 + Next + + Application.ScreenUpdating = True + + rng_data.Cells(1, 1).Activate + + On Error GoTo 0 + Exit Sub + +ChartCreateXYGrid_Error: + + MsgBox "Error " & Err.Number & " (" & Err.Description & _ + ") in procedure ChartCreateXYGrid of Module Chart_Format" + MsgBox "This is most likely due to Range issues" + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ChartDefaultFormat +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Set the default format for all charts on ActiveSheet +'--------------------------------------------------------------------------------------- +' +Sub ChartDefaultFormat() + + Dim cht_obj As ChartObject + + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + Dim cht As Chart + + Set cht = cht_obj.Chart + + Dim ser As series + For Each ser In cht.SeriesCollection + + ser.MarkerSize = 3 + ser.MarkerStyle = xlMarkerStyleCircle + + If ser.ChartType = xlXYScatterLines Then + ser.Format.Line.Weight = 1.5 + + End If + + ser.MarkerForegroundColorIndex = xlColorIndexNone + ser.MarkerBackgroundColorIndex = xlColorIndexAutomatic + + Next ser + + + cht.HasLegend = True + cht.Legend.Position = xlLegendPositionBottom + + Dim ax As Axis + Set ax = cht.Axes(xlValue) + + ax.MajorGridlines.Border.Color = RGB(242, 242, 242) + ax.Crosses = xlAxisCrossesMinimum + + Set ax = cht.Axes(xlCategory) + + ax.HasMajorGridlines = True + + ax.MajorGridlines.Border.Color = RGB(242, 242, 242) + + If cht.HasTitle Then + cht.ChartTitle.Characters.Font.Size = 12 + cht.ChartTitle.Characters.Font.Bold = True + End If + + Set ax = cht.Axes(xlCategory) + + Next cht_obj + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ChartPropMove +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Sets the "move or size" setting for all charts +' Flag : not-used +'--------------------------------------------------------------------------------------- +' +Sub ChartPropMove() + + Dim obj As ChartObject + + For Each obj In Chart_GetObjectsFromObject(Selection) + obj.Placement = xlFreeFloating + Next obj + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ChartTitleEqualsSeriesSelection +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Sets the chart title equal to the name of the first series +' Flag : not-used +'--------------------------------------------------------------------------------------- +' +Sub ChartTitleEqualsSeriesSelection() + + Dim cht_obj As ChartObject + + + For Each cht_obj In Selection + cht_obj.Chart.ChartTitle.Text = cht_obj.Chart.SeriesCollection(1).name + Next cht_obj + + +End Sub + diff --git a/src/code/Chart_Helpers.bas b/src/code/Chart_Helpers.bas index 496cd59..728e4f0 100644 --- a/src/code/Chart_Helpers.bas +++ b/src/code/Chart_Helpers.bas @@ -1,104 +1,106 @@ -Attribute VB_Name = "Chart_Helpers" -'--------------------------------------------------------------------------------------- -' Module : Chart_Helpers -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains code that helps other chart related features -'--------------------------------------------------------------------------------------- - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_GetColor -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Returns a list of colors for styling chart series -'--------------------------------------------------------------------------------------- -' -Function Chart_GetColor(index As Variant) As Long - - Dim colors(1 To 10) As Variant - - colors(6) = RGB(166, 206, 227) - colors(1) = RGB(31, 120, 180) - colors(7) = RGB(178, 223, 138) - colors(3) = RGB(51, 160, 44) - colors(8) = RGB(251, 154, 153) - colors(2) = RGB(227, 26, 28) - colors(9) = RGB(253, 191, 111) - colors(4) = RGB(255, 127, 0) - colors(10) = RGB(202, 178, 214) - colors(5) = RGB(106, 61, 154) - - Chart_GetColor = colors(index) - -End Function - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_GetObjectsFromObject -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Helper function which finds a valid ChartObject based on what is actually selected -' Returns a Collection (possibly empty) and should be handled with a For Each -'--------------------------------------------------------------------------------------- -' -Function Chart_GetObjectsFromObject(obj_in As Object) As Variant - - Dim str_type As String - 'TODO: these should be upgrade to TypeOf instead of strings - str_type = TypeName(obj_in) - - Dim coll As New Collection - - Dim obj As Variant - - If str_type = "DrawingObjects" Then - 'this means that multiple charts are selected - For Each obj In obj_in - If TypeName(obj) = "ChartObject" Then - 'add it to the set - coll.Add obj - End If - Next obj - - ElseIf str_type = "Chart" Then - coll.Add obj_in.Parent - - ElseIf str_type = "ChartArea" Or str_type = "PlotArea" Then - 'parent is the chart, parent of that is the chart obj - coll.Add obj_in.Parent.Parent - - ElseIf str_type = "Series" Then - 'need to go up three levels - coll.Add obj_in.Parent.Parent.Parent - - Else - MsgBox "Select an object that is supported." - End If - - Set Chart_GetObjectsFromObject = coll - -End Function - -'--------------------------------------------------------------------------------------- -' Procedure : DeleteAllCharts -' Author : @byronwall -' Date : 2015 08 11 -' Purpose : Helper Sub to delete all charts on ActiveSheet -'--------------------------------------------------------------------------------------- -' -Sub DeleteAllCharts() - - If MsgBox("Delete all charts?", vbYesNo) = vbYes Then - Application.ScreenUpdating = False - - Dim iCounter As Integer - For iCounter = ActiveSheet.ChartObjects.count To 1 Step -1 - - ActiveSheet.ChartObjects(iCounter).Delete - - Next iCounter - - Application.ScreenUpdating = True - - End If -End Sub - +Attribute VB_Name = "Chart_Helpers" +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : Chart_Helpers +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains code that helps other chart related features +'--------------------------------------------------------------------------------------- + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_GetColor +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Returns a list of colors for styling chart series +'--------------------------------------------------------------------------------------- +' +Function Chart_GetColor(index As Variant) As Long + + Dim colors(1 To 10) As Variant + + colors(6) = RGB(166, 206, 227) + colors(1) = RGB(31, 120, 180) + colors(7) = RGB(178, 223, 138) + colors(3) = RGB(51, 160, 44) + colors(8) = RGB(251, 154, 153) + colors(2) = RGB(227, 26, 28) + colors(9) = RGB(253, 191, 111) + colors(4) = RGB(255, 127, 0) + colors(10) = RGB(202, 178, 214) + colors(5) = RGB(106, 61, 154) + + Chart_GetColor = colors(index) + +End Function + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_GetObjectsFromObject +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Helper function which finds a valid ChartObject based on what is actually selected +' Returns a Collection (possibly empty) and should be handled with a For Each +'--------------------------------------------------------------------------------------- +' +Function Chart_GetObjectsFromObject(obj_in As Object) As Variant + + Dim str_type As String + 'TODO: these should be upgrade to TypeOf instead of strings + str_type = TypeName(obj_in) + + Dim coll As New Collection + + Dim obj As Variant + + If str_type = "DrawingObjects" Then + 'this means that multiple charts are selected + For Each obj In obj_in + If TypeName(obj) = "ChartObject" Then + 'add it to the set + coll.Add obj + End If + Next obj + + ElseIf str_type = "Chart" Then + coll.Add obj_in.Parent + + ElseIf str_type = "ChartArea" Or str_type = "PlotArea" Then + 'parent is the chart, parent of that is the chart obj + coll.Add obj_in.Parent.Parent + + ElseIf str_type = "Series" Then + 'need to go up three levels + coll.Add obj_in.Parent.Parent.Parent + + Else + MsgBox "Select an object that is supported." + End If + + Set Chart_GetObjectsFromObject = coll + +End Function + +'--------------------------------------------------------------------------------------- +' Procedure : DeleteAllCharts +' Author : @byronwall +' Date : 2015 08 11 +' Purpose : Helper Sub to delete all charts on ActiveSheet +'--------------------------------------------------------------------------------------- +' +Sub DeleteAllCharts() + + If MsgBox("Delete all charts?", vbYesNo) = vbYes Then + Application.ScreenUpdating = False + + Dim iCounter As Long + For iCounter = ActiveSheet.ChartObjects.count To 1 Step -1 + + ActiveSheet.ChartObjects(iCounter).Delete + + Next iCounter + + Application.ScreenUpdating = True + + End If +End Sub + diff --git a/src/code/Chart_Processing.bas b/src/code/Chart_Processing.bas index 94520e1..431b970 100644 --- a/src/code/Chart_Processing.bas +++ b/src/code/Chart_Processing.bas @@ -1,157 +1,239 @@ -Attribute VB_Name = "Chart_Processing" -'--------------------------------------------------------------------------------------- -' Module : Chart_Processing -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains some of the heavy lifting processing code for charts -'--------------------------------------------------------------------------------------- - -Option Explicit - - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_TimeSeries -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Helper Sub to create a set of charts with the same x axis and varying y -'--------------------------------------------------------------------------------------- -' -Sub Chart_TimeSeries(rng_dates As Range, rng_data As Range, rng_titles As Range) - - Dim int_counter As Integer - int_counter = 1 - - Dim rng_title As Range - Dim rng_col As Range - - For Each rng_title In rng_titles - - Dim cht_obj As ChartObject - Set cht_obj = ActiveSheet.ChartObjects.Add(int_counter * 300, 0, 300, 300) - - Dim cht As Chart - Set cht = cht_obj.Chart - cht.ChartType = xlXYScatterLines - cht.HasTitle = True - cht.Legend.Delete - - Dim ax As Axis - Set ax = cht.Axes(xlValue) - ax.MajorGridlines.Border.Color = RGB(200, 200, 200) - - Dim ser As series - Dim b_ser As New bUTLChartSeries - - Set b_ser.XValues = rng_dates - Set b_ser.Values = rng_data.Columns(int_counter) - Set b_ser.name = rng_title - - Set ser = b_ser.AddSeriesToChart(cht) - - ser.MarkerSize = 3 - ser.MarkerStyle = xlMarkerStyleCircle - - int_counter = int_counter + 1 - - Next rng_title -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_TimeSeries_FastCreation -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : this will create a fast set of charts from a block of data -' Flag : not-used -'--------------------------------------------------------------------------------------- -' -Sub Chart_TimeSeries_FastCreation() - - Dim rng_dates As Range - Dim rng_data As Range - Dim rng_titles As Range - - 'dates are in B4 and down - Set rng_dates = RangeEnd_Boundary(Range("B4"), xlDown) - - 'data starts in C4, down and over - Set rng_data = RangeEnd_Boundary(Range("C4"), xlDown, xlToRight) - - 'titels are C2 and over - Set rng_titles = RangeEnd_Boundary(Range("C2"), xlToRight) - - Chart_TimeSeries rng_dates, rng_data, rng_titles - ChartDefaultFormat - Chart_GridOfCharts - -End Sub - - -'--------------------------------------------------------------------------------------- -' Procedure : CreateMultipleTimeSeries -' Author : @byronwall -' Date : 2015 08 11 -' Purpose : Entry point from Ribbon to create a set of time series charts -'--------------------------------------------------------------------------------------- -' -Sub CreateMultipleTimeSeries() - - Dim rng_dates As Range - Dim rng_data As Range - Dim rng_titles As Range - - On Error GoTo CreateMultipleTimeSeries_Error - - DeleteAllCharts - - Set rng_dates = Application.InputBox("Select date range", Type:=8) - Set rng_data = Application.InputBox("Select data", Type:=8) - Set rng_titles = Application.InputBox("Select titles", Type:=8) - - Chart_TimeSeries rng_dates, rng_data, rng_titles - - On Error GoTo 0 - Exit Sub - -CreateMultipleTimeSeries_Error: - - MsgBox "Error " & Err.Number & " (" & Err.Description & "), likely due to Range selection." - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : RemoveZeroValueDataLabel -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Code deletes data labels that have 0 value -' Flag : not-used -'--------------------------------------------------------------------------------------- -' -Sub RemoveZeroValueDataLabel() - -'uses the ActiveChart, be sure a chart is selected - Dim cht As Chart - Set cht = ActiveChart - - Dim ser As series - For Each ser In cht.SeriesCollection - - Dim vals As Variant - vals = ser.Values - - 'include this line if you want to reestablish labels before deleting - ser.ApplyDataLabels xlDataLabelsShowLabel, , , , True, False, False, False, False - - 'loop through values and delete 0-value labels - Dim i As Integer - For i = LBound(vals) To UBound(vals) - If vals(i) = 0 Then - With ser.Points(i) - If .HasDataLabel Then - .DataLabel.Delete - End If - End With - End If - Next i - Next ser -End Sub - +Attribute VB_Name = "Chart_Processing" +'--------------------------------------------------------------------------------------- +' Module : Chart_Processing +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains some of the heavy lifting processing code for charts +'--------------------------------------------------------------------------------------- + +Option Explicit + +Public Sub Chart_CreateChartWithSeriesForEachColumn() +'will create a chart that includes a series with no x value for each column + + Dim rng_data As Range + Set rng_data = GetInputOrSelection("Select chart data") + + 'create a chart + Dim cht_obj As ChartObject + Set cht_obj = ActiveSheet.ChartObjects.Add(0, 0, 300, 300) + + cht_obj.Chart.ChartType = xlXYScatter + + Dim rng_col As Range + For Each rng_col In rng_data.Columns + + Dim rng_chart As Range + Set rng_chart = RangeEnd(rng_col.Cells(1, 1), xlDown) + + Dim b_ser As New bUTLChartSeries + Set b_ser.Values = rng_chart + + b_ser.AddSeriesToChart cht_obj.Chart + Next + +End Sub + +Public Sub Chart_CopyToSheet() + + Dim cht_obj As ChartObject + + Dim obj_all As Object + Set obj_all = Selection + + Dim msg_newSheet As VbMsgBoxResult + msg_newSheet = MsgBox("New sheet?", vbYesNo, "New sheet?") + + Dim sht_out As Worksheet + If msg_newSheet = vbYes Then + Set sht_out = Worksheets.Add() + Else + Set sht_out = Application.InputBox("Pick a cell on a sheet", "Pick sheet", Type:=8).Parent + End If + + For Each cht_obj In Chart_GetObjectsFromObject(obj_all) + cht_obj.Copy + + sht_out.Paste + Next + + sht_out.Activate +End Sub + +Sub Chart_SortSeriesByName() +'this will sort series by names + Dim cht_obj As ChartObject + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + + 'uses a simple bubble sort but it works... shouldn't have 1000 series anyways + Dim int_chart1 As Long + Dim int_chart2 As Long + For int_chart1 = 1 To cht_obj.Chart.SeriesCollection.count + For int_chart2 = (int_chart1 + 1) To cht_obj.Chart.SeriesCollection.count + + Dim b_ser1 As New bUTLChartSeries + Dim b_ser2 As New bUTLChartSeries + + b_ser1.UpdateFromChartSeries cht_obj.Chart.SeriesCollection(int_chart1) + b_ser2.UpdateFromChartSeries cht_obj.Chart.SeriesCollection(int_chart2) + + If b_ser1.name.Value > b_ser2.name.Value Then + Dim int_num As Long + int_num = b_ser2.SeriesNumber + b_ser2.SeriesNumber = b_ser1.SeriesNumber + b_ser1.SeriesNumber = int_num + + b_ser2.UpdateSeriesWithNewValues + b_ser1.UpdateSeriesWithNewValues + End If + Next + Next + Next +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_TimeSeries +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Helper Sub to create a set of charts with the same x axis and varying y +'--------------------------------------------------------------------------------------- +' +Sub Chart_TimeSeries(rng_dates As Range, rng_data As Range, rng_titles As Range) + + Dim int_counter As Long + int_counter = 1 + + Dim rng_title As Range + Dim rng_col As Range + + For Each rng_title In rng_titles + + Dim cht_obj As ChartObject + Set cht_obj = ActiveSheet.ChartObjects.Add(int_counter * 300, 0, 300, 300) + + Dim cht As Chart + Set cht = cht_obj.Chart + cht.ChartType = xlXYScatterLines + cht.HasTitle = True + cht.Legend.Delete + + Dim ax As Axis + Set ax = cht.Axes(xlValue) + ax.MajorGridlines.Border.Color = RGB(200, 200, 200) + + Dim ser As series + Dim b_ser As New bUTLChartSeries + + Set b_ser.XValues = rng_dates + Set b_ser.Values = rng_data.Columns(int_counter) + Set b_ser.name = rng_title + + Set ser = b_ser.AddSeriesToChart(cht) + + ser.MarkerSize = 3 + ser.MarkerStyle = xlMarkerStyleCircle + + int_counter = int_counter + 1 + + Next rng_title +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_TimeSeries_FastCreation +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : this will create a fast set of charts from a block of data +' Flag : not-used +'--------------------------------------------------------------------------------------- +' +Sub Chart_TimeSeries_FastCreation() + + Dim rng_dates As Range + Dim rng_data As Range + Dim rng_titles As Range + + 'dates are in B4 and down + Set rng_dates = RangeEnd_Boundary(Range("B4"), xlDown) + + 'data starts in C4, down and over + Set rng_data = RangeEnd_Boundary(Range("C4"), xlDown, xlToRight) + + 'titels are C2 and over + Set rng_titles = RangeEnd_Boundary(Range("C2"), xlToRight) + + Chart_TimeSeries rng_dates, rng_data, rng_titles + ChartDefaultFormat + Chart_GridOfCharts + +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : CreateMultipleTimeSeries +' Author : @byronwall +' Date : 2015 08 11 +' Purpose : Entry point from Ribbon to create a set of time series charts +'--------------------------------------------------------------------------------------- +' +Sub CreateMultipleTimeSeries() + + Dim rng_dates As Range + Dim rng_data As Range + Dim rng_titles As Range + + On Error GoTo CreateMultipleTimeSeries_Error + + DeleteAllCharts + + Set rng_dates = Application.InputBox("Select date range", Type:=8) + Set rng_data = Application.InputBox("Select data", Type:=8) + Set rng_titles = Application.InputBox("Select titles", Type:=8) + + Chart_TimeSeries rng_dates, rng_data, rng_titles + + On Error GoTo 0 + Exit Sub + +CreateMultipleTimeSeries_Error: + + MsgBox "Error " & Err.Number & " (" & Err.Description & "), likely due to Range selection." + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : RemoveZeroValueDataLabel +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Code deletes data labels that have 0 value +' Flag : not-used +'--------------------------------------------------------------------------------------- +' +Sub RemoveZeroValueDataLabel() + +'uses the ActiveChart, be sure a chart is selected + Dim cht As Chart + Set cht = ActiveChart + + Dim ser As series + For Each ser In cht.SeriesCollection + + Dim vals As Variant + vals = ser.Values + + 'include this line if you want to reestablish labels before deleting + ser.ApplyDataLabels xlDataLabelsShowLabel, , , , True, False, False, False, False + + 'loop through values and delete 0-value labels + Dim i As Long + For i = LBound(vals) To UBound(vals) + If vals(i) = 0 Then + With ser.Points(i) + If .HasDataLabel Then + .DataLabel.Delete + End If + End With + End If + Next i + Next ser +End Sub + diff --git a/src/code/Chart_Series.bas b/src/code/Chart_Series.bas index 0ccbbd4..51f8bf0 100644 --- a/src/code/Chart_Series.bas +++ b/src/code/Chart_Series.bas @@ -1,368 +1,376 @@ -Attribute VB_Name = "Chart_Series" -'--------------------------------------------------------------------------------------- -' Module : Chart_Series -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains charting code related to managing series -'--------------------------------------------------------------------------------------- - - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_AddTrendlineToSeriesAndColor -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Adds a trendline to each series in all charts -'--------------------------------------------------------------------------------------- -' -Sub Chart_AddTrendlineToSeriesAndColor() - - Dim cht_obj As ChartObject - - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - - Dim ser As series - - Dim i As Integer - i = 1 - - For Each ser In cht_obj.Chart.SeriesCollection - - Dim b_ser As New bUTLChartSeries - b_ser.UpdateFromChartSeries ser - - 'clear out old ones - Dim j As Integer - For j = 1 To ser.Trendlines.count - ser.Trendlines(j).Delete - Next j - - ser.MarkerBackgroundColor = Chart_GetColor(i) - - Dim trend As Trendline - Set trend = ser.Trendlines.Add() - trend.Type = xlLinear - trend.Border.Color = ser.MarkerBackgroundColor - trend.name = b_ser.name - - trend.DisplayEquation = True - trend.DisplayRSquared = True - trend.DataLabel.Format.TextFrame2.TextRange.Font.Fill.ForeColor.RGB = Chart_GetColor(i) - - i = i + 1 - Next ser - - Next cht_obj -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_ExtendSeriesToRanges -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Extends the underlying data for a series to go to the end of its current Range -'--------------------------------------------------------------------------------------- -' -Sub Chart_ExtendSeriesToRanges() - - Dim cht_obj As ChartObject - - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - - Dim ser As series - - 'get each series - For Each ser In cht_obj.Chart.SeriesCollection - - 'create the bUTL obj and manipulate series ranges - Dim b_ser As New bUTLChartSeries - b_ser.UpdateFromChartSeries ser - - ser.XValues = RangeEnd(b_ser.XValues, xlDown) - ser.Values = RangeEnd(b_ser.Values, xlDown) - - Next ser - - Next cht_obj - - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_GoToXRange -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Selects the x value range that is used for the series -'--------------------------------------------------------------------------------------- -' -Sub Chart_GoToXRange() - - Dim ser As series - - If TypeName(Selection) = "Series" Then - Dim b As New bUTLChartSeries - b.UpdateFromChartSeries Selection - - b.XValues.Parent.Activate - b.XValues.Activate - Else - MsgBox "Select a series in order to use this." - End If - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_GoToYRange -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Selects the y values used for the series -'--------------------------------------------------------------------------------------- -' -Sub Chart_GoToYRange() - - Dim ser As series - - If TypeName(Selection) = "Series" Then - Dim b As New bUTLChartSeries - b.UpdateFromChartSeries Selection - - b.Values.Parent.Activate - b.Values.Activate - Else - MsgBox "Select a series in order to use this." - End If - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_RemoveTrendlines -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Remove all trendlines from a chart -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub Chart_RemoveTrendlines() - - Dim cht_obj As ChartObject - - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - - Dim ser As series - For Each ser In cht_obj.Chart.SeriesCollection - - Dim trend As Trendline - - For Each trend In ser.Trendlines - trend.Delete - Next trend - - Next ser - - Next cht_obj -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_RerangeSeries -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Entry point for an interface to help rerange series -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub Chart_RerangeSeries() - - Dim frm As New form_chtSeries - frm.Show - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Chart_TrendlinesToAverage -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Creates a trendline using a moving average instead of linear -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub Chart_TrendlinesToAverage() - Dim cht_obj As ChartObject - - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - - Dim series As series - - For Each series In cht_obj.Chart.SeriesCollection - - Dim trend As Trendline - - For Each trend In series.Trendlines - trend.Type = xlMovingAvg - trend.Period = 15 - trend.Format.Line.Weight = 2 - Next - Next - Next - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ChartFlipXYValues -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Flips the x/y ranges for each series -'--------------------------------------------------------------------------------------- -' -Sub ChartFlipXYValues() - - Dim cht_obj As ChartObject - Dim cht As Chart - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - Set cht = cht_obj.Chart - - Dim ser As series - - Dim b_series As New Collection - Dim b_ser As bUTLChartSeries - - For Each ser In cht.SeriesCollection - Set b_ser = New bUTLChartSeries - b_ser.UpdateFromChartSeries ser - - Dim rng_dummy As Range - - Set rng_dummy = b_ser.Values - Set b_ser.Values = b_ser.XValues - Set b_ser.XValues = rng_dummy - - 'need to change the series name also - 'assume that title is same offset - 'code blocked for now - If False And Not b_ser.name Is Nothing Then - Dim int_offset_rows As Integer, int_offset_cols As Integer - int_offset_rows = b_ser.name.Row - b_ser.XValues.Cells(1, 1).Row - int_offset_cols = b_ser.name.Column - b_ser.XValues.Cells(1, 1).Column - - Set b_ser.name = b_ser.Values.Cells(1, 1).Offset(int_offset_rows, int_offset_cols) - End If - - b_ser.UpdateSeriesWithNewValues - - Next ser - - ''need to flip axis labels if they exist - Dim dummy_title As AxisTitle - - ''three cases: X only, Y only, X and Y - - If cht.Axes(xlCategory).HasTitle And Not cht.Axes(xlValue).HasTitle Then - - cht.Axes(xlValue).HasTitle = True - cht.Axes(xlValue).AxisTitle.Text = cht.Axes(xlCategory).AxisTitle.Text - cht.Axes(xlCategory).HasTitle = False - - ElseIf Not cht.Axes(xlCategory).HasTitle And cht.Axes(xlValue).HasTitle Then - cht.Axes(xlCategory).HasTitle = True - cht.Axes(xlCategory).AxisTitle.Text = cht.Axes(xlValue).AxisTitle.Text - cht.Axes(xlValue).HasTitle = False - ElseIf cht.Axes(xlCategory).HasTitle And cht.Axes(xlValue).HasTitle Then - Dim dummy_text As String - - dummy_text = cht.Axes(xlCategory).AxisTitle.Text - - cht.Axes(xlCategory).AxisTitle.Text = cht.Axes(xlValue).AxisTitle.Text - cht.Axes(xlValue).AxisTitle.Text = dummy_text - - End If - - Set b_series = Nothing - - Next cht_obj - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ChartMergeSeries -' Author : @byronwall -' Date : 2015 08 11 -' Purpose : Merges all selected charts into a single chart -'--------------------------------------------------------------------------------------- -' -Sub ChartMergeSeries() - - Dim cht_obj As ChartObject - Dim cht As Chart - Dim sel As Variant - Dim cht_first As Chart - - Dim bool_first As Boolean - bool_first = True - - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - - Set cht = cht_obj.Chart - If bool_first Then - Set cht_first = cht - bool_first = False - Else - Dim ser As series - For Each ser In cht.SeriesCollection - - Dim ser_new As series - Dim b_ser As New bUTLChartSeries - - b_ser.UpdateFromChartSeries ser - Set ser_new = b_ser.AddSeriesToChart(cht_first) - - ser_new.MarkerSize = ser.MarkerSize - ser_new.MarkerStyle = ser.MarkerStyle - - ser.Delete - - Next ser - - cht_obj.Delete - - End If - Next cht_obj - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ChartSplitSeries -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Take all series from selected charts and puts them in their own charts -'--------------------------------------------------------------------------------------- -' -Sub ChartSplitSeries() - - Dim cht_obj As ChartObject - Dim cht As Chart - Dim sel As Variant - - Dim ser As series - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - - For Each ser In cht_obj.Chart.SeriesCollection - - Dim cht_obj_new As ChartObject - Set cht_obj_new = ActiveSheet.ChartObjects.Add(0, 0, 300, 300) - - Dim ser_new As series - Dim b_ser As New bUTLChartSeries - - b_ser.UpdateFromChartSeries ser - Set ser_new = b_ser.AddSeriesToChart(cht_obj_new.Chart) - - ser_new.MarkerSize = ser.MarkerSize - ser_new.MarkerStyle = ser.MarkerStyle - - ser.Delete - - Next ser - - - cht_obj.Delete - - Next cht_obj -End Sub - +Attribute VB_Name = "Chart_Series" +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : Chart_Series +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains charting code related to managing series +'--------------------------------------------------------------------------------------- + + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_AddTrendlineToSeriesAndColor +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Adds a trendline to each series in all charts +'--------------------------------------------------------------------------------------- +' +Sub Chart_AddTrendlineToSeriesAndColor() + + Dim cht_obj As ChartObject + + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + + Dim ser As series + + Dim i As Long + i = 1 + + For Each ser In cht_obj.Chart.SeriesCollection + + Dim b_ser As New bUTLChartSeries + b_ser.UpdateFromChartSeries ser + + 'clear out old ones + Dim j As Long + For j = 1 To ser.Trendlines.count + ser.Trendlines(j).Delete + Next j + + ser.MarkerBackgroundColor = Chart_GetColor(i) + + Dim trend As Trendline + Set trend = ser.Trendlines.Add() + trend.Type = xlLinear + trend.Border.Color = ser.MarkerBackgroundColor + + '2015 11 06 test to avoid error without name + If Not b_ser.name Is Nothing Then + trend.name = b_ser.name + End If + + trend.DisplayEquation = True + trend.DisplayRSquared = True + trend.DataLabel.Format.TextFrame2.TextRange.Font.Fill.ForeColor.RGB = Chart_GetColor(i) + + i = i + 1 + Next ser + + Next cht_obj +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_ExtendSeriesToRanges +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Extends the underlying data for a series to go to the end of its current Range +'--------------------------------------------------------------------------------------- +' +Sub Chart_ExtendSeriesToRanges() + + Dim cht_obj As ChartObject + + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + + Dim ser As series + + 'get each series + For Each ser In cht_obj.Chart.SeriesCollection + + 'create the bUTL obj and manipulate series ranges + Dim b_ser As New bUTLChartSeries + b_ser.UpdateFromChartSeries ser + + If Not b_ser.XValues Is Nothing Then + ser.XValues = RangeEnd(b_ser.XValues.Cells(1), xlDown) + End If + ser.Values = RangeEnd(b_ser.Values.Cells(1), xlDown) + + Next ser + + Next cht_obj + + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_GoToXRange +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Selects the x value range that is used for the series +'--------------------------------------------------------------------------------------- +' +Sub Chart_GoToXRange() + + Dim ser As series + + If TypeName(Selection) = "Series" Then + Dim b As New bUTLChartSeries + b.UpdateFromChartSeries Selection + + b.XValues.Parent.Activate + b.XValues.Activate + Else + MsgBox "Select a series in order to use this." + End If + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_GoToYRange +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Selects the y values used for the series +'--------------------------------------------------------------------------------------- +' +Sub Chart_GoToYRange() + + Dim ser As series + + If TypeName(Selection) = "Series" Then + Dim b As New bUTLChartSeries + b.UpdateFromChartSeries Selection + + b.Values.Parent.Activate + b.Values.Activate + Else + MsgBox "Select a series in order to use this." + End If + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_RemoveTrendlines +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Remove all trendlines from a chart +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub Chart_RemoveTrendlines() + + Dim cht_obj As ChartObject + + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + + Dim ser As series + For Each ser In cht_obj.Chart.SeriesCollection + + Dim trend As Trendline + + For Each trend In ser.Trendlines + trend.Delete + Next trend + + Next ser + + Next cht_obj +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_RerangeSeries +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Entry point for an interface to help rerange series +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub Chart_RerangeSeries() + + Dim frm As New form_chtSeries + frm.Show + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Chart_TrendlinesToAverage +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Creates a trendline using a moving average instead of linear +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub Chart_TrendlinesToAverage() + Dim cht_obj As ChartObject + + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + + Dim series As series + + For Each series In cht_obj.Chart.SeriesCollection + + Dim trend As Trendline + + For Each trend In series.Trendlines + trend.Type = xlMovingAvg + trend.Period = 15 + trend.Format.Line.Weight = 2 + Next + Next + Next + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ChartFlipXYValues +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Flips the x/y ranges for each series +'--------------------------------------------------------------------------------------- +' +Sub ChartFlipXYValues() + + Dim cht_obj As ChartObject + Dim cht As Chart + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + Set cht = cht_obj.Chart + + Dim ser As series + + Dim b_series As New Collection + Dim b_ser As bUTLChartSeries + + For Each ser In cht.SeriesCollection + Set b_ser = New bUTLChartSeries + b_ser.UpdateFromChartSeries ser + + Dim rng_dummy As Range + + Set rng_dummy = b_ser.Values + Set b_ser.Values = b_ser.XValues + Set b_ser.XValues = rng_dummy + + 'need to change the series name also + 'assume that title is same offset + 'code blocked for now + If False And Not b_ser.name Is Nothing Then + Dim int_offset_rows As Long, int_offset_cols As Long + int_offset_rows = b_ser.name.Row - b_ser.XValues.Cells(1, 1).Row + int_offset_cols = b_ser.name.Column - b_ser.XValues.Cells(1, 1).Column + + Set b_ser.name = b_ser.Values.Cells(1, 1).Offset(int_offset_rows, int_offset_cols) + End If + + b_ser.UpdateSeriesWithNewValues + + Next ser + + ''need to flip axis labels if they exist + Dim dummy_title As AxisTitle + + ''three cases: X only, Y only, X and Y + + If cht.Axes(xlCategory).HasTitle And Not cht.Axes(xlValue).HasTitle Then + + cht.Axes(xlValue).HasTitle = True + cht.Axes(xlValue).AxisTitle.Text = cht.Axes(xlCategory).AxisTitle.Text + cht.Axes(xlCategory).HasTitle = False + + ElseIf Not cht.Axes(xlCategory).HasTitle And cht.Axes(xlValue).HasTitle Then + cht.Axes(xlCategory).HasTitle = True + cht.Axes(xlCategory).AxisTitle.Text = cht.Axes(xlValue).AxisTitle.Text + cht.Axes(xlValue).HasTitle = False + ElseIf cht.Axes(xlCategory).HasTitle And cht.Axes(xlValue).HasTitle Then + Dim dummy_text As String + + dummy_text = cht.Axes(xlCategory).AxisTitle.Text + + cht.Axes(xlCategory).AxisTitle.Text = cht.Axes(xlValue).AxisTitle.Text + cht.Axes(xlValue).AxisTitle.Text = dummy_text + + End If + + Set b_series = Nothing + + Next cht_obj + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ChartMergeSeries +' Author : @byronwall +' Date : 2015 08 11 +' Purpose : Merges all selected charts into a single chart +'--------------------------------------------------------------------------------------- +' +Sub ChartMergeSeries() + + Dim cht_obj As ChartObject + Dim cht As Chart + Dim sel As Variant + Dim cht_first As Chart + + Dim bool_first As Boolean + bool_first = True + + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + + Set cht = cht_obj.Chart + If bool_first Then + Set cht_first = cht + bool_first = False + Else + Dim ser As series + For Each ser In cht.SeriesCollection + + Dim ser_new As series + Dim b_ser As New bUTLChartSeries + + b_ser.UpdateFromChartSeries ser + Set ser_new = b_ser.AddSeriesToChart(cht_first) + + ser_new.MarkerSize = ser.MarkerSize + ser_new.MarkerStyle = ser.MarkerStyle + + ser.Delete + + Next ser + + cht_obj.Delete + + End If + Next cht_obj + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ChartSplitSeries +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Take all series from selected charts and puts them in their own charts +'--------------------------------------------------------------------------------------- +' +Sub ChartSplitSeries() + + Dim cht_obj As ChartObject + Dim cht As Chart + Dim sel As Variant + + Dim ser As series + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + + For Each ser In cht_obj.Chart.SeriesCollection + + Dim cht_obj_new As ChartObject + Set cht_obj_new = ActiveSheet.ChartObjects.Add(0, 0, 300, 300) + + Dim ser_new As series + Dim b_ser As New bUTLChartSeries + + b_ser.UpdateFromChartSeries ser + Set ser_new = b_ser.AddSeriesToChart(cht_obj_new.Chart) + + ser_new.MarkerSize = ser.MarkerSize + ser_new.MarkerStyle = ser.MarkerStyle + + ser.Delete + + Next ser + + + cht_obj.Delete + + Next cht_obj +End Sub + diff --git a/src/code/Formatting_Helpers.bas b/src/code/Formatting_Helpers.bas index de75441..73698e2 100644 --- a/src/code/Formatting_Helpers.bas +++ b/src/code/Formatting_Helpers.bas @@ -1,598 +1,615 @@ -Attribute VB_Name = "Formatting_Helpers" -'--------------------------------------------------------------------------------------- -' Module : Formatting_Helpers -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : contains code related to formatting and other cell value stuff -'--------------------------------------------------------------------------------------- - -'--------------------------------------------------------------------------------------- -' Procedure : CategoricalColoring -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Applies the formatting from one range to another if cell value's match -'--------------------------------------------------------------------------------------- -' -Public Sub CategoricalColoring() -'+Get User Input - Dim rngToColor As Range - On Error GoTo errHandler - Set rngToColor = GetInputOrSelection("Select Range to Color") - - Dim rngColors As Range - Set rngColors = GetInputOrSelection("Select Range with Colors") - - '+Do Magic - Application.ScreenUpdating = False - Dim c As Range - Dim varRow As Variant - - For Each c In rngToColor - varRow = Application.Match(c, rngColors, 0) - '+ Matches font style as well as interior color - If IsNumeric(varRow) Then - c.Font.FontStyle = rngColors.Cells(varRow).Font.FontStyle - c.Font.Color = rngColors.Cells(varRow).Font.Color - '+Skip interior color if there is none - If Not rngColors.Cells(varRow).Interior.ColorIndex = xlNone Then - c.Interior.Color = rngColors.Cells(varRow).Interior.Color - End If - End If - Next c - '+ If no fill, restore gridlines - rngToColor.Borders.LineStyle = xlNone - Application.ScreenUpdating = True - Exit Sub -errHandler: - MsgBox ("No Range Selected!") -End Sub - - -'--------------------------------------------------------------------------------------- -' Procedure : ColorForUnique -' Author : @byronwall, @RaymondWise -' Date : 2015 07 29 -' Purpose : Adds the same unique color to each unique value in a range -' Flag : not-used -'--------------------------------------------------------------------------------------- -' -Sub ColorForUnique() - - Dim dictKeysAndColors As New Scripting.Dictionary - Dim dictColorsOnly As New Scripting.Dictionary - - Dim rngToColor As Range - - On Error GoTo ColorForUnique_Error - - Set rngToColor = GetInputOrSelection("Select column to color") - Set rngToColor = Intersect(rngToColor, rngToColor.Parent.UsedRange) - - 'We can colorize the sorting column, or the entire row - Dim vShouldColorEntireRow As VbMsgBoxResult - vShouldColorEntireRow = MsgBox("Do you want to color the entire row?", vbYesNo) - - Application.ScreenUpdating = False - - Dim rngRowToColor As Range - For Each rngRowToColor In rngToColor.Rows - - 'allow for a multi column key if intial range is multi-column - 'TODO: consider making this another prompt... might (?) want to color multi range based on single column key - Dim id As String - If rngRowToColor.Columns.count > 1 Then - id = Join(Application.Transpose(Application.Transpose(rngRowToColor.Value)), "||") - Else - id = rngRowToColor.Value - End If - - 'new value, need a color - If Not dictKeysAndColors.Exists(id) Then - Dim lRgbColor As Long -createNewColor: - lRgbColor = RGB(Application.RandBetween(50, 255), _ - Application.RandBetween(50, 255), Application.RandBetween(50, 255)) - If dictColorsOnly.Exists(lRgbColor) Then - 'ensure unique colors only - GoTo createNewColor - End If - - dictKeysAndColors.Add id, lRgbColor - End If - - If vShouldColorEntireRow = vbYes Then - rngRowToColor.EntireRow.Interior.Color = dictKeysAndColors(id) - Else - rngRowToColor.Interior.Color = dictKeysAndColors(id) - End If - Next rngRowToColor - - Application.ScreenUpdating = True - - On Error GoTo 0 - Exit Sub - -ColorForUnique_Error: - MsgBox "Select a valid range or fewer than 65650 unique entries." - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Colorize -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Creates an alternating color band based on cell values -'--------------------------------------------------------------------------------------- -' -Public Sub Colorize() - - Dim rngToColor As Range - On Error GoTo errHandler - Set rngToColor = GetInputOrSelection("Select range to color") - Dim lastrow As Integer - lastrow = rngToColor.Rows.count - - likevalues = MsgBox("Do you want to keep duplicate values the same color?", vbYesNo) - - If likevalues = vbNo Then - - For i = 1 To lastrow - If i Mod 2 = 0 Then - rngToColor.Rows(i).Interior.Color = RGB(200, 200, 200) - Else: rngToColor.Rows(i).Interior.ColorIndex = xlNone - End If - Next - End If - - - If likevalues = vbYes Then - Dim flip As Boolean - For i = 2 To lastrow - If rngToColor.Cells(i, 1) <> rngToColor.Cells(i - 1, 1) Then - flip = Not flip - End If - - If flip Then - rngToColor.Rows(i).Interior.Color = RGB(200, 200, 200) - Else: rngToColor.Rows(i).Interior.ColorIndex = xlNone - End If - Next - End If - Exit Sub -errHandler: - MsgBox ("No Range Selected!") -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : CombineCells -' Author : @byronwall, @RaymondWise -' Date : 2015 07 24 -' Purpose : Takes a row of values and converts them to a single column -'--------------------------------------------------------------------------------------- -' -Sub CombineCells() - 'collect all user data up front - Dim rngInput As Range - On Error GoTo errHandler - Set rngInput = GetInputOrSelection("Select the range of cells to combine") - - Dim strDelim As String - strDelim = Application.InputBox("Delimeter:") - If strDelim = "" Then GoTo errHandler - If strDelim = "False" Then GoTo errHandler - Dim rngOutput As Range - Set rngOutput = GetInputOrSelection("Select the output range") - - 'Check the size of input and adjust output - Dim y As Long - y = rngInput.Columns.count - - Dim x As Long - x = rngInput.Rows.count - - rngOutput = rngOutput.Resize(x, 1) - - 'Read input rows into a single string - Dim strOutput As String - For i = 1 To x - strOutput = vbNullString - For j = 1 To y - strOutput = strOutput & strDelim & rngInput(i, j) - Next - 'Get rid of the first character (strDelim) - strOutput = Right(strOutput, Len(strOutput) - 1) - 'Print it! - rngOutput(i, 1) = strOutput - Next - Exit Sub -errHandler: - MsgBox ("No Range or Delimiter Selected!") -End Sub - - -'--------------------------------------------------------------------------------------- -' Procedure : ConvertToNumber -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Forces all numbers stored as text to be converted to actual numbers -'--------------------------------------------------------------------------------------- -' -Sub ConvertToNumber() - - Dim cell As Range - Dim sel As Range - - Set sel = Selection - - Application.ScreenUpdating = False - Application.Calculation = xlCalculationManual - - For Each cell In Intersect(sel, ActiveSheet.UsedRange) - If Not IsEmpty(cell.Value) And IsNumeric(cell.Value) Then - cell.Value = CDbl(cell.Value) - End If - Next cell - - Application.ScreenUpdating = True - Application.Calculation = xlCalculationAutomatic - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : CopyTranspose -' Author : @byronwall, @RaymondWise -' Date : 2015 07 31 -' Purpose : Takes a range of cells and does a copy/tranpose -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub CopyTranspose() - - 'If user cancels a range input, we need to handle it when it occurs - On Error GoTo errCancel - Dim rngSelect As Range - - Set rngSelect = GetInputOrSelection("Select your range") - - Dim rngOut As Range - Set rngOut = GetInputOrSelection("Select the output corner") - - Application.ScreenUpdating = False - Application.EnableEvents = False - Application.Calculation = xlCalculationManual - - Dim rCorner As Range - Set rCorner = rngSelect.Cells(1, 1) - - Dim iCRow As Integer - iCRow = rCorner.Row - Dim iCCol As Integer - iCCol = rCorner.Column - - Dim iORow As Integer - Dim iOCol As Integer - iORow = rngOut.Row - iOCol = rngOut.Column - - Dim c As Range - - 'We check for the intersection to ensure we don't overwrite any of the original data - For Each c In rngSelect - If Not Intersect(rngSelect, Cells(iORow + c.Column - iCCol, iOCol + c.Row - iCRow)) Is Nothing Then - MsgBox ("Your destination intersects with your data") - Exit Sub - End If - Next c - - For Each c In rngSelect - ActiveSheet.Cells(iORow + c.Column - iCCol, iOCol + c.Row - iCRow).Formula = c.Formula - Next c - - Application.ScreenUpdating = True - Application.EnableEvents = True - Application.Calculation = xlCalculationAutomatic - Application.Calculate - -errCancel: -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : CreateConditionalsForFormatting -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Creates a set of conditional formats for order of magnitude numbers -'--------------------------------------------------------------------------------------- -' -Sub CreateConditionalsForFormatting() - On Error GoTo errHandler - Dim rngInput As Range - Set rngInput = GetInputOrSelection("Select the range of cells to convert") - 'add these in as powers of 3, starting at 1 = 10^0 - Dim arrMarkers As Variant - arrMarkers = Array("", "k", "M", "B") - - For i = UBound(arrMarkers) To 0 Step -1 - - With rngInput.FormatConditions.Add(xlCellValue, xlGreaterEqual, 10 ^ (3 * i)) - .NumberFormat = "0" & Application.WorksheetFunction.Rept(",", i) & " "" " & arrMarkers(i) & """" - End With - - Next - Exit Sub -errHandler: - MsgBox ("No Range Selected!") -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ExtendArrayFormulaDown -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Takes an array formula and extends it down as far as the range on its right goes -'--------------------------------------------------------------------------------------- -' -Sub ExtendArrayFormulaDown() - - Dim rngArrForm As Range - Dim RngArea As Range - - - Application.ScreenUpdating = False - - Set rngArrForm = Selection - - For Each RngArea In rngArrForm.Areas - For Each c In RngArea.Cells - - If c.HasArray Then - - Dim strFormula As String - strFormula = c.FormulaArray - - Dim arrStart As Range - Dim arrEnd As Range - - Set arrStart = c.CurrentArray.Cells(1, 1) - Set arrEnd = arrStart.Offset(0, -1).End(xlDown).Offset(0, 1) - - c.CurrentArray.Formula = "" - - Range(arrStart, arrEnd).FormulaArray = strFormula - - End If - - Next c - Next RngArea - - - 'Find the range of the new array formula - 'Save current formula and clear it out - 'Apply the formula to the new range - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : MakeHyperlinks -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Converts a set of cells to hyperlink to their cell value -'--------------------------------------------------------------------------------------- -' -Sub MakeHyperlinks() -'+Changed to inputbox - On Error GoTo errHandler - Dim rngEval As Range - Set rngEval = GetInputOrSelection("Select the range of cells to convert to hyperlink") - For Each c In rngEval - ActiveSheet.Hyperlinks.Add Anchor:=c, Address:=c - Next c - Exit Sub -errHandler: - MsgBox ("No Range Selected!") -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : OutputColors -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Outputs the list of chart colors available -' Flag : not-used -'--------------------------------------------------------------------------------------- -' -Sub OutputColors() - - For i = 1 To 10 - ActiveCell.Offset(i).Interior.Color = Chart_GetColor(i) - Next i - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : SelectedToValue -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Forces a cell to take on its value. Removes formulas. -'--------------------------------------------------------------------------------------- -' -Sub SelectedToValue() - - Dim rng As Range - On Error GoTo errHandler - Set rng = GetInputOrSelection("Select the formulas you'd like to convert to static values") - - For Each c In rng - c.Value = c.Value - Next c - Exit Sub -errHandler: - MsgBox ("No selection made!") -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Selection_ColorWithHex -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Colors a cell based on the hex value stored in the cell -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub Selection_ColorWithHex() - - Dim c As Range - Dim rngToColor As Range - On Error GoTo errHandler - Set rngToColor = GetInputOrSelection("Select the range of cells to color") - - For Each c In rngToColor - - c.Interior.Color = RGB(WorksheetFunction.Hex2Dec(Mid(c.Value, 2, 2)), _ - WorksheetFunction.Hex2Dec(Mid(c.Value, 4, 2)), _ - WorksheetFunction.Hex2Dec(Mid(c.Value, 6, 2))) - - Next c - Exit Sub -errHandler: - MsgBox ("No selection made!") -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : SplitAndKeep -' Author : @byronwall -' Date : 2015 08 12 -' Purpose : Reduces a cell's value to one item returned from Split -'--------------------------------------------------------------------------------------- -' -Sub SplitAndKeep() - - On Error GoTo SplitAndKeep_Error - - Dim rngToSplit As Range - Set rngToSplit = GetInputOrSelection("Select range to split") - - If rngToSplit Is Nothing Then - Exit Sub - End If - - Dim delim As Variant - delim = InputBox("What delimeter to split on?") - - If StrPtr(delim) = 0 Then - Exit Sub - End If - - Dim vItemToKeep As Variant - vItemToKeep = InputBox("Which item to keep? (This is 0-indexed)") - - If StrPtr(vItemToKeep) = 0 Then - Exit Sub - End If - - Dim rngCell As Range - For Each rngCell In Intersect(rngToSplit, rngToSplit.Parent.UsedRange) - - Dim vParts As Variant - vParts = Split(rngCell, delim) - - If UBound(vParts) >= vItemToKeep Then - rngCell.Value = vParts(vItemToKeep) - End If - - Next rngCell - - On Error GoTo 0 - Exit Sub - -SplitAndKeep_Error: - MsgBox "Check that a valid Range is selected and that a number was entered for which item to keep." -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : SplitIntoColumns -' Author : @byronwall, @RaymondWise -' Date : 2015 07 24 -' Purpose : Splits a cell into columns next to it based on a delimeter -'--------------------------------------------------------------------------------------- -' -Sub SplitIntoColumns() - - Dim rngInput As Range - - Set rngInput = GetInputOrSelection("Select the range of cells to split") - - Dim c As Range - - Dim strDelim As String - strDelim = Application.InputBox("What is the delimeter?", , ",", vbOKCancel) - If strDelim = "" Then GoTo errHandler - If strDelim = "False" Then GoTo errHandler - For Each c In rngInput - - Dim arrParts As Variant - arrParts = Split(c, strDelim) - - Dim varPart As Variant - For Each varPart In arrParts - - Set c = c.Offset(, 1) - c = varPart - - Next varPart - - Next c - Exit Sub -errHandler: - MsgBox ("No Delimiter Defined!") -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : SplitIntoRows -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Splits a cell with return characters into multiple rows with no returns -'--------------------------------------------------------------------------------------- -' -Sub SplitIntoRows() - - Dim rngOutput As Range - - Dim rngInput As Range - Set rngInput = Selection - - Set rngOutput = GetInputOrSelection("Select the output corner") - - Dim varPart As Variant - Dim iRow As Integer - iRow = 0 - Dim c As Range - - For Each c In rngInput.SpecialCells(xlCellTypeVisible) - Dim varParts As Variant - varParts = Split(c, vbLf) - - For Each varPart In varParts - rngOutput.Offset(iRow) = varPart - - iRow = iRow + 1 - Next varPart - Next c -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : TrimSelection -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Trims whitespace from a cell's value -'--------------------------------------------------------------------------------------- -' -Sub TrimSelection() - Dim rngToTrim As Range - On Error GoTo errHandler - Set rngToTrim = GetInputOrSelection("Select the formulas you'd like to convert to static values") - - For Each c In rngToTrim - c.Value = Trim(c.Value) - Next c - Exit Sub -errHandler: - MsgBox ("No Delimiter Defined!") -End Sub - +Attribute VB_Name = "Formatting_Helpers" +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : Formatting_Helpers +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : contains code related to formatting and other cell value stuff +'--------------------------------------------------------------------------------------- + +'--------------------------------------------------------------------------------------- +' Procedure : CategoricalColoring +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Applies the formatting from one range to another if cell value's match +'--------------------------------------------------------------------------------------- +' +Public Sub CategoricalColoring() +'+Get User Input + Dim rngToColor As Range + On Error GoTo errHandler + Set rngToColor = GetInputOrSelection("Select Range to Color") + + Dim rngColors As Range + Set rngColors = GetInputOrSelection("Select Range with Colors") + + '+Do Magic + Application.ScreenUpdating = False + Dim c As Range + Dim varRow As Variant + + For Each c In rngToColor + varRow = Application.Match(c, rngColors, 0) + '+ Matches font style as well as interior color + If IsNumeric(varRow) Then + c.Font.FontStyle = rngColors.Cells(varRow).Font.FontStyle + c.Font.Color = rngColors.Cells(varRow).Font.Color + '+Skip interior color if there is none + If Not rngColors.Cells(varRow).Interior.ColorIndex = xlNone Then + c.Interior.Color = rngColors.Cells(varRow).Interior.Color + End If + End If + Next c + '+ If no fill, restore gridlines + rngToColor.Borders.LineStyle = xlNone + Application.ScreenUpdating = True + Exit Sub +errHandler: + MsgBox ("No Range Selected!") +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : ColorForUnique +' Author : @byronwall, @RaymondWise +' Date : 2015 07 29 +' Purpose : Adds the same unique color to each unique value in a range +' Flag : not-used +'--------------------------------------------------------------------------------------- +' +Sub ColorForUnique() + + Dim dictKeysAndColors As New Scripting.Dictionary + Dim dictColorsOnly As New Scripting.Dictionary + + Dim rngToColor As Range + + On Error GoTo ColorForUnique_Error + + Set rngToColor = GetInputOrSelection("Select column to color") + Set rngToColor = Intersect(rngToColor, rngToColor.Parent.UsedRange) + + 'We can colorize the sorting column, or the entire row + Dim vShouldColorEntireRow As VbMsgBoxResult + vShouldColorEntireRow = MsgBox("Do you want to color the entire row?", vbYesNo) + + Application.ScreenUpdating = False + + Dim rngRowToColor As Range + For Each rngRowToColor In rngToColor.Rows + + 'allow for a multi column key if intial range is multi-column + 'TODO: consider making this another prompt... might (?) want to color multi range based on single column key + Dim id As String + If rngRowToColor.Columns.count > 1 Then + id = Join(Application.Transpose(Application.Transpose(rngRowToColor.Value)), "||") + Else + id = rngRowToColor.Value + End If + + 'new value, need a color + If Not dictKeysAndColors.Exists(id) Then + Dim lRgbColor As Long +createNewColor: + lRgbColor = RGB(Application.RandBetween(50, 255), _ + Application.RandBetween(50, 255), Application.RandBetween(50, 255)) + If dictColorsOnly.Exists(lRgbColor) Then + 'ensure unique colors only + GoTo createNewColor + End If + + dictKeysAndColors.Add id, lRgbColor + End If + + If vShouldColorEntireRow = vbYes Then + rngRowToColor.EntireRow.Interior.Color = dictKeysAndColors(id) + Else + rngRowToColor.Interior.Color = dictKeysAndColors(id) + End If + Next rngRowToColor + + Application.ScreenUpdating = True + + On Error GoTo 0 + Exit Sub + +ColorForUnique_Error: + MsgBox "Select a valid range or fewer than 65650 unique entries." + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Colorize +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Creates an alternating color band based on cell values +'--------------------------------------------------------------------------------------- +' +Public Sub Colorize() + + Dim rngToColor As Range + On Error GoTo errHandler + Set rngToColor = GetInputOrSelection("Select range to color") + Dim lastrow As Long + lastrow = rngToColor.Rows.count + + Dim likevalues As VbMsgBoxResult + likevalues = MsgBox("Do you want to keep duplicate values the same color?", vbYesNo) + + If likevalues = vbNo Then + + Dim i As Long + For i = 1 To lastrow + If i Mod 2 = 0 Then + rngToColor.Rows(i).Interior.Color = RGB(200, 200, 200) + Else: rngToColor.Rows(i).Interior.ColorIndex = xlNone + End If + Next + End If + + + If likevalues = vbYes Then + Dim flip As Boolean + For i = 2 To lastrow + If rngToColor.Cells(i, 1) <> rngToColor.Cells(i - 1, 1) Then + flip = Not flip + End If + + If flip Then + rngToColor.Rows(i).Interior.Color = RGB(200, 200, 200) + Else: rngToColor.Rows(i).Interior.ColorIndex = xlNone + End If + Next + End If + Exit Sub +errHandler: + MsgBox ("No Range Selected!") +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : CombineCells +' Author : @byronwall, @RaymondWise +' Date : 2015 07 24 +' Purpose : Takes a row of values and converts them to a single column +'--------------------------------------------------------------------------------------- +' +Sub CombineCells() + 'collect all user data up front + Dim rngInput As Range + On Error GoTo errHandler + Set rngInput = GetInputOrSelection("Select the range of cells to combine") + + Dim strDelim As String + strDelim = Application.InputBox("Delimeter:") + If strDelim = "" Then GoTo errHandler + If strDelim = "False" Then GoTo errHandler + Dim rngOutput As Range + Set rngOutput = GetInputOrSelection("Select the output range") + + 'Check the size of input and adjust output + Dim y As Long + y = rngInput.Columns.count + + Dim x As Long + x = rngInput.Rows.count + + rngOutput = rngOutput.Resize(x, 1) + + 'Read input rows into a single string + Dim strOutput As String + Dim i As Long + For i = 1 To x + strOutput = vbNullString + Dim j As Long + For j = 1 To y + strOutput = strOutput & strDelim & rngInput(i, j) + Next + 'Get rid of the first character (strDelim) + strOutput = Right(strOutput, Len(strOutput) - 1) + 'Print it! + rngOutput(i, 1) = strOutput + Next + Exit Sub +errHandler: + MsgBox ("No Range or Delimiter Selected!") +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : ConvertToNumber +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Forces all numbers stored as text to be converted to actual numbers +'--------------------------------------------------------------------------------------- +' +Sub ConvertToNumber() + + Dim cell As Range + Dim sel As Range + + Set sel = Selection + + Application.ScreenUpdating = False + Application.Calculation = xlCalculationManual + + For Each cell In Intersect(sel, ActiveSheet.UsedRange) + If Not IsEmpty(cell.Value) And IsNumeric(cell.Value) Then + cell.Value = CDbl(cell.Value) + End If + Next cell + + Application.ScreenUpdating = True + Application.Calculation = xlCalculationAutomatic + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : CopyTranspose +' Author : @byronwall, @RaymondWise +' Date : 2015 07 31 +' Purpose : Takes a range of cells and does a copy/tranpose +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub CopyTranspose() + + 'If user cancels a range input, we need to handle it when it occurs + On Error GoTo errCancel + Dim rngSelect As Range + + Set rngSelect = GetInputOrSelection("Select your range") + + Dim rngOut As Range + Set rngOut = GetInputOrSelection("Select the output corner") + + Application.ScreenUpdating = False + Application.EnableEvents = False + Application.Calculation = xlCalculationManual + + Dim rCorner As Range + Set rCorner = rngSelect.Cells(1, 1) + + Dim iCRow As Long + iCRow = rCorner.Row + Dim iCCol As Long + iCCol = rCorner.Column + + Dim iORow As Long + Dim iOCol As Long + iORow = rngOut.Row + iOCol = rngOut.Column + + Dim c As Range + + 'We check for the intersection to ensure we don't overwrite any of the original data + For Each c In rngSelect + If Not Intersect(rngSelect, Cells(iORow + c.Column - iCCol, iOCol + c.Row - iCRow)) Is Nothing Then + MsgBox ("Your destination intersects with your data") + Exit Sub + End If + Next c + + For Each c In rngSelect + ActiveSheet.Cells(iORow + c.Column - iCCol, iOCol + c.Row - iCRow).Formula = c.Formula + Next c + + Application.ScreenUpdating = True + Application.EnableEvents = True + Application.Calculation = xlCalculationAutomatic + Application.Calculate + +errCancel: +End Sub + + + +'--------------------------------------------------------------------------------------- +' Procedure : CreateConditionalsForFormatting +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Creates a set of conditional formats for order of magnitude numbers +'--------------------------------------------------------------------------------------- +' +Sub CreateConditionalsForFormatting() + On Error GoTo errHandler + Dim rngInput As Range + Set rngInput = GetInputOrSelection("Select the range of cells to convert") + 'add these in as powers of 3, starting at 1 = 10^0 + Dim arrMarkers As Variant + arrMarkers = Array("", "k", "M", "B") + + Dim i As Long + For i = UBound(arrMarkers) To 0 Step -1 + + With rngInput.FormatConditions.Add(xlCellValue, xlGreaterEqual, 10 ^ (3 * i)) + .NumberFormat = "0" & Application.WorksheetFunction.Rept(",", i) & " "" " & arrMarkers(i) & """" + End With + + Next + Exit Sub +errHandler: + MsgBox ("No Range Selected!") +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ExtendArrayFormulaDown +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Takes an array formula and extends it down as far as the range on its right goes +'--------------------------------------------------------------------------------------- +' +Sub ExtendArrayFormulaDown() + + Dim rngArrForm As Range + Dim RngArea As Range + + + Application.ScreenUpdating = False + + Set rngArrForm = Selection + + For Each RngArea In rngArrForm.Areas + + Dim c As Range + For Each c In RngArea.Cells + + If c.HasArray Then + + Dim strFormula As String + strFormula = c.FormulaArray + + Dim arrStart As Range + Dim arrEnd As Range + + Set arrStart = c.CurrentArray.Cells(1, 1) + Set arrEnd = arrStart.Offset(0, -1).End(xlDown).Offset(0, 1) + + c.CurrentArray.Formula = "" + + Range(arrStart, arrEnd).FormulaArray = strFormula + + End If + + Next c + Next RngArea + + + 'Find the range of the new array formula + 'Save current formula and clear it out + 'Apply the formula to the new range + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : MakeHyperlinks +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Converts a set of cells to hyperlink to their cell value +'--------------------------------------------------------------------------------------- +' +Sub MakeHyperlinks() +'+Changed to inputbox + On Error GoTo errHandler + Dim rngEval As Range + Set rngEval = GetInputOrSelection("Select the range of cells to convert to hyperlink") + + 'TODO: choose a better variable name + Dim c As Range + For Each c In rngEval + ActiveSheet.Hyperlinks.Add Anchor:=c, Address:=c + Next c + Exit Sub +errHandler: + MsgBox ("No Range Selected!") +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : OutputColors +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Outputs the list of chart colors available +' Flag : not-used +'--------------------------------------------------------------------------------------- +' +Sub OutputColors() + + Dim i As Long + For i = 1 To 10 + ActiveCell.Offset(i).Interior.Color = Chart_GetColor(i) + Next i + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : SelectedToValue +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Forces a cell to take on its value. Removes formulas. +'--------------------------------------------------------------------------------------- +' +Sub SelectedToValue() + + Dim rng As Range + On Error GoTo errHandler + Set rng = GetInputOrSelection("Select the formulas you'd like to convert to static values") + + Dim c As Range + For Each c In rng + c.Value = c.Value + Next c + Exit Sub +errHandler: + MsgBox ("No selection made!") +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : Selection_ColorWithHex +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Colors a cell based on the hex value stored in the cell +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub Selection_ColorWithHex() + + Dim c As Range + Dim rngToColor As Range + On Error GoTo errHandler + Set rngToColor = GetInputOrSelection("Select the range of cells to color") + + For Each c In rngToColor + + c.Interior.Color = RGB(WorksheetFunction.Hex2Dec(Mid(c.Value, 2, 2)), _ + WorksheetFunction.Hex2Dec(Mid(c.Value, 4, 2)), _ + WorksheetFunction.Hex2Dec(Mid(c.Value, 6, 2))) + + Next c + Exit Sub +errHandler: + MsgBox ("No selection made!") +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : SplitAndKeep +' Author : @byronwall +' Date : 2015 08 12 +' Purpose : Reduces a cell's value to one item returned from Split +'--------------------------------------------------------------------------------------- +' +Sub SplitAndKeep() + + On Error GoTo SplitAndKeep_Error + + Dim rngToSplit As Range + Set rngToSplit = GetInputOrSelection("Select range to split") + + If rngToSplit Is Nothing Then + Exit Sub + End If + + Dim delim As Variant + delim = InputBox("What delimeter to split on?") + + If StrPtr(delim) = 0 Then + Exit Sub + End If + + Dim vItemToKeep As Variant + vItemToKeep = InputBox("Which item to keep? (This is 0-indexed)") + + If StrPtr(vItemToKeep) = 0 Then + Exit Sub + End If + + Dim rngCell As Range + For Each rngCell In Intersect(rngToSplit, rngToSplit.Parent.UsedRange) + + Dim vParts As Variant + vParts = Split(rngCell, delim) + + If UBound(vParts) >= vItemToKeep Then + rngCell.Value = vParts(vItemToKeep) + End If + + Next rngCell + + On Error GoTo 0 + Exit Sub + +SplitAndKeep_Error: + MsgBox "Check that a valid Range is selected and that a number was entered for which item to keep." +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : SplitIntoColumns +' Author : @byronwall, @RaymondWise +' Date : 2015 07 24 +' Purpose : Splits a cell into columns next to it based on a delimeter +'--------------------------------------------------------------------------------------- +' +Sub SplitIntoColumns() + + Dim rngInput As Range + + Set rngInput = GetInputOrSelection("Select the range of cells to split") + + Dim c As Range + + Dim strDelim As String + strDelim = Application.InputBox("What is the delimeter?", , ",", vbOKCancel) + If strDelim = "" Then GoTo errHandler + If strDelim = "False" Then GoTo errHandler + For Each c In rngInput + + Dim arrParts As Variant + arrParts = Split(c, strDelim) + + Dim varPart As Variant + For Each varPart In arrParts + + Set c = c.Offset(, 1) + c = varPart + + Next varPart + + Next c + Exit Sub +errHandler: + MsgBox ("No Delimiter Defined!") +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : SplitIntoRows +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Splits a cell with return characters into multiple rows with no returns +'--------------------------------------------------------------------------------------- +' +Sub SplitIntoRows() + + Dim rngOutput As Range + + Dim rngInput As Range + Set rngInput = Selection + + Set rngOutput = GetInputOrSelection("Select the output corner") + + Dim varPart As Variant + Dim iRow As Long + iRow = 0 + Dim c As Range + + For Each c In rngInput.SpecialCells(xlCellTypeVisible) + Dim varParts As Variant + varParts = Split(c, vbLf) + + For Each varPart In varParts + rngOutput.Offset(iRow) = varPart + + iRow = iRow + 1 + Next varPart + Next c +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : TrimSelection +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Trims whitespace from a cell's value +'--------------------------------------------------------------------------------------- +' +Sub TrimSelection() + Dim rngToTrim As Range + On Error GoTo errHandler + Set rngToTrim = GetInputOrSelection("Select the formulas you'd like to convert to static values") + + Dim c As Range + For Each c In rngToTrim + c.Value = Trim(c.Value) + Next c + Exit Sub +errHandler: + MsgBox ("No Delimiter Defined!") +End Sub + diff --git a/src/code/RandomCode.bas b/src/code/RandomCode.bas index 9cf5f53..d07c53d 100644 --- a/src/code/RandomCode.bas +++ b/src/code/RandomCode.bas @@ -1,384 +1,386 @@ -Attribute VB_Name = "RandomCode" -'--------------------------------------------------------------------------------------- -' Module : RandomCode -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains a lot of junk code that was stored. Most is too specific to be useful. -'--------------------------------------------------------------------------------------- - - - - -'''this one goes through a data source and alphabetizes it. -'''keeping mainly for the select case and find/findnext -Sub AlphabetizeAndReportWithDupes() - - Dim rng_data As Range - Set rng_data = Range("B2:B28") - - Dim rng_output As Range - Set rng_output = Range("I2") - - Dim arr As Variant - arr = Application.Transpose(rng_data.Value) - QuickSort arr - 'arr is now sorted - - Dim i As Integer - For i = LBound(arr) To UBound(arr) - - 'if duplicate, use FindNext, else just Find - Dim rng_search As Range - Select Case True - Case i = LBound(arr), UCase(arr(i)) <> UCase(arr(i - 1)) - Set rng_search = rng_data.Find(arr(i)) - Case Else - Set rng_search = rng_data.FindNext(rng_search) - End Select - - ''''do your report stuff in here for each row - 'copy data over - rng_output.Offset(i - 1).Resize(, 6).Value = rng_search.Resize(, 6).Value - - Next i -End Sub - - -Sub Rand_OpenFilesAndCopy() - - Dim sht_data As Worksheet - Dim sht_output As Worksheet - - Set sht_output = ActiveSheet - - Dim path As Variant - Dim folder As Variant - - Application.ScreenUpdating = False - ' Another static folder - folder = "O:\HCCShare\Operations\PE\Plant 8\Production Engineer\BWall\2013 11 Rheo troubleshooting\Recipes\PE7\2\" - - path = Dir(folder) - - Do While path <> "" - - Dim wkbk As Workbook - Set wkbk = Workbooks.Open(folder & path) - Set sht_data = wkbk.Sheets(1) - sht_data.UsedRange.Copy - - sht_output.Cells(sht_output.UsedRange.Rows.count + 1, 1) = wkbk.name - sht_output.Cells(sht_output.UsedRange.Rows.count, 2).PasteSpecial xlPasteValues - - wkbk.Close False - - path = Dir - - Loop - -End Sub - - -Sub Rand_PrintMultiple() - - 'go through the tags, pick one, put it in place - - 'print out a PDF to a file - - Application.ScreenUpdating = False - 'Another static folder - Dim rng_tag As Range - Dim str_path As String - str_path = "C:\Documents and Settings\wallbd\Application Data\PDF OUTPUT\" - - For Each rng_tag In Range("TAGS[TAG]").SpecialCells(xlCellTypeVisible) - - Range("C1") = rng_tag - - Sheets("SUMMARY").ExportAsFixedFormat xlTypePDF, str_path & rng_tag & ".PDF", , , , , , False - - 'code is used to get a summary - 'Dim sht As Worksheet - 'Set sht = Sheets("ALL TAGS") - - 'sht.Range("A1").EntireRow.Insert - 'sht.Range("A1") = rng_tag - - 'Range("I8:L8").Copy - 'sht.Range("B1").PasteSpecial xlPasteValues - 'sht.Range("F1").Value = str_path & rng_tag & ".PDF" - - Next rng_tag - - Application.ScreenUpdating = True - -End Sub - -Sub Rand_PrintMultiplePvVsOp() - - 'go through the tags, pick one, put it in place - - 'print out a PDF to a file - - Application.ScreenUpdating = False - 'Another static folder - Dim rng_tag As Range - Dim str_path As String - str_path = "C:\Documents and Settings\wallbd\Application Data\PDF OUTPUT\" - - For Each rng_tag In Range("tag_table[TAG]").SpecialCells(xlCellTypeVisible) - - Range("Charts!C3") = rng_tag - - Sheets("CHARTS").ExportAsFixedFormat xlTypePDF, str_path & rng_tag & "-" & rng_tag.Offset(, 5) & ".PDF", , , , , , False - - Next rng_tag - - Application.ScreenUpdating = True - -End Sub - -Function Download_File(ByVal vWebFile As String, ByVal vLocalFile As String) As Boolean - Dim oXMLHTTP As Object, i As Long, vFF As Long, oResp() As Byte - - 'You can also set a ref. to Microsoft XML, and Dim oXMLHTTP as MSXML2.XMLHTTP - Set oXMLHTTP = CreateObject("MSXML2.XMLHTTP") - oXMLHTTP.Open "GET", vWebFile, False 'Open socket to get the website - oXMLHTTP.Send 'send request - - 'Wait for request to finish - Do While oXMLHTTP.readyState <> 4 - DoEvents - Loop - - oResp = oXMLHTTP.responseBody 'Returns the results as a byte array - - 'Create local file and save results to it - vFF = FreeFile - If Dir(vLocalFile) <> "" Then Kill vLocalFile - Open vLocalFile For Binary As #vFF - Put #vFF, , oResp - Close #vFF - - 'Clear memory - Set oXMLHTTP = Nothing -End Function - -Sub Rand_DownloadFromSheet() - - Dim rng_addr As Range - - Dim str_folder As Variant - 'Another static folder - str_folder = "C:\Documents and Settings\wallbd\Application Data\DSP Guide\" - - For Each rng_addr In Range("B2:B35") - - Download_File rng_add, str_folder & rng_addr.Offset(, 1) - - Next rng_addr - -End Sub - -Sub Rand_CommonPrintSettings() - -Application.ScreenUpdating = False -Dim sht As Worksheet - -For Each sht In Sheets - sht.PageSetup.PrintArea = "" - sht.ResetAllPageBreaks - sht.PageSetup.PrintArea = "" - - With sht.PageSetup - .LeftHeader = "" - .CenterHeader = "" - .RightHeader = "" - .LeftFooter = "" - .CenterFooter = "" - .RightFooter = "" - .LeftMargin = Application.InchesToPoints(0.75) - .RightMargin = Application.InchesToPoints(0.75) - .TopMargin = Application.InchesToPoints(1) - .BottomMargin = Application.InchesToPoints(1) - .HeaderMargin = Application.InchesToPoints(0.5) - .FooterMargin = Application.InchesToPoints(0.5) - .PrintHeadings = False - .PrintGridlines = False - .PrintComments = xlPrintNoComments - .PrintQuality = 600 - .CenterHorizontally = False - .CenterVertically = False - .Orientation = xlLandscape - .Draft = False - .PaperSize = xlPaperLetter - .FirstPageNumber = xlAutomatic - .Order = xlDownThenOver - .BlackAndWhite = False - .Zoom = False - .FitToPagesWide = 1 - .FitToPagesTall = False - .PrintErrors = xlPrintErrorsDisplayed - .OddAndEvenPagesHeaderFooter = False - .DifferentFirstPageHeaderFooter = False - .ScaleWithDocHeaderFooter = True - .AlignMarginsHeaderFooter = False - .EvenPage.LeftHeader.Text = "" - .EvenPage.CenterHeader.Text = "" - .EvenPage.RightHeader.Text = "" - .EvenPage.LeftFooter.Text = "" - .EvenPage.CenterFooter.Text = "" - .EvenPage.RightFooter.Text = "" - .FirstPage.LeftHeader.Text = "" - .FirstPage.CenterHeader.Text = "" - .FirstPage.RightHeader.Text = "" - .FirstPage.LeftFooter.Text = "" - .FirstPage.CenterFooter.Text = "" - .FirstPage.RightFooter.Text = "" - .PrintTitleRows = "" - .PrintTitleColumns = "" - End With - Next sht - - Application.ScreenUpdating = True -End Sub - - -Sub Rand_DumpTextFromAllSheets() - - Dim c As Range - Dim s As Worksheet - - Application.ScreenUpdating = False - Application.EnableEvents = False - Application.Calculation = xlCalculationManual - - Dim main As Workbook - Set main = ActiveWorkbook - - Dim w As Workbook - Dim sw As Worksheet - - Set w = Application.Workbooks.Add - Set sw = w.Sheets.Add - - Dim Row As Integer - Row = 0 - For Each s In main.Sheets - For Each c In s.UsedRange.SpecialCells(xlCellTypeConstants) - sw.Range("A1").Offset(Row) = c - Row = Row + 1 - Next c - Next s - -End Sub - - -Sub Rand_ApplyHeadersAndFootersToAll() - - Dim sht As Worksheet - Dim sht_hdr As Worksheet - - Set sht_hdr = ActiveSheet - - For Each sht In Sheets - sht.PageSetup.LeftHeader = sht_hdr.PageSetup.LeftHeader - sht.PageSetup.CenterHeader = sht_hdr.PageSetup.CenterHeader - sht.PageSetup.RightHeader = sht_hdr.PageSetup.RightHeader - sht.PageSetup.LeftFooter = sht_hdr.PageSetup.LeftFooter - sht.PageSetup.CenterFooter = sht_hdr.PageSetup.CenterFooter - sht.PageSetup.RightFooter = sht_hdr.PageSetup.RightFooter - Next sht - -End Sub - -'Takes a table of values and flattens it. -Sub Rand_Matrix() - - Dim rng_left As Range - Dim rng_top As Range - Dim rng_body As Range - - Set rng_left = Application.InputBox("Select left column", Type:=8) - Set rng_top = Application.InputBox("Select top column", Type:=8) - - Dim int_left As Integer, int_top As Integer - - Set rng_body = Range(Cells(rng_left.Row, rng_top.Column), _ - Cells(rng_left.Rows(rng_left.Rows.count).Row, rng_top.Columns(rng_top.Columns.count).Column)) - - Dim sht_out As Worksheet - Set sht_out = Application.Worksheets.Add() - - Dim rng_cell As Range - - Dim int_row As Integer - int_row = 1 - - For Each rng_cell In rng_body.SpecialCells(xlCellTypeConstants) - sht_out.Range("A1").Offset(int_row) = rng_left.Cells(rng_cell.Row - rng_left.Row + 1, 1) - sht_out.Range("B1").Offset(int_row) = rng_top.Cells(1, rng_cell.Column - rng_top.Column + 1) - sht_out.Range("C1").Offset(int_row) = rng_cell - - int_row = int_row + 1 - Next rng_cell - -End Sub - -Sub Rand_CopyPasteValuesIntoNewSheet() - - Dim sht_new As Worksheet - Dim sht_current As Worksheet - - Set sht_current = ActiveSheet - - Set sht_new = Worksheets.Add - sht_current.UsedRange.Copy - sht_new.PasteSpecial xlPasteValuesAndNumberFormats - - -End Sub - -Sub Rand_ConvertToString() - - Dim cell As Range - Dim sel As Range - - Set sel = Selection - - Application.ScreenUpdating = False - Application.Calculation = xlCalculationManual - - For Each cell In Intersect(sel, sel.Parent.UsedRange) - If Not IsEmpty(cell.Value) And Not cell.HasFormula Then - cell.Value = CStr(cell.Value) - End If - Next cell - - Application.ScreenUpdating = True - Application.Calculation = xlCalculationAutomatic - -End Sub - - -Sub Rand_KeepCellsWithText() - - Selection.SpecialCells(xlCellTypeConstants).Select - -End Sub - -Sub Rand_DeleteHiddenSheets() - - Dim sht As Worksheet - - Application.DisplayAlerts = False - - For Each sht In Worksheets - If sht.Visible = xlSheetHidden Then - sht.Delete - End If - Next sht - - Application.DisplayAlerts = True - -End Sub +Attribute VB_Name = "RandomCode" +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : RandomCode +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains a lot of junk code that was stored. Most is too specific to be useful. +'--------------------------------------------------------------------------------------- + + + + +'''this one goes through a data source and alphabetizes it. +'''keeping mainly for the select case and find/findnext +Sub AlphabetizeAndReportWithDupes() + + Dim rng_data As Range + Set rng_data = Range("B2:B28") + + Dim rng_output As Range + Set rng_output = Range("I2") + + Dim arr As Variant + arr = Application.Transpose(rng_data.Value) + QuickSort arr + 'arr is now sorted + + Dim i As Long + For i = LBound(arr) To UBound(arr) + + 'if duplicate, use FindNext, else just Find + Dim rng_search As Range + Select Case True + Case i = LBound(arr), UCase(arr(i)) <> UCase(arr(i - 1)) + Set rng_search = rng_data.Find(arr(i)) + Case Else + Set rng_search = rng_data.FindNext(rng_search) + End Select + + ''''do your report stuff in here for each row + 'copy data over + rng_output.Offset(i - 1).Resize(, 6).Value = rng_search.Resize(, 6).Value + + Next i +End Sub + + +Sub Rand_OpenFilesAndCopy() + + Dim sht_data As Worksheet + Dim sht_output As Worksheet + + Set sht_output = ActiveSheet + + Dim path As Variant + Dim folder As Variant + + Application.ScreenUpdating = False + ' Another static folder + folder = "O:\HCCShare\Operations\PE\Plant 8\Production Engineer\BWall\2013 11 Rheo troubleshooting\Recipes\PE7\2\" + + path = Dir(folder) + + Do While path <> "" + + Dim wkbk As Workbook + Set wkbk = Workbooks.Open(folder & path) + Set sht_data = wkbk.Sheets(1) + sht_data.UsedRange.Copy + + sht_output.Cells(sht_output.UsedRange.Rows.count + 1, 1) = wkbk.name + sht_output.Cells(sht_output.UsedRange.Rows.count, 2).PasteSpecial xlPasteValues + + wkbk.Close False + + path = Dir + + Loop + +End Sub + + +Sub Rand_PrintMultiple() + + 'go through the tags, pick one, put it in place + + 'print out a PDF to a file + + Application.ScreenUpdating = False + 'Another static folder + Dim rng_tag As Range + Dim str_path As String + str_path = "C:\Documents and Settings\wallbd\Application Data\PDF OUTPUT\" + + For Each rng_tag In Range("TAGS[TAG]").SpecialCells(xlCellTypeVisible) + + Range("C1") = rng_tag + + Sheets("SUMMARY").ExportAsFixedFormat xlTypePDF, str_path & rng_tag & ".PDF", , , , , , False + + 'code is used to get a summary + 'Dim sht As Worksheet + 'Set sht = Sheets("ALL TAGS") + + 'sht.Range("A1").EntireRow.Insert + 'sht.Range("A1") = rng_tag + + 'Range("I8:L8").Copy + 'sht.Range("B1").PasteSpecial xlPasteValues + 'sht.Range("F1").Value = str_path & rng_tag & ".PDF" + + Next rng_tag + + Application.ScreenUpdating = True + +End Sub + +Sub Rand_PrintMultiplePvVsOp() + + 'go through the tags, pick one, put it in place + + 'print out a PDF to a file + + Application.ScreenUpdating = False + 'Another static folder + Dim rng_tag As Range + Dim str_path As String + str_path = "C:\Documents and Settings\wallbd\Application Data\PDF OUTPUT\" + + For Each rng_tag In Range("tag_table[TAG]").SpecialCells(xlCellTypeVisible) + + Range("Charts!C3") = rng_tag + + Sheets("CHARTS").ExportAsFixedFormat xlTypePDF, str_path & rng_tag & "-" & rng_tag.Offset(, 5) & ".PDF", , , , , , False + + Next rng_tag + + Application.ScreenUpdating = True + +End Sub + +Function Download_File(ByVal vWebFile As String, ByVal vLocalFile As String) As Boolean + Dim oXMLHTTP As Object, i As Long, vFF As Long, oResp() As Byte + + 'You can also set a ref. to Microsoft XML, and Dim oXMLHTTP as MSXML2.XMLHTTP + Set oXMLHTTP = CreateObject("MSXML2.XMLHTTP") + oXMLHTTP.Open "GET", vWebFile, False 'Open socket to get the website + oXMLHTTP.Send 'send request + + 'Wait for request to finish + Do While oXMLHTTP.readyState <> 4 + DoEvents + Loop + + oResp = oXMLHTTP.responseBody 'Returns the results as a byte array + + 'Create local file and save results to it + vFF = FreeFile + If Dir(vLocalFile) <> "" Then Kill vLocalFile + Open vLocalFile For Binary As #vFF + Put #vFF, , oResp + Close #vFF + + 'Clear memory + Set oXMLHTTP = Nothing +End Function + +Sub Rand_DownloadFromSheet() + + Dim rng_addr As Range + + Dim str_folder As Variant + 'Another static folder + str_folder = "C:\Documents and Settings\wallbd\Application Data\DSP Guide\" + + For Each rng_addr In Range("B2:B35") + + Download_File rng_addr, str_folder & rng_addr.Offset(, 1) + + Next rng_addr + +End Sub + +Sub Rand_CommonPrintSettings() + +Application.ScreenUpdating = False +Dim sht As Worksheet + +For Each sht In Sheets + sht.PageSetup.PrintArea = "" + sht.ResetAllPageBreaks + sht.PageSetup.PrintArea = "" + + With sht.PageSetup + .LeftHeader = "" + .CenterHeader = "" + .RightHeader = "" + .LeftFooter = "" + .CenterFooter = "" + .RightFooter = "" + .LeftMargin = Application.InchesToPoints(0.75) + .RightMargin = Application.InchesToPoints(0.75) + .TopMargin = Application.InchesToPoints(1) + .BottomMargin = Application.InchesToPoints(1) + .HeaderMargin = Application.InchesToPoints(0.5) + .FooterMargin = Application.InchesToPoints(0.5) + .PrintHeadings = False + .PrintGridlines = False + .PrintComments = xlPrintNoComments + .PrintQuality = 600 + .CenterHorizontally = False + .CenterVertically = False + .Orientation = xlLandscape + .Draft = False + .PaperSize = xlPaperLetter + .FirstPageNumber = xlAutomatic + .Order = xlDownThenOver + .BlackAndWhite = False + .Zoom = False + .FitToPagesWide = 1 + .FitToPagesTall = False + .PrintErrors = xlPrintErrorsDisplayed + .OddAndEvenPagesHeaderFooter = False + .DifferentFirstPageHeaderFooter = False + .ScaleWithDocHeaderFooter = True + .AlignMarginsHeaderFooter = False + .EvenPage.LeftHeader.Text = "" + .EvenPage.CenterHeader.Text = "" + .EvenPage.RightHeader.Text = "" + .EvenPage.LeftFooter.Text = "" + .EvenPage.CenterFooter.Text = "" + .EvenPage.RightFooter.Text = "" + .FirstPage.LeftHeader.Text = "" + .FirstPage.CenterHeader.Text = "" + .FirstPage.RightHeader.Text = "" + .FirstPage.LeftFooter.Text = "" + .FirstPage.CenterFooter.Text = "" + .FirstPage.RightFooter.Text = "" + .PrintTitleRows = "" + .PrintTitleColumns = "" + End With + Next sht + + Application.ScreenUpdating = True +End Sub + + +Sub Rand_DumpTextFromAllSheets() + + Dim c As Range + Dim s As Worksheet + + Application.ScreenUpdating = False + Application.EnableEvents = False + Application.Calculation = xlCalculationManual + + Dim main As Workbook + Set main = ActiveWorkbook + + Dim w As Workbook + Dim sw As Worksheet + + Set w = Application.Workbooks.Add + Set sw = w.Sheets.Add + + Dim Row As Long + Row = 0 + For Each s In main.Sheets + For Each c In s.UsedRange.SpecialCells(xlCellTypeConstants) + sw.Range("A1").Offset(Row) = c + Row = Row + 1 + Next c + Next s + +End Sub + + +Sub Rand_ApplyHeadersAndFootersToAll() + + Dim sht As Worksheet + Dim sht_hdr As Worksheet + + Set sht_hdr = ActiveSheet + + For Each sht In Sheets + sht.PageSetup.LeftHeader = sht_hdr.PageSetup.LeftHeader + sht.PageSetup.CenterHeader = sht_hdr.PageSetup.CenterHeader + sht.PageSetup.RightHeader = sht_hdr.PageSetup.RightHeader + sht.PageSetup.LeftFooter = sht_hdr.PageSetup.LeftFooter + sht.PageSetup.CenterFooter = sht_hdr.PageSetup.CenterFooter + sht.PageSetup.RightFooter = sht_hdr.PageSetup.RightFooter + Next sht + +End Sub + +'Takes a table of values and flattens it. +Sub Rand_Matrix() + + Dim rng_left As Range + Dim rng_top As Range + Dim rng_body As Range + + Set rng_left = Application.InputBox("Select left column", Type:=8) + Set rng_top = Application.InputBox("Select top column", Type:=8) + + Dim int_left As Long, int_top As Long + + Set rng_body = Range(Cells(rng_left.Row, rng_top.Column), _ + Cells(rng_left.Rows(rng_left.Rows.count).Row, rng_top.Columns(rng_top.Columns.count).Column)) + + Dim sht_out As Worksheet + Set sht_out = Application.Worksheets.Add() + + Dim rng_cell As Range + + Dim int_row As Long + int_row = 1 + + For Each rng_cell In rng_body.SpecialCells(xlCellTypeConstants) + sht_out.Range("A1").Offset(int_row) = rng_left.Cells(rng_cell.Row - rng_left.Row + 1, 1) + sht_out.Range("B1").Offset(int_row) = rng_top.Cells(1, rng_cell.Column - rng_top.Column + 1) + sht_out.Range("C1").Offset(int_row) = rng_cell + + int_row = int_row + 1 + Next rng_cell + +End Sub + +Sub Rand_CopyPasteValuesIntoNewSheet() + + Dim sht_new As Worksheet + Dim sht_current As Worksheet + + Set sht_current = ActiveSheet + + Set sht_new = Worksheets.Add + sht_current.UsedRange.Copy + sht_new.PasteSpecial xlPasteValuesAndNumberFormats + + +End Sub + +Sub Rand_ConvertToString() + + Dim cell As Range + Dim sel As Range + + Set sel = Selection + + Application.ScreenUpdating = False + Application.Calculation = xlCalculationManual + + For Each cell In Intersect(sel, sel.Parent.UsedRange) + If Not IsEmpty(cell.Value) And Not cell.HasFormula Then + cell.Value = CStr(cell.Value) + End If + Next cell + + Application.ScreenUpdating = True + Application.Calculation = xlCalculationAutomatic + +End Sub + + +Sub Rand_KeepCellsWithText() + + Selection.SpecialCells(xlCellTypeConstants).Select + +End Sub + +Sub Rand_DeleteHiddenSheets() + + Dim sht As Worksheet + + Application.DisplayAlerts = False + + For Each sht In Worksheets + If sht.Visible = xlSheetHidden Then + sht.Delete + End If + Next sht + + Application.DisplayAlerts = True + +End Sub diff --git a/src/code/Ribbon_Callbacks.bas b/src/code/Ribbon_Callbacks.bas index ea19e99..a5b6b4b 100644 --- a/src/code/Ribbon_Callbacks.bas +++ b/src/code/Ribbon_Callbacks.bas @@ -1,230 +1,232 @@ -Attribute VB_Name = "Ribbon_Callbacks" -'--------------------------------------------------------------------------------------- -' Module : Ribbon_Callbacks -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains all of the callbacks used by the Ribbon XML file -'--------------------------------------------------------------------------------------- - -Dim frm_chartGrid As New form_chtGrid - -Public Sub btn_aboutForm_onAction(control As IRibbonControl) - -'catch the rare case where the add-in is opened directly - If ActiveWorkbook Is Nothing Then - Application.Workbooks.Add - End If - - ActiveWorkbook.FollowHyperlink "https://github.com/byronwall/bUTL" - -End Sub - -Sub btn_chartAddTitles_onAction(control As IRibbonControl) - Chart_AddTitles -End Sub - -Sub btn_chartApplyColors_onAction(control As IRibbonControl) - Chart_ApplyTrendColors -End Sub - -Sub btn_chartAxisTitleBySeries_onAction(control As IRibbonControl) - Chart_AxisTitleIsSeriesTitle -End Sub - -Public Sub btn_chartBothAxis_onAction(control As IRibbonControl) - Chart_FitAxisToMaxAndMin xlCategory - Chart_FitAxisToMaxAndMin xlValue -End Sub - -Sub btn_chartExtendSeries_onAction(control As IRibbonControl) - Chart_ExtendSeriesToRanges -End Sub - -Public Sub btn_chartFindX_onAction(control As IRibbonControl) - Chart_GoToXRange -End Sub - -Public Sub btn_chartFindY_onAction(control As IRibbonControl) - Chart_GoToYRange -End Sub - -Sub btn_chartFitAutoX_onAction(control As IRibbonControl) - Chart_Axis_AutoX -End Sub - -Sub btn_chartFitAutoY_onAction(control As IRibbonControl) - Chart_Axis_AutoY -End Sub - -Public Sub btn_chartFitX_onAction(control As IRibbonControl) - Chart_FitAxisToMaxAndMin xlCategory -End Sub - -Public Sub btn_chartFlipXY_onAction(control As IRibbonControl) - ChartFlipXYValues -End Sub - -Public Sub btn_chartFormat_onAction(control As IRibbonControl) - ChartDefaultFormat -End Sub - -Public Sub btn_chartMergeSeries_onAction(control As IRibbonControl) - ChartMergeSeries -End Sub - -Public Sub btn_chartPivot_onAction(control As IRibbonControl) - ChartDefaultFormat -End Sub - -Sub btn_chartSplitSeries_onAction(control As IRibbonControl) - ChartSplitSeries -End Sub - -Sub btn_chartTimeSeries_onAction(control As IRibbonControl) - CreateMultipleTimeSeries -End Sub - -Sub btn_chartTrendLines_onAction(control As IRibbonControl) - Chart_AddTrendlineToSeriesAndColor -End Sub - -Public Sub btn_chartXYMatrix_onAction(control As IRibbonControl) - ChartCreateXYGrid -End Sub - -Public Sub btn_chartYAxis_onAction(control As IRibbonControl) - Chart_FitAxisToMaxAndMin xlValue -End Sub - -Public Sub btn_chtGrid_onAction(control As IRibbonControl) - frm_chartGrid.Show -End Sub - -Public Sub btn_colorCategory_onAction(control As IRibbonControl) - CategoricalColoring -End Sub - -Public Sub btn_colorize_onAction(control As IRibbonControl) - Colorize -End Sub - -Public Sub btn_convertValue_onAction(control As IRibbonControl) - SelectedToValue -End Sub - -Public Sub btn_copyClear_onAction(control As IRibbonControl) - CopyClear -End Sub - -Public Sub btn_cutTranspose_onAction(control As IRibbonControl) - CutPasteTranspose -End Sub - -Public Sub btn_extendArray_onAction(control As IRibbonControl) - ExtendArrayFormulaDown -End Sub - -Public Sub btn_fillDown_onAction(control As IRibbonControl) - FillValueDown -End Sub - -Sub btn_fmtDateTime_onAction(control As IRibbonControl) - Selection.NumberFormat = "mm/dd/yyyy HH:MM" -End Sub - -Public Sub btn_folder_onAction(control As IRibbonControl) - OpenContainingFolder -End Sub - -Public Sub btn_hyperlink_onAction(control As IRibbonControl) - MakeHyperlinks -End Sub - -Public Sub btn_joinCells_onAction(control As IRibbonControl) - CombineCells -End Sub - -Public Sub btn_openNewFeatures_onAction(control As IRibbonControl) - Dim frm As New form_newCommands - frm.Show -End Sub - -Public Sub btn_panelCharts_onAction(control As IRibbonControl) - MsgBox "feature is not implemented yet..." -End Sub - -Public Sub btn_piRecalc_onAction(control As IRibbonControl) - ForceRecalc -End Sub - -Public Sub btn_protect_onAction(control As IRibbonControl) - LockAllSheets -End Sub - -Public Sub btn_rmvComments_onAction(control As IRibbonControl) - RemoveComments -End Sub - -Public Sub btn_seriesSplit_onAction(control As IRibbonControl) - SeriesSplit -End Sub - -' -Sub btn_sheetDeleteHiddenRows_onAction(control As IRibbonControl) - Sheet_DeleteHiddenRows -End Sub -' - -Public Sub btn_sheetNamesOutput_onAction(control As IRibbonControl) - OutputSheets -End Sub - -Sub btn_sht_unhide_onAction(control As IRibbonControl) - Dim sht As Worksheet - - For Each sht In Sheets - sht.Visible = xlSheetVisible - Next sht -End Sub - -Public Sub btn_split_onAction(control As IRibbonControl) - SplitAndKeep -End Sub - -Public Sub btn_splitCol_onAction(control As IRibbonControl) - SplitIntoColumns -End Sub - -Public Sub btn_splitRows_onAction(control As IRibbonControl) - SplitIntoRows -End Sub - -Public Sub btn_toNumeric_onAction(control As IRibbonControl) - ConvertToNumber -End Sub - -Public Sub btn_trimSelection_onAction(control As IRibbonControl) - TrimSelection -End Sub - -Public Sub btn_unprotectAll_onAction(control As IRibbonControl) - UnlockAllSheets -End Sub - -Public Sub btn_updateScrollbars_onAction(control As IRibbonControl) - UpdateScrollbars -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : RibbonOnLoad -' Author : @byronwall -' Date : 2015 08 05 -' Purpose : OnLoad entry point for the add-in -'--------------------------------------------------------------------------------------- -' -Public Sub RibbonOnLoad(ribbon As IRibbonUI) - - SetUpKeyboardHooksForSelection - -End Sub - +Attribute VB_Name = "Ribbon_Callbacks" +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : Ribbon_Callbacks +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains all of the callbacks used by the Ribbon XML file +'--------------------------------------------------------------------------------------- + +Dim frm_chartGrid As New form_chtGrid + +Public Sub btn_aboutForm_onAction(control As IRibbonControl) + +'catch the rare case where the add-in is opened directly + If ActiveWorkbook Is Nothing Then + Application.Workbooks.Add + End If + + ActiveWorkbook.FollowHyperlink "https://github.com/byronwall/bUTL" + +End Sub + +Sub btn_chartAddTitles_onAction(control As IRibbonControl) + Chart_AddTitles +End Sub + +Sub btn_chartApplyColors_onAction(control As IRibbonControl) + Chart_ApplyTrendColors +End Sub + +Sub btn_chartAxisTitleBySeries_onAction(control As IRibbonControl) + Chart_AxisTitleIsSeriesTitle +End Sub + +Public Sub btn_chartBothAxis_onAction(control As IRibbonControl) + Chart_FitAxisToMaxAndMin xlCategory + Chart_FitAxisToMaxAndMin xlValue +End Sub + +Sub btn_chartExtendSeries_onAction(control As IRibbonControl) + Chart_ExtendSeriesToRanges +End Sub + +Public Sub btn_chartFindX_onAction(control As IRibbonControl) + Chart_GoToXRange +End Sub + +Public Sub btn_chartFindY_onAction(control As IRibbonControl) + Chart_GoToYRange +End Sub + +Sub btn_chartFitAutoX_onAction(control As IRibbonControl) + Chart_Axis_AutoX +End Sub + +Sub btn_chartFitAutoY_onAction(control As IRibbonControl) + Chart_Axis_AutoY +End Sub + +Public Sub btn_chartFitX_onAction(control As IRibbonControl) + Chart_FitAxisToMaxAndMin xlCategory +End Sub + +Public Sub btn_chartFlipXY_onAction(control As IRibbonControl) + ChartFlipXYValues +End Sub + +Public Sub btn_chartFormat_onAction(control As IRibbonControl) + ChartDefaultFormat +End Sub + +Public Sub btn_chartMergeSeries_onAction(control As IRibbonControl) + ChartMergeSeries +End Sub + +Public Sub btn_chartPivot_onAction(control As IRibbonControl) + ChartDefaultFormat +End Sub + +Sub btn_chartSplitSeries_onAction(control As IRibbonControl) + ChartSplitSeries +End Sub + +Sub btn_chartTimeSeries_onAction(control As IRibbonControl) + CreateMultipleTimeSeries +End Sub + +Sub btn_chartTrendLines_onAction(control As IRibbonControl) + Chart_AddTrendlineToSeriesAndColor +End Sub + +Public Sub btn_chartXYMatrix_onAction(control As IRibbonControl) + ChartCreateXYGrid +End Sub + +Public Sub btn_chartYAxis_onAction(control As IRibbonControl) + Chart_FitAxisToMaxAndMin xlValue +End Sub + +Public Sub btn_chtGrid_onAction(control As IRibbonControl) + frm_chartGrid.Show +End Sub + +Public Sub btn_colorCategory_onAction(control As IRibbonControl) + CategoricalColoring +End Sub + +Public Sub btn_colorize_onAction(control As IRibbonControl) + Colorize +End Sub + +Public Sub btn_convertValue_onAction(control As IRibbonControl) + SelectedToValue +End Sub + +Public Sub btn_copyClear_onAction(control As IRibbonControl) + MsgBox "Copy clear is missing" +End Sub + +Public Sub btn_cutTranspose_onAction(control As IRibbonControl) + CutPasteTranspose +End Sub + +Public Sub btn_extendArray_onAction(control As IRibbonControl) + ExtendArrayFormulaDown +End Sub + +Public Sub btn_fillDown_onAction(control As IRibbonControl) + FillValueDown +End Sub + +Sub btn_fmtDateTime_onAction(control As IRibbonControl) + Selection.NumberFormat = "mm/dd/yyyy HH:MM" +End Sub + +Public Sub btn_folder_onAction(control As IRibbonControl) + OpenContainingFolder +End Sub + +Public Sub btn_hyperlink_onAction(control As IRibbonControl) + MakeHyperlinks +End Sub + +Public Sub btn_joinCells_onAction(control As IRibbonControl) + CombineCells +End Sub + +Public Sub btn_openNewFeatures_onAction(control As IRibbonControl) + Dim frm As New form_newCommands + frm.Show +End Sub + +Public Sub btn_panelCharts_onAction(control As IRibbonControl) + MsgBox "feature is not implemented yet..." +End Sub + +Public Sub btn_piRecalc_onAction(control As IRibbonControl) + ForceRecalc +End Sub + +Public Sub btn_protect_onAction(control As IRibbonControl) + LockAllSheets +End Sub + +Public Sub btn_rmvComments_onAction(control As IRibbonControl) + MsgBox "RemoveComments missing" +End Sub + +Public Sub btn_seriesSplit_onAction(control As IRibbonControl) + SeriesSplit +End Sub + +' +Sub btn_sheetDeleteHiddenRows_onAction(control As IRibbonControl) + Sheet_DeleteHiddenRows +End Sub +' + +Public Sub btn_sheetNamesOutput_onAction(control As IRibbonControl) + OutputSheets +End Sub + +Sub btn_sht_unhide_onAction(control As IRibbonControl) + Dim sht As Worksheet + + For Each sht In Sheets + sht.Visible = xlSheetVisible + Next sht +End Sub + +Public Sub btn_split_onAction(control As IRibbonControl) + SplitAndKeep +End Sub + +Public Sub btn_splitCol_onAction(control As IRibbonControl) + SplitIntoColumns +End Sub + +Public Sub btn_splitRows_onAction(control As IRibbonControl) + SplitIntoRows +End Sub + +Public Sub btn_toNumeric_onAction(control As IRibbonControl) + ConvertToNumber +End Sub + +Public Sub btn_trimSelection_onAction(control As IRibbonControl) + TrimSelection +End Sub + +Public Sub btn_unprotectAll_onAction(control As IRibbonControl) + UnlockAllSheets +End Sub + +Public Sub btn_updateScrollbars_onAction(control As IRibbonControl) + UpdateScrollbars +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : RibbonOnLoad +' Author : @byronwall +' Date : 2015 08 05 +' Purpose : OnLoad entry point for the add-in +'--------------------------------------------------------------------------------------- +' +Public Sub RibbonOnLoad(ribbon As IRibbonUI) + + SetUpKeyboardHooksForSelection + +End Sub + diff --git a/src/code/SelectionMgr.bas b/src/code/SelectionMgr.bas index c0029cd..f53fcde 100644 --- a/src/code/SelectionMgr.bas +++ b/src/code/SelectionMgr.bas @@ -1,101 +1,101 @@ -Attribute VB_Name = "SelectionMgr" -'--------------------------------------------------------------------------------------- -' Module : SelectionMgr -' Author : @byronwall -' Date : 2015 08 05 -' Purpose : This module contains code related to changing the Selection with kbd shortcuts -'--------------------------------------------------------------------------------------- - -Option Explicit - -'--------------------------------------------------------------------------------------- -' Procedure : OffsetSelectionByRowsAndColumns -' Author : @byronwall -' Date : 2015 08 05 -' Purpose : Offsets and selects the Selection a given number of rows/columns -'--------------------------------------------------------------------------------------- -' -Sub OffsetSelectionByRowsAndColumns(iRowsOff As Integer, iColsOff As Integer) - - If TypeOf Selection Is Range Then - - 'this error should only get called if the new range is outside the sheet boundaries - On Error GoTo OffsetSelectionByRowsAndColumns_Exit - - Selection.Offset(iRowsOff, iColsOff).Select - - On Error GoTo 0 - End If - -OffsetSelectionByRowsAndColumns_Exit: - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : SelectionOffsetDown -' Author : @byronwall -' Date : 2015 08 05 -' Purpose : Moves Selection down one row -'--------------------------------------------------------------------------------------- -' -Sub SelectionOffsetDown() - - Call OffsetSelectionByRowsAndColumns(1, 0) - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : SelectionOffsetLeft -' Author : @byronwall -' Date : 2015 08 05 -' Purpose : Moves Selection left one column -'--------------------------------------------------------------------------------------- -' -Sub SelectionOffsetLeft() - - Call OffsetSelectionByRowsAndColumns(0, -1) - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : SelectionOffsetRight -' Author : @byronwall -' Date : 2015 08 05 -' Purpose : Moves selection right one column -'--------------------------------------------------------------------------------------- -' -Sub SelectionOffsetRight() - - Call OffsetSelectionByRowsAndColumns(0, 1) - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : SelectionOffsetUp -' Author : @byronwall -' Date : 2015 08 05 -' Purpose : Moves Selection up one row -'--------------------------------------------------------------------------------------- -' -Sub SelectionOffsetUp() - - Call OffsetSelectionByRowsAndColumns(-1, 0) - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : SetUpKeyboardHooksForSelection -' Author : @byronwall -' Date : 2015 08 05 -' Purpose : Creates hotkey events for the selection events -'--------------------------------------------------------------------------------------- -' -Sub SetUpKeyboardHooksForSelection() - - Application.OnKey "^%{RIGHT}", "SelectionOffsetRight" - Application.OnKey "^%{LEFT}", "SelectionOffsetLeft" - Application.OnKey "^%{UP}", "SelectionOffsetUp" - Application.OnKey "^%{DOWN}", "SelectionOffsetDown" - -End Sub - +Attribute VB_Name = "SelectionMgr" +'--------------------------------------------------------------------------------------- +' Module : SelectionMgr +' Author : @byronwall +' Date : 2015 08 05 +' Purpose : This module contains code related to changing the Selection with kbd shortcuts +'--------------------------------------------------------------------------------------- + +Option Explicit + +'--------------------------------------------------------------------------------------- +' Procedure : OffsetSelectionByRowsAndColumns +' Author : @byronwall +' Date : 2015 08 05 +' Purpose : Offsets and selects the Selection a given number of rows/columns +'--------------------------------------------------------------------------------------- +' +Sub OffsetSelectionByRowsAndColumns(iRowsOff As Long, iColsOff As Long) + + If TypeOf Selection Is Range Then + + 'this error should only get called if the new range is outside the sheet boundaries + On Error GoTo OffsetSelectionByRowsAndColumns_Exit + + Selection.Offset(iRowsOff, iColsOff).Select + + On Error GoTo 0 + End If + +OffsetSelectionByRowsAndColumns_Exit: + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : SelectionOffsetDown +' Author : @byronwall +' Date : 2015 08 05 +' Purpose : Moves Selection down one row +'--------------------------------------------------------------------------------------- +' +Sub SelectionOffsetDown() + + Call OffsetSelectionByRowsAndColumns(1, 0) + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : SelectionOffsetLeft +' Author : @byronwall +' Date : 2015 08 05 +' Purpose : Moves Selection left one column +'--------------------------------------------------------------------------------------- +' +Sub SelectionOffsetLeft() + + Call OffsetSelectionByRowsAndColumns(0, -1) + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : SelectionOffsetRight +' Author : @byronwall +' Date : 2015 08 05 +' Purpose : Moves selection right one column +'--------------------------------------------------------------------------------------- +' +Sub SelectionOffsetRight() + + Call OffsetSelectionByRowsAndColumns(0, 1) + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : SelectionOffsetUp +' Author : @byronwall +' Date : 2015 08 05 +' Purpose : Moves Selection up one row +'--------------------------------------------------------------------------------------- +' +Sub SelectionOffsetUp() + + Call OffsetSelectionByRowsAndColumns(-1, 0) + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : SetUpKeyboardHooksForSelection +' Author : @byronwall +' Date : 2015 08 05 +' Purpose : Creates hotkey events for the selection events +'--------------------------------------------------------------------------------------- +' +Sub SetUpKeyboardHooksForSelection() + + Application.OnKey "^%{RIGHT}", "SelectionOffsetRight" + Application.OnKey "^%{LEFT}", "SelectionOffsetLeft" + Application.OnKey "^%{UP}", "SelectionOffsetUp" + Application.OnKey "^%{DOWN}", "SelectionOffsetDown" + +End Sub + diff --git a/src/code/Sheet_Helpers.bas b/src/code/Sheet_Helpers.bas index d932816..a0c5ac7 100644 --- a/src/code/Sheet_Helpers.bas +++ b/src/code/Sheet_Helpers.bas @@ -1,167 +1,170 @@ -Attribute VB_Name = "Sheet_Helpers" -'--------------------------------------------------------------------------------------- -' Module : Sheet_Helpers -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains code related to sheets and sheet processing -'--------------------------------------------------------------------------------------- - -'--------------------------------------------------------------------------------------- -' Procedure : LockAllSheets -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Locks all sheets with the same password -'--------------------------------------------------------------------------------------- -' -Sub LockAllSheets() - - Dim pass As Variant - pass = Application.InputBox("Password to lock") - - If pass = False Then - MsgBox "Cancelled." - Else - Application.ScreenUpdating = False - - 'Changed to activeworkbook so if add-in is not installed, it will target the active book rather than the xlam - For Each Sheet In ActiveWorkbook.Sheets - On Error Resume Next - Sheet.Protect (pass) - Next - - Application.ScreenUpdating = True - End If - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : OutputSheets -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Creates a new worksheet with a list and link to each sheet -'--------------------------------------------------------------------------------------- -' -Sub OutputSheets() - - Dim wsOut As Worksheet - Set wsOut = Worksheets.Add(Before:=Worksheets(1)) - wsOut.Activate - - Dim rngOut As Range - Set rngOut = wsOut.Range("B2") - - Dim iRow As Integer - iRow = 0 - - Dim sht As Worksheet - For Each sht In Worksheets - - If sht.name <> wsOut.name Then - - sht.Hyperlinks.Add _ - rngOut.Offset(iRow), "", _ - "'" & sht.name & "'!A1", , _ - sht.name - iRow = iRow + 1 - - End If - Next sht - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : UnlockAllSheets -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Unlocks all sheets with the same password -'--------------------------------------------------------------------------------------- -' -Sub UnlockAllSheets() - - Dim pass As Variant - pass = Application.InputBox("Password to unlock") - - Dim iErr As Integer - iErr = 0 - - If pass = False Then - MsgBox "Cancelled." - Else - Application.ScreenUpdating = False - 'Changed to activeworkbook so if add-in is not installed, it will target the active book rather than the xlam - Dim sht As Worksheet - For Each sht In ActiveWorkbook.Sheets - 'Let's keep track of the errors to inform the user - If Err.Number <> 0 Then iErr = iErr + 1 - Err.Clear - On Error Resume Next - sht.Unprotect (pass) - - Next sht - If Err.Number <> 0 Then iErr = iErr + 1 - Application.ScreenUpdating = True - End If - If iErr <> 0 Then - MsgBox (iErr & " sheets could not be unlocked due to bad password.") - End If -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : AscendSheets -' Author : @raymondwise -' Date : 2015 08 07 -' Purpose : Places worksheets in ascending alphabetical order. -'--------------------------------------------------------------------------------------- -Sub AscendSheets() -Application.ScreenUpdating = False -Dim wb As Workbook -Set wb = ActiveWorkbook - -Dim intSheets As Integer -intSheets = wb.Sheets.count - -Dim i As Integer -Dim j As Integer - -With wb - For j = 1 To intSheets - For i = 1 To intSheets - 1 - If UCase(.Sheets(i).name) > UCase(.Sheets(i + 1).name) Then - .Sheets(i).Move after:=.Sheets(i + 1) - End If - Next i - Next j -End With - -Application.ScreenUpdating = True -End Sub -'--------------------------------------------------------------------------------------- -' Procedure : DescendSheets -' Author : @raymondwise -' Date : 2015 08 07 -' Purpose : Places worksheets in descending alphabetical order. -'--------------------------------------------------------------------------------------- -Sub DescendSheets() -Application.ScreenUpdating = False -Dim wb As Workbook -Set wb = ActiveWorkbook - -Dim intSheets As Integer -intSheets = wb.Sheets.count - -Dim i As Integer -Dim j As Integer - -With wb - For j = 1 To intSheets - For i = 1 To intSheets - 1 - If UCase(.Sheets(i).name) < UCase(.Sheets(i + 1).name) Then - .Sheets(i).Move after:=.Sheets(i + 1) - End If - Next i - Next j -End With - -Application.ScreenUpdating = True -End Sub - +Attribute VB_Name = "Sheet_Helpers" +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : Sheet_Helpers +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains code related to sheets and sheet processing +'--------------------------------------------------------------------------------------- + +'--------------------------------------------------------------------------------------- +' Procedure : LockAllSheets +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Locks all sheets with the same password +'--------------------------------------------------------------------------------------- +' +Sub LockAllSheets() + + Dim pass As Variant + pass = Application.InputBox("Password to lock") + + If pass = False Then + MsgBox "Cancelled." + Else + Application.ScreenUpdating = False + + 'Changed to activeworkbook so if add-in is not installed, it will target the active book rather than the xlam + Dim sheet As Worksheet + For Each sheet In ActiveWorkbook.Sheets + On Error Resume Next + sheet.Protect (pass) + Next + + Application.ScreenUpdating = True + End If + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : OutputSheets +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Creates a new worksheet with a list and link to each sheet +'--------------------------------------------------------------------------------------- +' +Sub OutputSheets() + + Dim wsOut As Worksheet + Set wsOut = Worksheets.Add(Before:=Worksheets(1)) + wsOut.Activate + + Dim rngOut As Range + Set rngOut = wsOut.Range("B2") + + Dim iRow As Long + iRow = 0 + + Dim sht As Worksheet + For Each sht In Worksheets + + If sht.name <> wsOut.name Then + + sht.Hyperlinks.Add _ + rngOut.Offset(iRow), "", _ + "'" & sht.name & "'!A1", , _ + sht.name + iRow = iRow + 1 + + End If + Next sht + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : UnlockAllSheets +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Unlocks all sheets with the same password +'--------------------------------------------------------------------------------------- +' +Sub UnlockAllSheets() + + Dim pass As Variant + pass = Application.InputBox("Password to unlock") + + Dim iErr As Long + iErr = 0 + + If pass = False Then + MsgBox "Cancelled." + Else + Application.ScreenUpdating = False + 'Changed to activeworkbook so if add-in is not installed, it will target the active book rather than the xlam + Dim sht As Worksheet + For Each sht In ActiveWorkbook.Sheets + 'Let's keep track of the errors to inform the user + If Err.Number <> 0 Then iErr = iErr + 1 + Err.Clear + On Error Resume Next + sht.Unprotect (pass) + + Next sht + If Err.Number <> 0 Then iErr = iErr + 1 + Application.ScreenUpdating = True + End If + If iErr <> 0 Then + MsgBox (iErr & " sheets could not be unlocked due to bad password.") + End If +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : AscendSheets +' Author : @raymondwise +' Date : 2015 08 07 +' Purpose : Places worksheets in ascending alphabetical order. +'--------------------------------------------------------------------------------------- +Sub AscendSheets() +Application.ScreenUpdating = False +Dim wb As Workbook +Set wb = ActiveWorkbook + +Dim intSheets As Long +intSheets = wb.Sheets.count + +Dim i As Long +Dim j As Long + +With wb + For j = 1 To intSheets + For i = 1 To intSheets - 1 + If UCase(.Sheets(i).name) > UCase(.Sheets(i + 1).name) Then + .Sheets(i).Move after:=.Sheets(i + 1) + End If + Next i + Next j +End With + +Application.ScreenUpdating = True +End Sub +'--------------------------------------------------------------------------------------- +' Procedure : DescendSheets +' Author : @raymondwise +' Date : 2015 08 07 +' Purpose : Places worksheets in descending alphabetical order. +'--------------------------------------------------------------------------------------- +Sub DescendSheets() +Application.ScreenUpdating = False +Dim wb As Workbook +Set wb = ActiveWorkbook + +Dim intSheets As Long +intSheets = wb.Sheets.count + +Dim i As Long +Dim j As Long + +With wb + For j = 1 To intSheets + For i = 1 To intSheets - 1 + If UCase(.Sheets(i).name) < UCase(.Sheets(i + 1).name) Then + .Sheets(i).Move after:=.Sheets(i + 1) + End If + Next i + Next j +End With + +Application.ScreenUpdating = True +End Sub + diff --git a/src/code/SubsFuncs_Helpers.bas b/src/code/SubsFuncs_Helpers.bas index 0d002d3..d01a4ed 100644 --- a/src/code/SubsFuncs_Helpers.bas +++ b/src/code/SubsFuncs_Helpers.bas @@ -1,120 +1,122 @@ -Attribute VB_Name = "SubsFuncs_Helpers" -'--------------------------------------------------------------------------------------- -' Module : SubsFuncs_Helpers -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains some common helper code across the add-in -'--------------------------------------------------------------------------------------- - -'--------------------------------------------------------------------------------------- -' Procedure : GetInputOrSelection -' Author : @byronwall -' Date : 2015 08 11 -' Purpose : Provides a single Function to get the Selection or Input with error handling -'--------------------------------------------------------------------------------------- -' -Function GetInputOrSelection(msg As String) As Range - - Dim strDefault As String - - If TypeOf Selection Is Range Then - strDefault = Selection.Address - End If - - On Error GoTo ErrorNoSelection - Set GetInputOrSelection = Application.InputBox(msg, Type:=8, Default:=strDefault) - - Exit Function - -ErrorNoSelection: - Set GetInputOrSelection = Nothing - -End Function - - -'--------------------------------------------------------------------------------------- -' Procedure : RangeEnd -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Helper function to return a block of cells using a starting Range and an End direction -'--------------------------------------------------------------------------------------- -' -Function RangeEnd(start As Range, direction As XlDirection, Optional direction2 As XlDirection = -1) As Range - - If direction2 = -1 Then - Set RangeEnd = Range(start, start.End(direction)) - Else - Set RangeEnd = Range(start, start.End(direction).End(direction2)) - End If -End Function - -'--------------------------------------------------------------------------------------- -' Procedure : RangeEnd_Boundary -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Helper function to return a range limited by the starting cell's CurrentRegion -'--------------------------------------------------------------------------------------- -' -Function RangeEnd_Boundary(start As Range, direction As XlDirection, Optional direction2 As XlDirection = -1) As Range - - If direction2 = -1 Then - Set RangeEnd_Boundary = Intersect(Range(start, start.End(direction)), start.CurrentRegion) - Else - Set RangeEnd_Boundary = Intersect(Range(start, start.End(direction).End(direction2)), start.CurrentRegion) - End If -End Function - -'--------------------------------------------------------------------------------------- -' Procedure : QuickSort -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Sorting implementation for arrays -' Source : http://stackoverflow.com/a/152325/4288101 -' http://en.allexperts.com/q/Visual-Basic-1048/string-manipulation.htm -'--------------------------------------------------------------------------------------- -' -Public Sub QuickSort(vArray As Variant, Optional inLow As Variant, Optional inHi As Variant) - - Dim pivot As Variant - Dim tmpSwap As Variant - Dim tmpLow As Long - Dim tmpHi As Long - - If IsMissing(inLow) Then - inLow = LBound(vArray) - End If - - If IsMissing(inHi) Then - inHi = UBound(vArray) - End If - - tmpLow = inLow - tmpHi = inHi - - pivot = vArray((inLow + inHi) \ 2) - - While (tmpLow <= tmpHi) - - While (UCase(vArray(tmpLow)) < UCase(pivot) And tmpLow < inHi) - tmpLow = tmpLow + 1 - Wend - - While (UCase(pivot) < UCase(vArray(tmpHi)) And tmpHi > inLow) - tmpHi = tmpHi - 1 - Wend - - If (tmpLow <= tmpHi) Then - tmpSwap = vArray(tmpLow) - vArray(tmpLow) = vArray(tmpHi) - vArray(tmpHi) = tmpSwap - tmpLow = tmpLow + 1 - tmpHi = tmpHi - 1 - End If - - Wend - - If (inLow < tmpHi) Then QuickSort vArray, inLow, tmpHi - If (tmpLow < inHi) Then QuickSort vArray, tmpLow, inHi - -End Sub - +Attribute VB_Name = "SubsFuncs_Helpers" +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : SubsFuncs_Helpers +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains some common helper code across the add-in +'--------------------------------------------------------------------------------------- + +'--------------------------------------------------------------------------------------- +' Procedure : GetInputOrSelection +' Author : @byronwall +' Date : 2015 08 11 +' Purpose : Provides a single Function to get the Selection or Input with error handling +'--------------------------------------------------------------------------------------- +' +Function GetInputOrSelection(msg As String) As Range + + Dim strDefault As String + + If TypeOf Selection Is Range Then + strDefault = Selection.Address + End If + + On Error GoTo ErrorNoSelection + Set GetInputOrSelection = Application.InputBox(msg, Type:=8, Default:=strDefault) + + Exit Function + +ErrorNoSelection: + Set GetInputOrSelection = Nothing + +End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : RangeEnd +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Helper function to return a block of cells using a starting Range and an End direction +'--------------------------------------------------------------------------------------- +' +Function RangeEnd(start As Range, direction As XlDirection, Optional direction2 As XlDirection = -1) As Range + + If direction2 = -1 Then + Set RangeEnd = Range(start, start.End(direction)) + Else + Set RangeEnd = Range(start, start.End(direction).End(direction2)) + End If +End Function + +'--------------------------------------------------------------------------------------- +' Procedure : RangeEnd_Boundary +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Helper function to return a range limited by the starting cell's CurrentRegion +'--------------------------------------------------------------------------------------- +' +Function RangeEnd_Boundary(start As Range, direction As XlDirection, Optional direction2 As XlDirection = -1) As Range + + If direction2 = -1 Then + Set RangeEnd_Boundary = Intersect(Range(start, start.End(direction)), start.CurrentRegion) + Else + Set RangeEnd_Boundary = Intersect(Range(start, start.End(direction).End(direction2)), start.CurrentRegion) + End If +End Function + +'--------------------------------------------------------------------------------------- +' Procedure : QuickSort +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Sorting implementation for arrays +' Source : http://stackoverflow.com/a/152325/4288101 +' http://en.allexperts.com/q/Visual-Basic-1048/string-manipulation.htm +'--------------------------------------------------------------------------------------- +' +Public Sub QuickSort(vArray As Variant, Optional inLow As Variant, Optional inHi As Variant) + + Dim pivot As Variant + Dim tmpSwap As Variant + Dim tmpLow As Long + Dim tmpHi As Long + + If IsMissing(inLow) Then + inLow = LBound(vArray) + End If + + If IsMissing(inHi) Then + inHi = UBound(vArray) + End If + + tmpLow = inLow + tmpHi = inHi + + pivot = vArray((inLow + inHi) \ 2) + + While (tmpLow <= tmpHi) + + While (UCase(vArray(tmpLow)) < UCase(pivot) And tmpLow < inHi) + tmpLow = tmpLow + 1 + Wend + + While (UCase(pivot) < UCase(vArray(tmpHi)) And tmpHi > inLow) + tmpHi = tmpHi - 1 + Wend + + If (tmpLow <= tmpHi) Then + tmpSwap = vArray(tmpLow) + vArray(tmpLow) = vArray(tmpHi) + vArray(tmpHi) = tmpSwap + tmpLow = tmpLow + 1 + tmpHi = tmpHi - 1 + End If + + Wend + + If (inLow < tmpHi) Then QuickSort vArray, inLow, tmpHi + If (tmpLow < inHi) Then QuickSort vArray, tmpLow, inHi + +End Sub + diff --git a/src/code/Testing.bas b/src/code/Testing.bas new file mode 100644 index 0000000..73d0f6f --- /dev/null +++ b/src/code/Testing.bas @@ -0,0 +1,172 @@ +Attribute VB_Name = "Testing" +Option Explicit + +Public Sub ComputeDistanceMatrix() + +'get the range of inputs, along with input name + Dim rng_input As Range + Set rng_input = Application.InputBox("Select input data", "Input", Type:=8) + + 'Dim rng_ID As Range + 'Set rng_ID = Application.InputBox("Select ID data", "ID", Type:=8) + + 'turning off updates makes a huge difference here... could also use array for output + Application.ScreenUpdating = False + Application.Calculation = xlCalculationManual + Application.EnableEvents = False + + 'create new workbook + Dim wkbk As Workbook + Set wkbk = Workbooks.Add + + Dim sht_out As Worksheet + Set sht_out = wkbk.Sheets(1) + sht_out.name = "scaled data" + + 'copy data over to standardize + rng_input.Copy wkbk.Sheets(1).Range("A1") + + 'go to edge of data, add a column, add STANDARDIZE, copy paste values, delete + + Dim rng_data As Range + Set rng_data = sht_out.Range("A1").CurrentRegion + + Dim rng_col As Range + For Each rng_col In rng_data.Columns + + 'edge cell + Dim rng_edge As Range + Set rng_edge = sht_out.Cells(1, sht_out.Columns.count).End(xlToLeft).Offset(, 1) + + 'do a normal dist standardization + '=STANDARDIZE(A1,AVERAGE(A:A),STDEV.S(A:A)) + + rng_edge.Formula = "=IFERROR(STANDARDIZE(" & rng_col.Cells(1, 1).Address(False, False) & ",AVERAGE(" & _ + rng_col.Address & "),STDEV.S(" & rng_col.Address & ")),0)" + + 'do a simple value over average to detect differences + rng_edge.Formula = "=IFERROR(" & rng_col.Cells(1, 1).Address(False, False) & "/AVERAGE(" & _ + rng_col.Address & "),1)" + + 'fill that down + Range(rng_edge, rng_edge.Offset(, -1).End(xlDown).Offset(, 1)).FillDown + + Next + + Application.Calculate + sht_out.UsedRange.Value = sht_out.UsedRange.Value + rng_data.EntireColumn.Delete + + Dim sht_dist As Worksheet + Set sht_dist = wkbk.Worksheets.Add() + sht_dist.name = "distances" + + Dim rng_out As Range + Set rng_out = sht_dist.Range("A1") + + 'loop through each row with each other row + Dim rng_row1 As Range + Dim rng_row2 As Range + + Set rng_input = sht_out.Range("A1").CurrentRegion + + For Each rng_row1 In rng_input.Rows + For Each rng_row2 In rng_input.Rows + + 'loop through each column and compute the distance + Dim dbl_dist_sq As Double + dbl_dist_sq = 0 + + Dim int_col As Long + For int_col = 1 To rng_row1.Cells.count + dbl_dist_sq = dbl_dist_sq + (rng_row1.Cells(1, int_col) - rng_row2.Cells(1, int_col)) ^ 2 + Next + + 'take the sqrt of that value and output + rng_out.Value = dbl_dist_sq ^ 0.5 + + 'get to next column for output + Set rng_out = rng_out.Offset(, 1) + Next + + 'drop down a row and go back to left edge + Set rng_out = rng_out.Offset(1).End(xlToLeft) + Next + + Application.EnableEvents = True + Application.Calculation = xlCalculationAutomatic + Application.ScreenUpdating = True + + sht_dist.UsedRange.NumberFormat = "0.00" + sht_dist.UsedRange.EntireColumn.AutoFit + + 'do the coloring + Formatting_AddCondFormat sht_dist.UsedRange + +End Sub + +Sub RemoveAllLegends() + + Dim cht_obj As ChartObject + + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + cht_obj.Chart.HasLegend = False + cht_obj.Chart.HasTitle = True + + cht_obj.Chart.SeriesCollection(1).MarkerSize = 4 + Next + +End Sub + +Sub ApplyFormattingToEachColumn() + Dim rng As Range + For Each rng In Selection.Columns + + Formatting_AddCondFormat rng + Next +End Sub + +Private Sub Formatting_AddCondFormat(ByVal rng As Range) + + rng.FormatConditions.AddColorScale ColorScaleType:=3 + rng.FormatConditions(rng.FormatConditions.count).SetFirstPriority + rng.FormatConditions(1).ColorScaleCriteria(1).Type = _ + xlConditionValueLowestValue + With rng.FormatConditions(1).ColorScaleCriteria(1).FormatColor + .Color = 7039480 + .TintAndShade = 0 + End With + rng.FormatConditions(1).ColorScaleCriteria(2).Type = _ + xlConditionValuePercentile + rng.FormatConditions(1).ColorScaleCriteria(2).Value = 50 + With rng.FormatConditions(1).ColorScaleCriteria(2).FormatColor + .Color = 8711167 + .TintAndShade = 0 + End With + rng.FormatConditions(1).ColorScaleCriteria(3).Type = _ + xlConditionValueHighestValue + With Selection.FormatConditions(1).ColorScaleCriteria(3).FormatColor + .Color = 8109667 + .TintAndShade = 0 + End With +End Sub + + + +'--------------------------------------------------------------------------------------- +' Procedure : TraceDependentsForAll +' Author : @byronwall +' Date : 2015 11 09 +' Purpose : Quick Sub to iterate through Selection and Trace Dependents for all +'--------------------------------------------------------------------------------------- +' +Sub TraceDependentsForAll() + + Dim rng As Range + + For Each rng In Intersect(Selection, Selection.Parent.UsedRange) + rng.ShowDependents + Next rng + +End Sub + diff --git a/src/code/UDFs.bas b/src/code/UDFs.bas index d93a08d..0776c44 100644 --- a/src/code/UDFs.bas +++ b/src/code/UDFs.bas @@ -1,32 +1,32 @@ -Attribute VB_Name = "UDFs" -'--------------------------------------------------------------------------------------- -' Module : UDFs -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains all code that is intended to be used as a UDF -'--------------------------------------------------------------------------------------- - -Option Explicit - -'--------------------------------------------------------------------------------------- -' Procedure : RandLetters -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : UDF that generates a sequence of random letters -'--------------------------------------------------------------------------------------- -' -Public Function RandLetters(num As Integer) As String - - Dim i As Integer - - Dim letters() As String - ReDim letters(1 To num) - - For i = 1 To num - letters(i) = Chr(Int(Rnd() * 26 + 65)) - Next - - RandLetters = Join(letters(), "") - -End Function - +Attribute VB_Name = "UDFs" +'--------------------------------------------------------------------------------------- +' Module : UDFs +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains all code that is intended to be used as a UDF +'--------------------------------------------------------------------------------------- + +Option Explicit + +'--------------------------------------------------------------------------------------- +' Procedure : RandLetters +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : UDF that generates a sequence of random letters +'--------------------------------------------------------------------------------------- +' +Public Function RandLetters(num As Long) As String + + Dim i As Long + + Dim letters() As String + ReDim letters(1 To num) + + For i = 1 To num + letters(i) = Chr(Int(Rnd() * 26 + 65)) + Next + + RandLetters = Join(letters(), "") + +End Function + diff --git a/src/code/Usability.bas b/src/code/Usability.bas index b1251d1..42d242f 100644 --- a/src/code/Usability.bas +++ b/src/code/Usability.bas @@ -1,606 +1,826 @@ -Attribute VB_Name = "Usability" -'--------------------------------------------------------------------------------------- -' Module : Usability -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Contains an assortment of code that automates some task -'--------------------------------------------------------------------------------------- - -'--------------------------------------------------------------------------------------- -' Procedure : ColorInputs -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Finds cells with no value and colors them based on having a formula? -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub ColorInputs() - - Dim c As Range - 'This is finding cells that aren't blank, but the description says it should be cells with no values.. - For Each c In Selection - If c.Value <> "" Then - If c.HasFormula Then - c.Interior.ThemeColor = msoThemeColorAccent1 - Else - c.Interior.ThemeColor = msoThemeColorAccent2 - End If - End If - Next c - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : CombineAllSheetsData -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Combines all sheets, resuing columns where the same -' Flag : not-used -'--------------------------------------------------------------------------------------- -' -Sub CombineAllSheetsData() - -'create the new wkbk and sheet - Dim wbCombo As Workbook - Dim wbData As Workbook - - Set wbData = ActiveWorkbook - Set wbCombo = Workbooks.Add - - Dim wsCombined As Worksheet - Set wsCombined = wbCombo.Sheets.Add - - Dim boolFirst As Boolean - boolFirst = True - - Dim iComboRow As Integer - iComboRow = 1 - - Dim wsData As Worksheet - For Each wsData In wbData.Sheets - If wsData.name <> wsCombined.name Then - - wsData.Unprotect - - 'get the headers squared up - If boolFirst Then - 'copy over all headers - wsData.Rows(1).Copy wsCombined.Range("A1") - - boolFirst = False - Else - 'search for missing columns - Dim rngHeader As Range - For Each rngHeader In Intersect(wsData.Rows(1), wsData.UsedRange) - - 'check if it exists - Dim varHdrMatch As Variant - varHdrMatch = Application.Match(rngHeader, wsCombined.Rows(1), 0) - - 'if not, add to header row - If IsError(varHdrMatch) Then - wsCombined.Range("A1").End(xlToRight).Offset(, 1) = rngHeader - End If - Next rngHeader - End If - - 'find the PnPID column for combo - Dim int_colId As Integer - int_colId = Application.Match("PnPID", wsCombined.Rows(1), 0) - - 'find the PnPID column for data - Dim iColIDData As Integer - iColIDData = Application.Match("PnPID", wsData.Rows(1), 0) - - 'add the data, row by row - Dim c As Range - For Each c In wsData.UsedRange.SpecialCells(xlCellTypeConstants) - If c.Row > 1 Then - - 'check if the PnPID exists in the combo sheet - Dim iDataRow As Variant - iDataRow = Application.Match( _ - wsData.Cells(c.Row, iColIDData), _ - wsCombined.Columns(int_colId), _ - 0) - - 'add new row if it did not exist and id number - If IsError(iDataRow) Then - iDataRow = wsCombined.Columns(int_colId).Cells(wsCombined.Rows.count, 1).End(xlUp).Offset(1).Row - wsCombined.Cells(iDataRow, int_colId) = wsData.Cells(c.Row, iColIDData) - End If - - 'get column - Dim iCol As Integer - iCol = Application.Match(wsData.Cells(1, c.Column), wsCombined.Rows(1), 0) - - 'update combo data - wsCombined.Cells(iDataRow, iCol) = c - - End If - Next c - End If - Next wsData -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ConvertSelectionToCsv -' Author : @byronwall -' Date : 2015 08 11 -' Purpose : Crude CSV output from the current selection, works with numbers -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub ConvertSelectionToCsv() - - Dim rngCSV As Range - Set rngCSV = GetInputOrSelection - - If rngCSV Is Nothing Then - Exit Sub - End If - - Dim csvOut As String - csvOut = "" - - Dim csvRow As Range - For Each csvRow In rngCSV.Rows - - Dim arr As Variant - arr = Application.Transpose(Application.Transpose(csvRow.Rows.Value2)) - - 'TODO: improve this to use another Join instead of string concats - csvOut = csvOut & Join(arr, ",") & vbCrLf - - Next csvRow - - Dim clipboard As MSForms.DataObject - Set clipboard = New MSForms.DataObject - - clipboard.SetText csvOut - clipboard.PutInClipboard - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : CopyClear -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Copies the cells and clears the source Range -'--------------------------------------------------------------------------------------- -' -Sub Sheet_DeleteHiddenRows() - 'These rows are unrecoverable - x = MsgBox("This will permanently delete hidden rows. They cannot be recovered. Are you sure?", vbYesNo) - If x = 7 Then Exit Sub - - Application.ScreenUpdating = False - - 'We might as well tell the user how many rows were hidden - Dim iCount As Integer - iCount = 0 - With ActiveSheet - For i = .UsedRange.Rows.count To 1 Step -1 - If .Rows(i).Hidden Then - .Rows(i).Delete - iCount = iCount + 1 - End If - Next i - End With - Application.ScreenUpdating = True - - MsgBox (iCount & " rows were deleted") -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : CutPasteTranspose -' Author : @byronwall, @RaymondWise -' Date : 2015 07 31 -' Purpose : Does a cut/transpose by cutting each cell individually -'--------------------------------------------------------------------------------------- -' - -'########Still Needs to address Issue#23############# -Sub CutPasteTranspose() - - On Error GoTo errHandler - Dim rngSelect As Range - 'TODO #Should use new inputbox function - Set rngSelect = Selection - - Dim rngOut As Range - Set rngOut = Application.InputBox("Select output corner", Type:=8) - - Application.ScreenUpdating = False - Application.EnableEvents = False - Application.Calculation = xlCalculationManual - - - - Dim rCorner As Range - Set rCorner = rngSelect.Cells(1, 1) - - Dim iCRow As Integer - iCRow = rCorner.Row - Dim iCCol As Integer - iCCol = rCorner.Column - - Dim iORow As Integer - Dim iOCol As Integer - iORow = rngOut.Row - iOCol = rngOut.Column - - rngOut.Activate - - 'Check to not overwrite - For Each c In rngSelect - If Not Intersect(rngSelect, Cells(iORow + c.Column - iCCol, iOCol + c.Row - iCRow)) Is Nothing Then - MsgBox ("Your destination intersects with your data") - Exit Sub - End If - Next - - Dim c As Range - For Each c In rngSelect - c.Cut - ActiveSheet.Cells(iORow + c.Column - iCCol, iOCol + c.Row - iCRow).Activate - ActiveSheet.Paste - Next c - - Application.CutCopyMode = False - - Application.ScreenUpdating = True - Application.EnableEvents = True - Application.Calculation = xlCalculationAutomatic - Application.Calculate -errHandler: -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : EvaluateArrayFormulaOnNewSheet -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Wacky thing to force an array formula to return as an array -' Flag : not-used -'--------------------------------------------------------------------------------------- -' -Sub EvaluateArrayFormulaOnNewSheet() - -'cut cell with formula - Dim StrAddress As String - Dim rngStart As Range - Set rngStart = Sheet1.Range("J2") - StrAddress = rngStart.Address - - rngStart.Cut - - 'create new sheet - Dim sht As Worksheet - Set sht = Worksheets.Add - - 'paste cell onto sheet - Dim rngArr As Range - Set rngArr = sht.Range("A1") - sht.Paste rngArr - - 'expand array formula size.. resize to whatever size is needed - rngArr.Resize(3).FormulaArray = rngArr.FormulaArray - - 'get your result - Dim VarArr As Variant - VarArr = Application.Evaluate(rngArr.CurrentArray.Address) - - ''''do something with your result here... it is an array - - - 'shrink the formula back to one cell - Dim strFormula As String - strFormula = rngArr.FormulaArray - - rngArr.CurrentArray.ClearContents - rngArr.FormulaArray = strFormula - - 'cut and paste back to original spot - rngArr.Cut - - Sheet1.Paste Sheet1.Range(StrAddress) - - Application.DisplayAlerts = False - sht.Delete - Application.DisplayAlerts = True - -End Sub - - -'--------------------------------------------------------------------------------------- -' Procedure : ExportFilesFromFolder -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Goes through a folder and process all workbooks therein -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub ExportFilesFromFolder() - '###Needs error handling - 'TODO: consider deleting this Sub since it is quite specific - Application.ScreenUpdating = False - - Dim file As Variant - Dim path As String - path = InputBox("What path?") - - file = Dir(path) - While (file <> "") - - Debug.Print path & file - - Dim FileName As String - - FileName = path & file - - Dim wbActive As Workbook - Set wbActive = Workbooks.Open(FileName) - - Dim wsActive As Worksheet - Set wsActive = wbActive.Sheets("Case Summary") - - With ActiveSheet.PageSetup - .TopMargin = Application.InchesToPoints(0.4) - .BottomMargin = Application.InchesToPoints(0.4) - End With - - wsActive.ExportAsFixedFormat xlTypePDF, path & "PDFs\" & file & ".pdf" - - wbActive.Close False - - file = Dir - Wend - - Application.ScreenUpdating = True - -End Sub - - -'--------------------------------------------------------------------------------------- -' Procedure : FillValueDown -' Author : @byronwall -' Date : 2015 08 11 -' Purpose : Does a fill of blank values from the cell above with a value -'--------------------------------------------------------------------------------------- -' -Sub FillValueDown() - - Dim rngInput As Range - Set rngInput = GetInputOrSelection() - - If rngInput Is Nothing Then - Exit Sub - End If - - Dim c As Range - For Each c In Intersect(rngInput.SpecialCells(xlCellTypeBlanks), rngInput.Parent.UsedRange) - c = c.End(xlUp) - Next c - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : ForceRecalc -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Provides a button to do a full recalc -'--------------------------------------------------------------------------------------- -' -Sub ForceRecalc() - - Application.CalculateFullRebuild - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : GenerateRandomData -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Generates a block of random data for testing questions on SO -'--------------------------------------------------------------------------------------- -' -Sub GenerateRandomData() - - Dim c As Range - Set c = Range("B2") - - Dim i As Integer - - For i = 0 To 3 - c.Offset(, i) = Chr(65 + i) - - With c.Offset(1, i).Resize(10) - Select Case i - Case 0 - .Formula = "=TODAY()+ROW()" - Case Else - .Formula = "=RANDBETWEEN(1,100)" - End Select - - .Value = .Value - End With - Next i - - ActiveSheet.UsedRange.Columns.ColumnWidth = 15 - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : OpenContainingFolder -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Open the folder that contains the ActiveWorkbook -'--------------------------------------------------------------------------------------- -' -Sub OpenContainingFolder() - - Dim wb As Workbook - Set wb = ActiveWorkbook - - If wb.path <> "" Then - wb.FollowHyperlink wb.path - Else - MsgBox "Open file is not in a folder yet." - End If - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : PivotSetAllFields -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Sets all fields in a PivotTable to use a certain calculation type -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub PivotSetAllFields() - - Dim pTable As PivotTable - Dim ws As Worksheet - - Set ws = ActiveSheet - - MsgBox "This defaults to the average for every Pivot table on the sheet. Edit code for other result." - - For Each pTable In ws.PivotTables - Dim pField As PivotField - For Each pField In pTable.DataFields - On Error Resume Next - pField.Function = xlAverage - Next pField - Next pTable - -End Sub - - -'--------------------------------------------------------------------------------------- -' Procedure : SeriesSplit -' Author : @byronwall -' Date : 2015 08 11 -' Purpose : Takes a category columns and splits the values out into new columns for each unique entry -'--------------------------------------------------------------------------------------- -' -Sub SeriesSplit() - - On Error GoTo ErrorNoSelection - - Dim rngSelection As Range - Set rngSelection = Application.InputBox("Select category range with heading", Type:=8) - Set rngSelection = Intersect(rngSelection, rngSelection.Parent.UsedRange).SpecialCells(xlCellTypeVisible, xlLogical + xlNumbers + xlTextValues) - - Dim rngValues As Range - Set rngValues = Application.InputBox("Select values range with heading", Type:=8) - Set rngValues = Intersect(rngValues, rngValues.Parent.UsedRange) - - On Error GoTo 0 - - 'determine default value - Dim strDefault As Variant - strDefault = InputBox("Enter the default value", , "#N/A") - - 'detect cancel and exit - If StrPtr(strDefault) = 0 Then - Exit Sub - End If - - Dim dictCategories As New Dictionary - - Dim rngCategory As Range - For Each rngCategory In rngSelection - 'skip the header row - If rngCategory.Address <> rngSelection.Cells(1).Address Then - dictCategories(rngCategory.Value) = 1 - End If - - Next rngCategory - - rngValues.EntireColumn.Offset(, 1).Resize(, dictCategories.count).Insert - 'head the columns with the values - - Dim varValues As Variant - Dim iCount As Integer - iCount = 1 - For Each varValues In dictCategories - rngValues.Cells(1).Offset(, iCount) = varValues - iCount = iCount + 1 - Next varValues - - 'put the formula in for each column - '=IF(RC13=R1C,RC16,#N/A) - Dim strFormula As Variant - strFormula = "=IF(RC" & rngSelection.Column & " =R" & _ - rngValues.Cells(1).Row & "C,RC" & rngValues.Column & "," & strDefault & ")" - - Dim rngFormula As Range - Set rngFormula = rngValues.Offset(1, 1).Resize(rngValues.Rows.count - 1, dictCategories.count) - rngFormula.FormulaR1C1 = strFormula - rngFormula.EntireColumn.AutoFit - - Exit Sub - -ErrorNoSelection: - 'TODO: consider removing this prompt - MsgBox "No selection made. Exiting.", , "No selection" - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : Sht_DeleteHiddenRows -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Deletes the hidden rows in a sheet. Good for a "permanent" filter -'--------------------------------------------------------------------------------------- -' -'Changed sub name to avoid reserved object name -Sub Sht_DeleteHiddenRows() - - Application.ScreenUpdating = False - Dim Row As Range - For i = ActiveSheet.UsedRange.Rows.count To 1 Step -1 - - - Set Row = ActiveSheet.Rows(i) - - If Row.Hidden Then - Row.Delete - End If - Next i - - Application.ScreenUpdating = True - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : UnhideAllRowsAndColumns -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Unhides everything in a Worksheet -' Flag : new-feature -'--------------------------------------------------------------------------------------- -' -Sub UnhideAllRowsAndColumns() - - ActiveSheet.Cells.EntireRow.Hidden = False - ActiveSheet.Cells.EntireColumn.Hidden = False - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : UpdateScrollbars -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Cheap trick that forces Excel to update the scroll bars after a large deletion -'--------------------------------------------------------------------------------------- -' -Sub UpdateScrollbars() - - Dim rng As Variant - rng = ActiveSheet.UsedRange.Address - -End Sub - +Attribute VB_Name = "Usability" +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : Usability +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Contains an assortment of code that automates some task +'--------------------------------------------------------------------------------------- + + +Sub CreatePdfOfEachXlsxFileInFolder() + + 'pick a folder + Dim diag_folder As FileDialog + Set diag_folder = Application.FileDialog(msoFileDialogFolderPicker) + + diag_folder.Show + + Dim str_path As String + str_path = diag_folder.SelectedItems(1) & "\" + + 'find all files in the folder + Dim str_file As String + str_file = Dir(str_path & "*.xlsx") + + Do While str_file <> "" + + Dim wkbk_file As Workbook + Set wkbk_file = Workbooks.Open(str_path & str_file, , True) + + Dim sht As Worksheet + + For Each sht In wkbk_file.Worksheets + sht.Range("A16").EntireRow.RowHeight = 15.75 + sht.Range("A17").EntireRow.RowHeight = 15.75 + sht.Range("A22").EntireRow.RowHeight = 15.75 + sht.Range("A23").EntireRow.RowHeight = 15.75 + Next + + wkbk_file.ExportAsFixedFormat xlTypePDF, str_path & str_file & ".pdf" + wkbk_file.Close False + + str_file = Dir + Loop +End Sub + +Sub MakeSeveralBoxesWithNumbers() + + Dim shp As Shape + Dim sht As Worksheet + + Dim rng_loc As Range + Set rng_loc = Application.InputBox("select range", Type:=8) + + Set sht = ActiveSheet + + Dim int_counter As Long + + For int_counter = 1 To InputBox("How many?") + + Set shp = sht.Shapes.AddTextbox(msoShapeRectangle, rng_loc.left, _ + rng_loc.top + 20 * int_counter, 20, 20) + + shp.Title = int_counter + + shp.Fill.Visible = msoFalse + shp.Line.Visible = msoFalse + + shp.TextFrame2.TextRange.Characters.Text = int_counter + + With shp.TextFrame2.TextRange.Font.Fill + .Visible = msoTrue + .ForeColor.RGB = RGB(0, 0, 0) + .Transparency = 0 + .Solid + End With + + Next + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ColorInputs +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Finds cells with no value and colors them based on having a formula? +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub ColorInputs() + + Dim c As Range + 'This is finding cells that aren't blank, but the description says it should be cells with no values.. + For Each c In Selection + If c.Value <> "" Then + If c.HasFormula Then + c.Interior.ThemeColor = msoThemeColorAccent1 + Else + c.Interior.ThemeColor = msoThemeColorAccent2 + End If + End If + Next c + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : CombineAllSheetsData +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Combines all sheets, resuing columns where the same +' Flag : not-used +'--------------------------------------------------------------------------------------- +' +Sub CombineAllSheetsData() + +'create the new wkbk and sheet + Dim wbCombo As Workbook + Dim wbData As Workbook + + Set wbData = ActiveWorkbook + Set wbCombo = Workbooks.Add + + Dim wsCombined As Worksheet + Set wsCombined = wbCombo.Sheets.Add + + Dim boolFirst As Boolean + boolFirst = True + + Dim iComboRow As Long + iComboRow = 1 + + Dim wsData As Worksheet + For Each wsData In wbData.Sheets + If wsData.name <> wsCombined.name Then + + wsData.Unprotect + + 'get the headers squared up + If boolFirst Then + 'copy over all headers + wsData.Rows(1).Copy wsCombined.Range("A1") + + boolFirst = False + Else + 'search for missing columns + Dim rngHeader As Range + For Each rngHeader In Intersect(wsData.Rows(1), wsData.UsedRange) + + 'check if it exists + Dim varHdrMatch As Variant + varHdrMatch = Application.Match(rngHeader, wsCombined.Rows(1), 0) + + 'if not, add to header row + If IsError(varHdrMatch) Then + wsCombined.Range("A1").End(xlToRight).Offset(, 1) = rngHeader + End If + Next rngHeader + End If + + 'find the PnPID column for combo + Dim int_colId As Long + int_colId = Application.Match("PnPID", wsCombined.Rows(1), 0) + + 'find the PnPID column for data + Dim iColIDData As Long + iColIDData = Application.Match("PnPID", wsData.Rows(1), 0) + + 'add the data, row by row + Dim c As Range + For Each c In wsData.UsedRange.SpecialCells(xlCellTypeConstants) + If c.Row > 1 Then + + 'check if the PnPID exists in the combo sheet + Dim iDataRow As Variant + iDataRow = Application.Match( _ + wsData.Cells(c.Row, iColIDData), _ + wsCombined.Columns(int_colId), _ + 0) + + 'add new row if it did not exist and id number + If IsError(iDataRow) Then + iDataRow = wsCombined.Columns(int_colId).Cells(wsCombined.Rows.count, 1).End(xlUp).Offset(1).Row + wsCombined.Cells(iDataRow, int_colId) = wsData.Cells(c.Row, iColIDData) + End If + + 'get column + Dim iCol As Long + iCol = Application.Match(wsData.Cells(1, c.Column), wsCombined.Rows(1), 0) + + 'update combo data + wsCombined.Cells(iDataRow, iCol) = c + + End If + Next c + End If + Next wsData +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ConvertSelectionToCsv +' Author : @byronwall +' Date : 2015 08 11 +' Purpose : Crude CSV output from the current selection, works with numbers +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub ConvertSelectionToCsv() + + Dim rngCSV As Range + Set rngCSV = GetInputOrSelection("Choose range for converting to CSV") + + If rngCSV Is Nothing Then + Exit Sub + End If + + Dim csvOut As String + csvOut = "" + + Dim csvRow As Range + For Each csvRow In rngCSV.Rows + + Dim arr As Variant + arr = Application.Transpose(Application.Transpose(csvRow.Rows.Value2)) + + 'TODO: improve this to use another Join instead of string concats + csvOut = csvOut & Join(arr, ",") & vbCrLf + + Next csvRow + + Dim clipboard As MSForms.DataObject + Set clipboard = New MSForms.DataObject + + clipboard.SetText csvOut + clipboard.PutInClipboard + +End Sub + +Public Sub CopyCellAddress() +'--------------------------------------------------------------------------------------- +' Procedure : CopyCellAddress +' Author : @byronwall +' Date : 2015 12 03 +' Purpose : Copies the current cell address to the clipboard for paste use in a formula +'--------------------------------------------------------------------------------------- +' + +'TODO: this need to get a button or a keyboard shortcut for easy use + Dim clipboard As MSForms.DataObject + Set clipboard = New MSForms.DataObject + + Dim rng_sel As Range + Set rng_sel = Selection + + clipboard.SetText rng_sel.Address(True, True, xlA1, True) + clipboard.PutInClipboard +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : CopyClear +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Copies the cells and clears the source Range +'--------------------------------------------------------------------------------------- +' +Sub Sheet_DeleteHiddenRows() + 'These rows are unrecoverable + Dim x As VbMsgBoxResult + x = MsgBox("This will permanently delete hidden rows. They cannot be recovered. Are you sure?", vbYesNo) + + If Not x = vbYes Then + Exit Sub + End If + + Application.ScreenUpdating = False + + 'We might as well tell the user how many rows were hidden + Dim iCount As Long + iCount = 0 + With ActiveSheet + Dim i As Long + For i = .UsedRange.Rows.count To 1 Step -1 + If .Rows(i).Hidden Then + .Rows(i).Delete + iCount = iCount + 1 + End If + Next i + End With + Application.ScreenUpdating = True + + MsgBox (iCount & " rows were deleted") +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : CutPasteTranspose +' Author : @byronwall, @RaymondWise +' Date : 2015 07 31 +' Purpose : Does a cut/transpose by cutting each cell individually +'--------------------------------------------------------------------------------------- +' + +'########Still Needs to address Issue#23############# +Sub CutPasteTranspose() + + On Error GoTo errHandler + Dim rngSelect As Range + 'TODO #Should use new inputbox function + Set rngSelect = Selection + + Dim rngOut As Range + Set rngOut = Application.InputBox("Select output corner", Type:=8) + + Application.ScreenUpdating = False + Application.EnableEvents = False + Application.Calculation = xlCalculationManual + + + + Dim rCorner As Range + Set rCorner = rngSelect.Cells(1, 1) + + Dim iCRow As Long + iCRow = rCorner.Row + Dim iCCol As Long + iCCol = rCorner.Column + + Dim iORow As Long + Dim iOCol As Long + iORow = rngOut.Row + iOCol = rngOut.Column + + rngOut.Activate + + 'Check to not overwrite + Dim c As Range + For Each c In rngSelect + If Not Intersect(rngSelect, Cells(iORow + c.Column - iCCol, iOCol + c.Row - iCRow)) Is Nothing Then + MsgBox ("Your destination intersects with your data") + Exit Sub + End If + Next + + For Each c In rngSelect + c.Cut + ActiveSheet.Cells(iORow + c.Column - iCCol, iOCol + c.Row - iCRow).Activate + ActiveSheet.Paste + Next c + + Application.CutCopyMode = False + + Application.ScreenUpdating = True + Application.EnableEvents = True + Application.Calculation = xlCalculationAutomatic + Application.Calculate +errHandler: +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : EvaluateArrayFormulaOnNewSheet +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Wacky thing to force an array formula to return as an array +' Flag : not-used +'--------------------------------------------------------------------------------------- +' +Sub EvaluateArrayFormulaOnNewSheet() + +'cut cell with formula + Dim StrAddress As String + Dim rngStart As Range + Set rngStart = Sheet1.Range("J2") + StrAddress = rngStart.Address + + rngStart.Cut + + 'create new sheet + Dim sht As Worksheet + Set sht = Worksheets.Add + + 'paste cell onto sheet + Dim rngArr As Range + Set rngArr = sht.Range("A1") + sht.Paste rngArr + + 'expand array formula size.. resize to whatever size is needed + rngArr.Resize(3).FormulaArray = rngArr.FormulaArray + + 'get your result + Dim VarArr As Variant + VarArr = Application.Evaluate(rngArr.CurrentArray.Address) + + ''''do something with your result here... it is an array + + + 'shrink the formula back to one cell + Dim strFormula As String + strFormula = rngArr.FormulaArray + + rngArr.CurrentArray.ClearContents + rngArr.FormulaArray = strFormula + + 'cut and paste back to original spot + rngArr.Cut + + Sheet1.Paste Sheet1.Range(StrAddress) + + Application.DisplayAlerts = False + sht.Delete + Application.DisplayAlerts = True + +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : ExportFilesFromFolder +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Goes through a folder and process all workbooks therein +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub ExportFilesFromFolder() + '###Needs error handling + 'TODO: consider deleting this Sub since it is quite specific + Application.ScreenUpdating = False + + Dim file As Variant + Dim path As String + path = InputBox("What path?") + + file = Dir(path) + While (file <> "") + + Debug.Print path & file + + Dim FileName As String + + FileName = path & file + + Dim wbActive As Workbook + Set wbActive = Workbooks.Open(FileName) + + Dim wsActive As Worksheet + Set wsActive = wbActive.Sheets("Case Summary") + + With ActiveSheet.PageSetup + .TopMargin = Application.InchesToPoints(0.4) + .BottomMargin = Application.InchesToPoints(0.4) + End With + + wsActive.ExportAsFixedFormat xlTypePDF, path & "PDFs\" & file & ".pdf" + + wbActive.Close False + + file = Dir + Wend + + Application.ScreenUpdating = True + +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : FillValueDown +' Author : @byronwall +' Date : 2015 08 11 +' Purpose : Does a fill of blank values from the cell above with a value +'--------------------------------------------------------------------------------------- +' +Sub FillValueDown() + + Dim rngInput As Range + Set rngInput = GetInputOrSelection("Select range for waterfall") + + If rngInput Is Nothing Then + Exit Sub + End If + + Dim c As Range + For Each c In Intersect(rngInput.SpecialCells(xlCellTypeBlanks), rngInput.Parent.UsedRange) + c = c.End(xlUp) + Next c + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : ForceRecalc +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Provides a button to do a full recalc +'--------------------------------------------------------------------------------------- +' +Sub ForceRecalc() + + Application.CalculateFullRebuild + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : GenerateRandomData +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Generates a block of random data for testing questions on SO +'--------------------------------------------------------------------------------------- +' +Sub GenerateRandomData() + + Dim c As Range + Set c = Range("B2") + + Dim i As Long + + For i = 0 To 3 + c.Offset(, i) = Chr(65 + i) + + With c.Offset(1, i).Resize(10) + Select Case i + Case 0 + .Formula = "=TODAY()+ROW()" + Case Else + .Formula = "=RANDBETWEEN(1,100)" + End Select + + .Value = .Value + End With + Next i + + ActiveSheet.UsedRange.Columns.ColumnWidth = 15 + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : OpenContainingFolder +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Open the folder that contains the ActiveWorkbook +'--------------------------------------------------------------------------------------- +' +Sub OpenContainingFolder() + + Dim wb As Workbook + Set wb = ActiveWorkbook + + If wb.path <> "" Then + wb.FollowHyperlink wb.path + Else + MsgBox "Open file is not in a folder yet." + End If + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : PivotSetAllFields +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Sets all fields in a PivotTable to use a certain calculation type +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub PivotSetAllFields() + + Dim pTable As PivotTable + Dim ws As Worksheet + + Set ws = ActiveSheet + + MsgBox "This defaults to the average for every Pivot table on the sheet. Edit code for other result." + + For Each pTable In ws.PivotTables + Dim pField As PivotField + For Each pField In pTable.DataFields + On Error Resume Next + pField.Function = xlAverage + Next pField + Next pTable + +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : SeriesSplit +' Author : @byronwall +' Date : 2015 08 11 +' Purpose : Takes a category columns and splits the values out into new columns for each unique entry +'--------------------------------------------------------------------------------------- +' +Sub SeriesSplit() + + On Error GoTo ErrorNoSelection + + Dim rngSelection As Range + Set rngSelection = Application.InputBox("Select category range with heading", Type:=8) + Set rngSelection = Intersect(rngSelection, rngSelection.Parent.UsedRange).SpecialCells(xlCellTypeVisible, xlLogical + xlNumbers + xlTextValues) + + Dim rngValues As Range + Set rngValues = Application.InputBox("Select values range with heading", Type:=8) + Set rngValues = Intersect(rngValues, rngValues.Parent.UsedRange) + + On Error GoTo 0 + + 'determine default value + Dim strDefault As Variant + strDefault = InputBox("Enter the default value", , "#N/A") + + 'detect cancel and exit + If StrPtr(strDefault) = 0 Then + Exit Sub + End If + + Dim dictCategories As New Dictionary + + Dim rngCategory As Range + For Each rngCategory In rngSelection + 'skip the header row + If rngCategory.Address <> rngSelection.Cells(1).Address Then + dictCategories(rngCategory.Value) = 1 + End If + + Next rngCategory + + rngValues.EntireColumn.Offset(, 1).Resize(, dictCategories.count).Insert + 'head the columns with the values + + Dim varValues As Variant + Dim iCount As Long + iCount = 1 + For Each varValues In dictCategories + rngValues.Cells(1).Offset(, iCount) = varValues + iCount = iCount + 1 + Next varValues + + 'put the formula in for each column + '=IF(RC13=R1C,RC16,#N/A) + Dim strFormula As Variant + strFormula = "=IF(RC" & rngSelection.Column & " =R" & _ + rngValues.Cells(1).Row & "C,RC" & rngValues.Column & "," & strDefault & ")" + + Dim rngFormula As Range + Set rngFormula = rngValues.Offset(1, 1).Resize(rngValues.Rows.count - 1, dictCategories.count) + rngFormula.FormulaR1C1 = strFormula + rngFormula.EntireColumn.AutoFit + + Exit Sub + +ErrorNoSelection: + 'TODO: consider removing this prompt + MsgBox "No selection made. Exiting.", , "No selection" + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : SeriesSplitIntoBins +' Author : @byronwall +' Date : 2015 11 03 +' Purpose : Code will break a column of continuous data into bins for plotting +'--------------------------------------------------------------------------------------- +' +Sub SeriesSplitIntoBins() + + On Error GoTo ErrorNoSelection + + Dim rngSelection As Range + Set rngSelection = Application.InputBox("Select category range with heading", _ + Type:=8) + Set rngSelection = Intersect(rngSelection, _ + rngSelection.Parent.UsedRange).SpecialCells(xlCellTypeVisible, xlLogical + _ + xlNumbers + xlTextValues) + + Dim rngValues As Range + Set rngValues = Application.InputBox("Select values range with heading", _ + Type:=8) + Set rngValues = Intersect(rngValues, rngValues.Parent.UsedRange) + + ''need to prompt for max/min/bins + Dim dbl_max As Double, dbl_min As Double, int_bins As Long + + dbl_min = Application.InputBox("Minimum value.", "Min", _ + WorksheetFunction.Min(rngSelection), Type:=1) + dbl_max = Application.InputBox("Maximum value.", "Max", _ + WorksheetFunction.Max(rngSelection), Type:=1) + int_bins = Application.InputBox("Number of groups.", "Bins", _ + WorksheetFunction.RoundDown(Math.Sqr(WorksheetFunction.count(rngSelection)), _ + 0), Type:=1) + + On Error GoTo 0 + + 'determine default value + Dim strDefault As Variant + strDefault = Application.InputBox("Enter the default value", "Default", _ + "#N/A") + + 'detect cancel and exit + If StrPtr(strDefault) = 0 Then + Exit Sub + End If + + ''TODO prompt for output location + + rngValues.EntireColumn.Offset(, 1).Resize(, int_bins + 2).Insert + 'head the columns with the values + + ''TODO add a For loop to go through the bins + + Dim int_binNo As Long + For int_binNo = 0 To int_bins + rngValues.Cells(1).Offset(, int_binNo + 1) = dbl_min + (dbl_max - _ + dbl_min) * int_binNo / int_bins + Next + + 'add the last item + rngValues.Cells(1).Offset(, int_bins + 2).FormulaR1C1 = "=RC[-1]" + + 'FIRST =IF($D2 <=V$1,$U2,#N/A) + '=IF(RC4 <=R1C,RC21,#N/A) + + 'MID =IF(AND($D2 <=W$1, $D2>V$1),$U2,#N/A) '''W current, then left + '=IF(AND(RC4 <=R1C, RC4>R1C[-1]),RC21,#N/A) + + 'LAST =IF($D2>AA$1,$U2,#N/A) + '=IF(RC4>R1C[-1],RC21,#N/A) + + ''TODO add number format to display header correctly (helps with charts) + + 'put the formula in for each column + '=IF(RC13=R1C,RC16,#N/A) + Dim strFormula As Variant + strFormula = "=IF(AND(RC" & rngSelection.Column & " <=R" & _ + rngValues.Cells(1).Row & "C," & "RC" & rngSelection.Column & ">R" & _ + rngValues.Cells(1).Row & "C[-1]" & ")" & ",RC" & rngValues.Column & "," & _ + strDefault & ")" + + Dim str_FirstFormula As Variant + str_FirstFormula = "=IF(AND(RC" & rngSelection.Column & " <=R" & _ + rngValues.Cells(1).Row & "C)" & ",RC" & rngValues.Column & "," & strDefault _ + & ")" + + Dim str_LastFormula As Variant + str_LastFormula = "=IF(AND(RC" & rngSelection.Column & " >R" & _ + rngValues.Cells(1).Row & "C)" & ",RC" & rngValues.Column & "," & strDefault _ + & ")" + + Dim rngFormula As Range + Set rngFormula = rngValues.Offset(1, 1).Resize(rngValues.Rows.count - 1, _ + int_bins + 2) + rngFormula.FormulaR1C1 = strFormula + + 'override with first/last + rngFormula.Columns(1).FormulaR1C1 = str_FirstFormula + rngFormula.Columns(rngFormula.Columns.count).FormulaR1C1 = str_LastFormula + + rngFormula.EntireColumn.AutoFit + + 'set the number formats + rngFormula.Offset(-1).Rows(1).Resize(1, int_bins + 1).NumberFormat = _ + "<= General" + rngFormula.Offset(-1).Rows(1).Offset(, int_bins + 1).NumberFormat = _ + "> General" + + Exit Sub + +ErrorNoSelection: + 'TODO: consider removing this prompt + MsgBox "No selection made. Exiting.", , "No selection" + +End Sub + + + + +'--------------------------------------------------------------------------------------- +' Procedure : Sht_DeleteHiddenRows +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Deletes the hidden rows in a sheet. Good for a "permanent" filter +'--------------------------------------------------------------------------------------- +' +'Changed sub name to avoid reserved object name +Sub Sht_DeleteHiddenRows() + + Application.ScreenUpdating = False + Dim Row As Range + Dim i As Long + For i = ActiveSheet.UsedRange.Rows.count To 1 Step -1 + + + Set Row = ActiveSheet.Rows(i) + + If Row.Hidden Then + Row.Delete + End If + Next i + + Application.ScreenUpdating = True + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : UnhideAllRowsAndColumns +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Unhides everything in a Worksheet +' Flag : new-feature +'--------------------------------------------------------------------------------------- +' +Sub UnhideAllRowsAndColumns() + + ActiveSheet.Cells.EntireRow.Hidden = False + ActiveSheet.Cells.EntireColumn.Hidden = False + +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : UpdateScrollbars +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Cheap trick that forces Excel to update the scroll bars after a large deletion +'--------------------------------------------------------------------------------------- +' +Sub UpdateScrollbars() + + Dim rng As Variant + rng = ActiveSheet.UsedRange.Address + +End Sub + diff --git a/src/code/bUTL.cls b/src/code/bUTL.cls index e0ffa54..c78ed0c 100644 --- a/src/code/bUTL.cls +++ b/src/code/bUTL.cls @@ -1,17 +1,17 @@ -VERSION 1.0 CLASS -BEGIN - MultiUse = -1 'True -END -Attribute VB_Name = "bUTL" -Attribute VB_GlobalNameSpace = False -Attribute VB_Creatable = False -Attribute VB_PredeclaredId = False -Attribute VB_Exposed = False -'--------------------------------------------------------------------------------------- -' Module : bUTL -' Author : @byronwall -' Date : 2015 08 12 -' Purpose : Code has been removed with the text boxes. Module might be deleted next. -'--------------------------------------------------------------------------------------- - -Option Explicit +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "bUTL" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'--------------------------------------------------------------------------------------- +' Module : bUTL +' Author : @byronwall +' Date : 2015 08 12 +' Purpose : Code has been removed with the text boxes. Module might be deleted next. +'--------------------------------------------------------------------------------------- + +Option Explicit diff --git a/src/code/bUTLChartSeries.cls b/src/code/bUTLChartSeries.cls index 38c95ff..f78a0dc 100644 --- a/src/code/bUTLChartSeries.cls +++ b/src/code/bUTLChartSeries.cls @@ -1,173 +1,176 @@ -VERSION 1.0 CLASS -BEGIN - MultiUse = -1 'True -END -Attribute VB_Name = "bUTLChartSeries" -Attribute VB_GlobalNameSpace = False -Attribute VB_Creatable = False -Attribute VB_PredeclaredId = False -Attribute VB_Exposed = False -'--------------------------------------------------------------------------------------- -' Module : bUTLChartSeries -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Helper class to read series from charts and manipulate easier -'--------------------------------------------------------------------------------------- - -Public Values As Range -Public XValues As Range -Public name As Range -Public SeriesNumber As Integer -Public ChartType As XlChartType -Public series As series - -'--------------------------------------------------------------------------------------- -' Procedure : AddSeriesToChart -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Adds the represented series to a chart -'--------------------------------------------------------------------------------------- -' -Function AddSeriesToChart(cht As Chart) As series - - Dim ser As series - Set ser = cht.SeriesCollection.NewSeries - - ser.Formula = Me.SeriesFormula - - If Me.ChartType <> 0 Then - ser.ChartType = Me.ChartType - End If - - - Set AddSeriesToChart = ser - -End Function - -'--------------------------------------------------------------------------------------- -' Procedure : FullAddress -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Gets the full address for a range -'--------------------------------------------------------------------------------------- -' -Private Function FullAddress(rng As Range) As Variant - - If rng Is Nothing Then - FullAddress = "" - Else - FullAddress = "'" & rng.Parent.name & "'!" & rng.Address(External:=False) - End If - -End Function - -'--------------------------------------------------------------------------------------- -' Procedure : SeriesFormula -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Returns a SERIES formula for the represented series -'--------------------------------------------------------------------------------------- -' -Public Property Get SeriesFormula() As String - - SeriesFormula = "=SERIES(" & FullAddress(Me.name) & "," & FullAddress(Me.XValues) & "," & FullAddress(Me.Values) & "," & Me.SeriesNumber & ")" - -End Property - -'--------------------------------------------------------------------------------------- -' Procedure : Class_Initialize -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Ensures the SERIES starts out first -'--------------------------------------------------------------------------------------- -' -Private Sub Class_Initialize() - Me.SeriesNumber = 1 -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : UpdateFromChartSeries -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Reads the series info from a Series and stores it in the class -'--------------------------------------------------------------------------------------- -' -Sub UpdateFromChartSeries(ser As series) - 'this will work for the simple case where all items are references - - Set series = ser - - Dim form As Variant - - ' "=SERIES("Y",Sheet1!$C$8:$C$13,Sheet1!$D$8:$D$13,1)" - - 'pull in teh formula - form = ser.Formula - - 'uppercase to remove match errors - form = UCase(form) - - 'remove the front of the formula - form = Replace(form, "=SERIES(", "") - - 'Debug.Print form & vbCrLf - ' "Y",SHEET1!$C$8:$C$13,SHEET1!$D$8:$D$13,1) - - 'find the first comma - Dim comma - comma = InStr(form, ",") - - 'Debug.Print comma - - If comma > 1 Then - Set Me.name = Range(left(form, comma - 1)) - End If - - 'pull out the title from that - form = Mid(form, comma + 1) - - 'Debug.Print vbCrLf & form - ' SHEET1!$C$8:$C$13,SHEET1!$D$8:$D$13,1) - - 'check the xvalues for multiple references (include paren) - - comma = InStr(form, ",") - - If comma > 1 Then - Set Me.XValues = Range(left(form, comma - 1)) - End If - form = Mid(form, comma + 1) - 'Debug.Print vbCrLf & form - - comma = InStr(form, ",") - Set Me.Values = Range(left(form, comma - 1)) - form = Mid(form, comma + 1) - 'Debug.Print vbCrLf & form - - comma = InStr(form, ")") - Me.SeriesNumber = left(form, comma - 1) - - Me.ChartType = ser.ChartType - - 'if parenth then bring in until ), // otherwise until next comma - - 'do teh same thing for y values - - 'pull in the series number - - -End Sub - -'--------------------------------------------------------------------------------------- -' Procedure : UpdateSeriesWithNewValues -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Forces the stored Series to take on any changed values in the class -'--------------------------------------------------------------------------------------- -' -Sub UpdateSeriesWithNewValues() - - Me.series.Formula = Me.SeriesFormula - -End Sub - +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "bUTLChartSeries" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : bUTLChartSeries +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Helper class to read series from charts and manipulate easier +'--------------------------------------------------------------------------------------- + +Public Values As Range +Public XValues As Range +Public name As Range +Public SeriesNumber As Long +Public ChartType As XlChartType +Public series As series + +Private str_name As String + +'--------------------------------------------------------------------------------------- +' Procedure : AddSeriesToChart +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Adds the represented series to a chart +'--------------------------------------------------------------------------------------- +' +Function AddSeriesToChart(cht As Chart) As series + + Dim ser As series + Set ser = cht.SeriesCollection.NewSeries + + ser.Formula = Me.SeriesFormula + + If Me.ChartType <> 0 Then + ser.ChartType = Me.ChartType + End If + + + Set AddSeriesToChart = ser + +End Function + +'--------------------------------------------------------------------------------------- +' Procedure : FullAddress +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Gets the full address for a range +'--------------------------------------------------------------------------------------- +' +Private Function FullAddress(rng As Range) As Variant + + If rng Is Nothing Then + FullAddress = "" + Else + FullAddress = "'" & rng.Parent.name & "'!" & rng.Address(External:=False) + End If + +End Function + +'--------------------------------------------------------------------------------------- +' Procedure : SeriesFormula +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Returns a SERIES formula for the represented series +'--------------------------------------------------------------------------------------- +' +Public Property Get SeriesFormula() As String + + '2015 11 09 add a trap here to allow for a string only name + If str_name <> "" Then + SeriesFormula = "=SERIES(" & str_name & "," & _ + FullAddress(Me.XValues) & "," & FullAddress(Me.Values) & "," & _ + Me.SeriesNumber & ")" + Else + + SeriesFormula = "=SERIES(" & FullAddress(Me.name) & "," & _ + FullAddress(Me.XValues) & "," & FullAddress(Me.Values) & "," & _ + Me.SeriesNumber & ")" + End If + +End Property + +'--------------------------------------------------------------------------------------- +' Procedure : Class_Initialize +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Ensures the SERIES starts out first +'--------------------------------------------------------------------------------------- +' +Private Sub Class_Initialize() + Me.SeriesNumber = 1 +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : UpdateFromChartSeries +' Author : @byronwall +' Date : 2015 11 09 +' Purpose : Reads the series info from a Series and stores it in the class +'--------------------------------------------------------------------------------------- +' +Sub UpdateFromChartSeries(ser As series) +'this will work for the simple case where all items are references + + Set series = ser + + Dim form As Variant + + '=SERIES("Y",Sheet1!$C$8:$C$13,Sheet1!$D$8:$D$13,1) + + 'pull in teh formula + form = ser.Formula + + 'uppercase to remove match errors + form = UCase(form) + + 'remove the front of the formula + form = Replace(form, "=SERIES(", "") + + 'find the first comma + Dim comma + comma = InStr(form, ",") + + If comma > 1 Then + + 'need to catch an error here if a text name is used instead of a valid range + On Error Resume Next + Set Me.name = Range(left(form, comma - 1)) + + If Err <> 0 Then + str_name = left(form, comma - 1) + End If + + On Error GoTo 0 + End If + + 'pull out the title from that + form = Mid(form, comma + 1) + + comma = InStr(form, ",") + + If comma > 1 Then + Set Me.XValues = Range(left(form, comma - 1)) + End If + form = Mid(form, comma + 1) + + comma = InStr(form, ",") + Set Me.Values = Range(left(form, comma - 1)) + form = Mid(form, comma + 1) + + comma = InStr(form, ")") + Me.SeriesNumber = left(form, comma - 1) + + Me.ChartType = ser.ChartType +End Sub + +'--------------------------------------------------------------------------------------- +' Procedure : UpdateSeriesWithNewValues +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Forces the stored Series to take on any changed values in the class +'--------------------------------------------------------------------------------------- +' +Sub UpdateSeriesWithNewValues() + + Me.series.Formula = Me.SeriesFormula + +End Sub + diff --git a/src/code/form_chtGrid.frm b/src/code/form_chtGrid.frm index 5f05be6..9bab78d 100644 --- a/src/code/form_chtGrid.frm +++ b/src/code/form_chtGrid.frm @@ -1,43 +1,45 @@ -VERSION 5.00 -Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} form_chtGrid - Caption = "Chart Grid" - ClientHeight = 3825 - ClientLeft = 45 - ClientTop = 435 - ClientWidth = 1755 - OleObjectBlob = "form_chtGrid.frx":0000 - StartUpPosition = 1 'CenterOwner -End -Attribute VB_Name = "form_chtGrid" -Attribute VB_GlobalNameSpace = False -Attribute VB_Creatable = False -Attribute VB_PredeclaredId = True -Attribute VB_Exposed = False - - - - -'--------------------------------------------------------------------------------------- -' Module : form_chtGrid -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Simple interface to create a grid of charts -'--------------------------------------------------------------------------------------- - -Option Explicit - -Private Sub btn_grid_Click() - - Chart_GridOfCharts _ - txt_cols, _ - CDbl(txt_width), _ - CDbl(txt_height), _ - CDbl(txt_vOff), _ - CDbl(txt_hOff), _ - chk_down, _ - chk_zoom - - Hide - -End Sub - +VERSION 5.00 +Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} form_chtGrid + Caption = "Chart Grid" + ClientHeight = 3825 + ClientLeft = 45 + ClientTop = 435 + ClientWidth = 1755 + OleObjectBlob = "form_chtGrid.frx":0000 + StartUpPosition = 1 'CenterOwner +End +Attribute VB_Name = "form_chtGrid" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = True +Attribute VB_Exposed = False + + + + + + +'--------------------------------------------------------------------------------------- +' Module : form_chtGrid +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Simple interface to create a grid of charts +'--------------------------------------------------------------------------------------- + +Option Explicit + +Private Sub btn_grid_Click() + + Chart_GridOfCharts _ + txt_cols, _ + CDbl(txt_width), _ + CDbl(txt_height), _ + CDbl(txt_vOff), _ + CDbl(txt_hOff), _ + chk_down, _ + chk_zoom + + Hide + +End Sub + diff --git a/src/code/form_chtGrid.frx b/src/code/form_chtGrid.frx index 1a56f94915c7a9e3ce6c39b908094d09b96cd965..18d894f5193e08cd8eab7cdb1cd9457a525b3a8a 100644 GIT binary patch delta 20 ccmbQCFhgNO3k&;$cakX!sxNNtX6fYy09(ok!2kdN delta 20 ccmbQCFhgNO3k&;(ujLP~UcIono28c<0A>IR0RR91 diff --git a/src/code/form_chtSeries.frm b/src/code/form_chtSeries.frm index 83ec0a7..c6ad8b9 100644 --- a/src/code/form_chtSeries.frm +++ b/src/code/form_chtSeries.frm @@ -1,164 +1,163 @@ -VERSION 5.00 -Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} form_chtSeries - Caption = "Correct Series" - ClientHeight = 3345 - ClientLeft = 45 - ClientTop = 435 - ClientWidth = 10035 - OleObjectBlob = "form_chtSeries.frx":0000 - ShowModal = 0 'False - StartUpPosition = 1 'CenterOwner -End -Attribute VB_Name = "form_chtSeries" -Attribute VB_GlobalNameSpace = False -Attribute VB_Creatable = False -Attribute VB_PredeclaredId = True -Attribute VB_Exposed = False - - - - -'--------------------------------------------------------------------------------------- -' Module : form_chtSeries -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : Code is under development to better change multiple series at once -'--------------------------------------------------------------------------------------- - - -Dim ser_coll As New Dictionary -Dim dirty As Boolean - -Private Sub btn_setXRange_Click() - -'get the selected series - - Dim i As Integer - For i = 0 To list_series.ListCount - 1 - - If list_series.Selected(i) Then - - Dim b_ser As bUTLChartSeries - Set b_ser = ser_coll(i & list_series.List(i, 0)) - - Set b_ser.XValues = Range(txt_xrange) - - b_ser.series.Formula = b_ser.SeriesFormula - - End If - - Next i - - UpdateSeries - -End Sub - -Private Sub btn_xrangedown_Click() - - txt_xrange = RangeEnd(Range(txt_xrange), xlDown).Address - -End Sub - -Private Sub btn_ydown_Click() - txt_yrange = RangeEnd(Range(txt_yrange), xlDown).Address -End Sub - -Private Sub btn_yrange_Click() - Dim i As Integer - For i = 0 To list_series.ListCount - 1 - - If list_series.Selected(i) Then - - Dim b_ser As bUTLChartSeries - Set b_ser = ser_coll(i & list_series.List(i, 0)) - - Set b_ser.Values = Range(txt_yrange) - - b_ser.series.Formula = b_ser.SeriesFormula - - End If - - Next i - - UpdateSeries -End Sub - -Private Sub txt_xrange_Enter() - Hide - - Dim rng_data As Range - Set rng_data = Application.InputBox("Select start", Type:=8) - - Show - - txt_xrange = rng_data.Address(External:=True) -End Sub - -Private Sub txt_yrange_Enter() - Hide - - Dim rng_data As Range - Set rng_data = Application.InputBox("Select start", Type:=8) - - Show - - txt_yrange = rng_data.Address(External:=True) -End Sub - -Private Sub UpdateSeries() - -'clean up the mess - ser_coll.RemoveAll - - Dim i As Integer - For i = list_series.ListCount - 1 To 0 Step -1 - list_series.RemoveItem (i) - Next i - - Dim cht_obj As ChartObject - - Dim ser As series - - For Each cht_obj In Chart_GetObjectsFromObject(Selection) - For Each ser In cht_obj.Chart.SeriesCollection - - Dim b_ser As bUTLChartSeries - Set b_ser = New bUTLChartSeries - - b_ser.UpdateFromChartSeries ser - - Dim ser_name As Variant - ser_name = IIf(Not b_ser.name Is Nothing, b_ser.name, "") - - list_series.AddItem - list_series.List(list_series.ListCount - 1, 0) = ser_name - list_series.List(list_series.ListCount - 1, 1) = b_ser.XValues.Address - list_series.List(list_series.ListCount - 1, 2) = b_ser.Values.Address - - list_series.Selected(list_series.ListCount - 1) = True - - ser_coll.Add list_series.ListCount - 1 & ser_name, b_ser - - Next ser - Next cht_obj -End Sub - -Private Sub UserForm_Activate() -'load up the series from selection - If dirty Then - UpdateSeries - dirty = False - End If - - If list_series.ListCount = 0 Then - Unload Me - Exit Sub - End If -End Sub - -Private Sub UserForm_Initialize() - - dirty = True - -End Sub - +VERSION 5.00 +Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} form_chtSeries + Caption = "Correct Series" + ClientHeight = 3345 + ClientLeft = 45 + ClientTop = 435 + ClientWidth = 10035 + OleObjectBlob = "form_chtSeries.frx":0000 + ShowModal = 0 'False + StartUpPosition = 1 'CenterOwner +End +Attribute VB_Name = "form_chtSeries" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = True +Attribute VB_Exposed = False + +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : form_chtSeries +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : Code is under development to better change multiple series at once +'--------------------------------------------------------------------------------------- + + +Dim ser_coll As New Dictionary +Dim dirty As Boolean + +Private Sub btn_setXRange_Click() + +'get the selected series + + Dim i As Integer + For i = 0 To list_series.ListCount - 1 + + If list_series.Selected(i) Then + + Dim b_ser As bUTLChartSeries + Set b_ser = ser_coll(i & list_series.List(i, 0)) + + Set b_ser.XValues = Range(txt_xrange) + + b_ser.series.Formula = b_ser.SeriesFormula + + End If + + Next i + + UpdateSeries + +End Sub + +Private Sub btn_xrangedown_Click() + + txt_xrange = RangeEnd(Range(txt_xrange), xlDown).Address(, , , True) + +End Sub + +Private Sub btn_ydown_Click() + txt_yrange = RangeEnd(Range(txt_yrange), xlDown).Address(, , , True) +End Sub + +Private Sub btn_yrange_Click() + Dim i As Integer + For i = 0 To list_series.ListCount - 1 + + If list_series.Selected(i) Then + + Dim b_ser As bUTLChartSeries + Set b_ser = ser_coll(i & list_series.List(i, 0)) + + Set b_ser.Values = Range(txt_yrange) + + b_ser.series.Formula = b_ser.SeriesFormula + + End If + + Next i + + UpdateSeries +End Sub + +Private Sub txt_xrange_Enter() + Hide + + Dim rng_data As Range + Set rng_data = Application.InputBox("Select start", Type:=8) + + Show + + txt_xrange = rng_data.Address(External:=True) +End Sub + +Private Sub txt_yrange_Enter() + Hide + + Dim rng_data As Range + Set rng_data = Application.InputBox("Select start", Type:=8) + + Show + + txt_yrange = rng_data.Address(External:=True) +End Sub + +Private Sub UpdateSeries() + +'clean up the mess + ser_coll.RemoveAll + + Dim i As Integer + For i = list_series.ListCount - 1 To 0 Step -1 + list_series.RemoveItem (i) + Next i + + Dim cht_obj As ChartObject + + Dim ser As series + + For Each cht_obj In Chart_GetObjectsFromObject(Selection) + For Each ser In cht_obj.Chart.SeriesCollection + + Dim b_ser As bUTLChartSeries + Set b_ser = New bUTLChartSeries + + b_ser.UpdateFromChartSeries ser + + Dim ser_name As Variant + ser_name = IIf(Not b_ser.name Is Nothing, b_ser.name, "") + + list_series.AddItem + list_series.List(list_series.ListCount - 1, 0) = ser_name + list_series.List(list_series.ListCount - 1, 1) = b_ser.XValues.Address + list_series.List(list_series.ListCount - 1, 2) = b_ser.Values.Address + + list_series.Selected(list_series.ListCount - 1) = True + + ser_coll.Add list_series.ListCount - 1 & ser_name, b_ser + + Next ser + Next cht_obj +End Sub + +Private Sub UserForm_Activate() +'load up the series from selection + If dirty Then + UpdateSeries + dirty = False + End If + + If list_series.ListCount = 0 Then + Unload Me + Exit Sub + End If +End Sub + +Private Sub UserForm_Initialize() + + dirty = True + +End Sub + diff --git a/src/code/form_chtSeries.frx b/src/code/form_chtSeries.frx index 5fef072bd9c11136bcee0a5eaa45b614d443ad2c..5513529d240f7b96f16932e8114e5a27a15d5b59 100644 GIT binary patch delta 20 bcmbOsF+*ZQ3k!RL1Aoeb>WiDZS$f$4PDlrp delta 20 ccmbOsF+*ZQ3k&;#*wTkruU^>P&C<&b09}9yLjV8( diff --git a/src/code/form_newCommands.frm b/src/code/form_newCommands.frm index 60b608b..466b182 100644 --- a/src/code/form_newCommands.frm +++ b/src/code/form_newCommands.frm @@ -1,85 +1,105 @@ -VERSION 5.00 -Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} form_newCommands - Caption = "Additional Features" - ClientHeight = 5970 - ClientLeft = 45 - ClientTop = 435 - ClientWidth = 6585 - OleObjectBlob = "form_newCommands.frx":0000 - StartUpPosition = 1 'CenterOwner -End -Attribute VB_Name = "form_newCommands" -Attribute VB_GlobalNameSpace = False -Attribute VB_Creatable = False -Attribute VB_PredeclaredId = True -Attribute VB_Exposed = False - - - - - -'--------------------------------------------------------------------------------------- -' Module : form_newCommands -' Author : @byronwall -' Date : 2015 07 24 -' Purpose : This form is just buttons to easier get to new code -'--------------------------------------------------------------------------------------- -Option Explicit - -Private Sub CommandButton1_Click() - - Chart_CreateDataLabels - -End Sub - -Private Sub CommandButton13_Click() - ChartApplyToAll -End Sub - -Private Sub CommandButton15_Click() - Hide - - Dim frm As New form_chtSeries - frm.Show -End Sub - -Private Sub CommandButton16_Click() - Selection_ColorWithHex -End Sub - -Private Sub CommandButton17_Click() - Chart_TrendlinesToAverage -End Sub - -Private Sub CommandButton18_Click() - ChartPropMove -End Sub - -Private Sub CommandButton19_Click() - Chart_RemoveTrendlines -End Sub - -Private Sub CommandButton20_Click() - PivotSetAllFields -End Sub - -Private Sub CommandButton21_Click() - ConvertSelectionToCsv -End Sub - -Private Sub CommandButton22_Click() - ColorInputs -End Sub - -Private Sub CommandButton23_Click() - UnhideAllRowsAndColumns -End Sub - -Private Sub CommandButton25_Click() - ExportFilesFromFolder -End Sub - -Private Sub CommandButton26_Click() - GenerateRandomData -End Sub - +VERSION 5.00 +Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} form_newCommands + Caption = "Additional Features" + ClientHeight = 8460 + ClientLeft = 45 + ClientTop = 435 + ClientWidth = 6585 + OleObjectBlob = "form_newCommands.frx":0000 + ShowModal = 0 'False + StartUpPosition = 1 'CenterOwner +End +Attribute VB_Name = "form_newCommands" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = True +Attribute VB_Exposed = False + + +Option Explicit + +'--------------------------------------------------------------------------------------- +' Module : form_newCommands +' Author : @byronwall +' Date : 2015 07 24 +' Purpose : This form is just buttons to easier get to new code +'--------------------------------------------------------------------------------------- + +Private Sub CommandButton1_Click() + Chart_CreateDataLabels +End Sub + +Private Sub CommandButton13_Click() + ChartApplyToAll +End Sub + +Private Sub CommandButton15_Click() + Hide + + Dim frm As New form_chtSeries + frm.Show +End Sub + +Private Sub CommandButton16_Click() + Selection_ColorWithHex +End Sub + +Private Sub CommandButton17_Click() + Chart_TrendlinesToAverage +End Sub + +Private Sub CommandButton18_Click() + ChartPropMove +End Sub + +Private Sub CommandButton19_Click() + Chart_RemoveTrendlines +End Sub + +Private Sub CommandButton20_Click() + PivotSetAllFields +End Sub + +Private Sub CommandButton21_Click() + ConvertSelectionToCsv +End Sub + +Private Sub CommandButton22_Click() + ColorInputs +End Sub + +Private Sub CommandButton23_Click() + UnhideAllRowsAndColumns +End Sub + +Private Sub CommandButton25_Click() + ExportFilesFromFolder +End Sub + +Private Sub CommandButton26_Click() + GenerateRandomData +End Sub + +Private Sub CommandButton27_Click() + SeriesSplitIntoBins +End Sub + +Private Sub CommandButton28_Click() + Chart_SortSeriesByName +End Sub + +Private Sub CommandButton29_Click() + CreatePdfOfEachXlsxFileInFolder +End Sub + +Private Sub CommandButton30_Click() + ApplyFormattingToEachColumn +End Sub + +Private Sub CommandButton31_Click() + ComputeDistanceMatrix +End Sub + +Private Sub CommandButton32_Click() + Chart_CreateChartWithSeriesForEachColumn +End Sub diff --git a/src/code/form_newCommands.frx b/src/code/form_newCommands.frx index 4e94b7a41f9c889d19969b83f005a14c001a6cc8..fac685a046162704ac021a009ad16f4803b5ca05 100644 GIT binary patch delta 1006 zcmZWnO-vI(6rL@G^pDb7MI(`n2qM~+wg?tAO4^1*1BQecdn4=alup>rY<8z28UqKf zUK|e`@uCMkXnOI8M{nxMi!m`V#=B?go2i(_Hu<`<-}~Ns-|^ z^JF4=R(_7WlKIG#d=b5E3~1&c7HOqpPmDf%*U`lH!JqP1IJy8gv$IM(O8iyVq2dM; zyhNFgGkB9a4)zs^EzSgGP2TjeZ3+`pP~mltH(g&9D-`u$BWMSmhH~Y)>4~*f?uky} z($+1rL7<}PV+JQ%VE52e<(f+Hh$+a5UCU;(AZwT##K$IHsVrh%!{i?Ews4I)1T2X< zu5rgE9`=btEI~QjP#$u6kTPdBRon{k%G_x-nCfcVK1|?nkFs?P#))aw6`{hMVJdvg zH<@x&S^77~tn$ShZc~s7^@YhSvTO>E-iM0%W-M(QY}$Xr;9JF!j7JOdcQ}#jHqlY5 zsVsFnok@Is@7RTDz>ab-AwR@pt*>!6u$@LfV5x?{28J)}0^%r%z*56xcBv@@XN61X zC1~!b4FltcdiVyY3K;6q7qxy_ZC{su4BiAF(XHRs4d&70@!ke!K&%WUyAzatCa3lP sHy8)4131#7&uc?vwfbF#)XI;l2C1Hef^JZrkpKyV4BP;dKv&4_d1pU diff --git a/src/package/[Content_Types].xml b/src/package/[Content_Types].xml index e360a9a..906f7da 100644 --- a/src/package/[Content_Types].xml +++ b/src/package/[Content_Types].xml @@ -1,2 +1,2 @@ - + \ No newline at end of file diff --git a/src/package/customUI/_rels/customUI.xml.rels b/src/package/customUI/_rels/customUI.xml.rels index 657e22e..d0382e2 100644 --- a/src/package/customUI/_rels/customUI.xml.rels +++ b/src/package/customUI/_rels/customUI.xml.rels @@ -1,2 +1,2 @@ - + \ No newline at end of file diff --git a/src/package/customUI/customUI.xml b/src/package/customUI/customUI.xml index e4d38e6..d35634d 100644 --- a/src/package/customUI/customUI.xml +++ b/src/package/customUI/customUI.xml @@ -1,365 +1,365 @@ - - - - - - -