Powerpoint layout algorithm for box-in-box view - vba
I am trying to write some code in VBA and PowerPoint to automatically create a poster (basically a box in box view) however, its proving to be a little more challenging than my skills can cope.
looking to get some guidance on how best to structure the code (and patterns)
at the moment:
Im reading the data (see table) from csv, and placing them into 4 in memory arrays (cat, sub-cat, column, intersection) - this is probably bad - but dont know of anything better.
my code tries to process the spacing logic and adjusts the x-and-y pos of each intersection block (grows the category and sub-category blocks when needed) within the arrays, then finally draws/places the blocks on the powerpoint canvas.
You will see in the graphic "initial selected block" at the moment thats the context from which i run -- the idea is to use that block size as the template for the dimensions
Im not worried about the placement, its more the algorithm to determine the spacing (or block size of each of the components
below is a sample of the data that I read:
and once the logic is generated, this is how the "poster" would look
Im happy to share code, its just getting a bit big now (and I feel im missing a trick in terms of simplicity)
code sample included below:
Const defaultObjectWidth = 95
Const defaultObjectHeight = 50
Const defaultCategoryWidth = 90
Const defaultSubCatWidth = 90
Const defaultBuffer = 3
Const blocksPerCol = 4
Const myYAxisWidth = 100
Sub insertShape()
On Error GoTo Err_Handler
Dim SlideHeight As Long, SlideWidth As Long
Dim ContainerHeight As Long, ContainerWidth As Long
Dim ObjectHeight As Long, ObjectWidth As Long
Dim x As Long, y As Long
Dim shp As Object
Dim colWidth As Integer
Dim blockSize As Integer
Dim IntersectCalcComplete As Boolean
IntersectCalcComplete = False
' create the Arrays for storing the data
Dim CategorysArr() As String
Dim SubCatsArr() As String
Dim YAxissArr() As String
Dim IntersectsArr() As String
Dim tmpIntersects() As String
Call LoadSubCats(SubCatsArr)
Call LoadCategorys(CategorysArr)
Call LoadYAxiss(YAxissArr)
Call LoadIntersects(IntersectsArr)
ReDim tmpIntersects(0 To 2, 0 To 0)
If ActiveWindow.Selection.Type = ppSelectionNone Then
MsgBox "Please select object", vbExclamation, "Make Selection"
Else
Set shp = ActiveWindow.Selection.ShapeRange(1)
SlideHeight = ActivePresentation.PageSetup.SlideHeight 'get slide vertical height
SlideWidth = ActivePresentation.PageSetup.SlideWidth 'get slide horizontal width
'Main Content Container width and height
ContainerHeight = shp.Height
ContainerWidth = shp.Width
' calculate the container column sizes.
'
colWidth = calcColumnSizes(shp.Width, CategorysArr, SubCatsArr, YAxissArr)
' calculate the blockwidth based on the density factor parameter
blockWidth = calcBlockWidth(colWidth, blocksPerCol)
' Iterate through the Categorys, SubCats and Intersects in order to calculate the spacing of the Intersects.
' first sort the matricies by a specific column.
' CategorysArr = QuickSortArray(CategorysArr, -1, -1, 4)
' Call multiSortArray(1, CategorysArr, arrFlds(CategorysArr), "2 Asc 5 Asc")
' -- ------------------------------------------------------------------------------------------------------------------------------------------------
Dim SubCatID As Integer
'Dim myYAxisWidth As Integer
Dim SubCatHeight, CategoryHeight, currentX, currentY, n, t As Integer
SubCatHeight = 0
CategoryHeight = 0
currentX = 0
currentY = 0
currentBlockUsed = 0
For i = LBound(SubCatsArr, 2) To UBound(SubCatsArr, 2) ' For each SubCat in the SubCats Array
t = t + 1
'Debug.Print (" processing SubCat: " & t & " called: " & SubCatsArr(1, i))
' Debug.Print (SubCatsArr(i, 1)) ' print the name
SubCatID = SubCatsArr(0, i)
If Not IsNull(SubCatID) Then
' with each SubCat - find all related Intersects and fit them into the canvas
' 1: Filter the Intersects by SubCat
Call getIntersectBySubCat(SubCatID, IntersectsArr, tmpIntersects)
'printArray (tmpIntersects)
' 2: re-structure the Intersects position to fit into the YAxiss allocated
' myYAxisWidth = 200 ' -- get YAxis width TODO: get the real width
' -- we already know what the blockWidth is (above)
SubCatHeight = defaultObjectHeight + defaultBuffer ' set the SubCatheight on the first pass.
For j = LBound(tmpIntersects, 2) To UBound(tmpIntersects, 2) ' Loop through tmp Intersects to add placement
If LBound(tmpIntersects, 2) = -1 Then ' SubCat has no Intersects
' Debug.Print ("SubCat ID:" & SubCatID & " has no Intersects")
Else
If myYAxisWidth < (currentX + blockWidth + (defaultBuffer * 2)) Then ' If there is not enough remaining space in YAxis
currentY = currentY + defaultBuffer + defaultObjectHeight
currentX = 0
SubCatHeight = SubCatHeight + defaultObjectHeight + defaultBuffer
End If
n = findArrStr(IntersectsArr, 0, str(tmpIntersects(0, j)))
IntersectsArr(6, n) = str(currentX + defaultBuffer) ' X CoOrdinate
IntersectsArr(7, n) = str(currentY + defaultBuffer) ' Y CoOrdinate
currentX = currentX + defaultBuffer + defaultObjectWidth
End If
Next j 'filtered Intersect
Else
' Debug.Print ("empty record in Array Detected")
End If
Call setSubCatHeight(SubCatHeight, SubCatID, SubCatsArr, CategorysArr)
SubCatHeight = 0
Next i 'SubCat
' -- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
' -- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
' ADDING OF THE SHAPES TO THE VIEW
Call addCategorys(CategorysArr, shp)
Call addSubCats(SubCatsArr, shp)
Call addIntersects(IntersectsArr, shp)
' x = (SlideWidth - ObjectWidth) / 2 'calculate horizontal position
' y = (SlideHeight - ObjectHeight) / 2 'calculate vertical position
' shp.Left = x 'move object horizontal
' shp.Top = y 'move object vertical
End If
Exit_Label:
On Error Resume Next
Set shp = Nothing
Exit Sub
Err_Handler:
MsgBox Err.Description, vbCritical
Resume Exit_Label
End Sub
Function getIntersectBySubCat(SubCatID As Integer, ByRef IntersectsArr As Variant, ByRef tmpIntersects As Variant)
' Filters the Intersects to the specific SubCat
Dim i, j As Integer
j = -1
Erase tmpIntersects
ReDim Preserve tmpIntersects(0 To 2, -1 To 0)
For i = LBound(IntersectsArr, 2) To UBound(IntersectsArr, 2)
If IntersectsArr(2, i) = SubCatID Then 'if the Intersect relates to the current SubCat
j = j + 1
ReDim Preserve tmpIntersects(0 To 2, 0 To j) ' Redimension:
tmpIntersects(0, j) = IntersectsArr(0, i) ' Intersect Code
tmpIntersects(1, j) = IntersectsArr(3, i) ' get the YAxis
tmpIntersects(2, j) = IntersectsArr(9, i) ' get the Order
'Debug.Print ("RESULT ARRAY" & tmpIntersects(i + 1, 0) & "_" & tmpIntersects(i + 1, 1) & "_" & tmpIntersects(i + 1, 2) & "_")
End If
Next i 'Intersect
' sort the array by the Intersect order column (2)
' Call QuickSortArray(tmpIntersects, , , 2)
End Function
Function calcColumnSizes(lenContainer As Integer, ByRef CategorysArr As Variant, ByRef SubCatsArr As Variant, ByRef YAxissArr As Variant) As Integer
Dim lenCategory, lenSubCat, cntQtr As Integer
Dim lenWorkArea, columnSize As Integer
cntQtr = UBound(YAxissArr) + 1
lenWorkArea = lenContainer - defaultCategoryWidth - defaultSubCatWidth - (2 * defaultBuffer)
lenWorkArea = lenWorkArea - ((2 * defaultBuffer) + (cntQtr * defaultBuffer))
columnSize = lenWorkArea / cntQtr
' now propogate the columnSize into the YAxiss data
For i = LBound(YAxissArr, 1) To UBound(YAxissArr, 1)
YAxissArr(2, i) = str(Round(columnSize, 1)) ' Width
Next i
calcColumnSizes = columnSize
End Function
Function calcBlockWidth(columnSize As Integer, density As Integer) As Integer
calcBlockWidth = columnSize / density
End Function
Function setSubCatHeight(SubCatYPos, ID As Integer, ByRef SubCatsArr As Variant, ByRef CategorysArr As Variant)
Dim curSubCat As Integer
curSubCat = findArrStr(SubCatsArr, 0, str(ID))
If SubCatsArr(3, curSubCat) < SubCatYPos Then
SubCatsArr(3, curSubCat) = SubCatYPos
Call setCategoryHeight(SubCatYPos, CInt(SubCatsArr(2, curSubCat)), CategorysArr)
End If
End Function
Function setCategoryHeight(CategoryYPos, ID As Integer, ByRef CategorysArr As Variant)
Dim curCategory As Integer
curCategory = findArrStr(CategorysArr, 0, str(ID))
If CategorysArr(2, curCategory) < CategoryYPos Then
CategorysArr(2, curCategory) = CategoryYPos
End If
End Function
Function addCategorys(ByRef CategorysArr As Variant, masterShp As Shape)
Dim i, j As Integer
For i = LBound(CategorysArr, 2) To UBound(CategorysArr, 2)
Call addBlockContents(CInt(CategorysArr(2, i)), CInt(CategorysArr(3, i)), CStr(CategorysArr(1, i)), masterShp) ' X, Y, Name, Shape
Next i
End Function
Function addSubCats(ByRef SubCatsArr As Variant, masterShp As Shape)
Dim i, j As Integer
For i = LBound(SubCatsArr, 2) To UBound(SubCatsArr, 2)
Call addBlockContents(CInt(SubCatsArr(3, i)), CInt(SubCatsArr(4, i)), CStr(SubCatsArr(1, i)), masterShp)
Next i
End Function
Function addIntersects(ByRef IntersectsArr As Variant, masterShp As Shape)
Dim i, j As Integer
Dim myX, myY As Integer
myX = 0
myY = 0
For i = LBound(IntersectsArr, 2) To UBound(IntersectsArr, 2)
' adjust the placement of the shape relative to the masterShape
myX = masterShp.Left + CInt(IntersectsArr(6, i)) + defaultCategoryWidth + defaultSubCatWidth + (defaultBuffer * 2)
myY = masterShp.Top + CInt(IntersectsArr(7, i))
Call addBlockContents(myX, myY, CStr(IntersectsArr(1, i)), masterShp)
'For j = LBound(COAArray, 2) To UBound(COAArray, 2)
'Next j
Next i
End Function
Function addBlockContents(x, y, IntersectName As String, myShp As Shape)
Dim oSh As Shape
Set oSh = ActiveWindow.Selection.SlideRange.Shapes.AddShape(msoShapeRectangle, x, y, defaultObjectWidth, defaultObjectHeight)
With oSh
With .Fill
.Visible = msoTrue
.ForeColor.ObjectThemeColor = msoThemeColorText2
.ForeColor.TintAndShade = 0
.ForeColor.Brightness = 0.8000000119
.Transparency = 0
.Solid
End With 'fill
With .TextFrame
.MarginLeft = 0
.MarginRight = 0
.MarginTop = 0
.MarginBottom = 0
.AutoSize = False
With .TextRange
.Text = IntersectName
With .Font
.Name = "Ariel"
.Size = 6.4
.Bold = msoFalse
.Italic = msoFalse
.Underline = msoFalse
.Shadow = msoFalse
.Emboss = msoFalse
.BaselineOffset = 0
.AutoRotateNumbers = msoFalse
.Color.SchemeColor = ppForeground
End With ' Font
End With ' TextRange
End With ' TextFrame
End With ' oSh, the shape itself
'Set addBlockContents = oSh
End Function
any help will be awesome!
The easiest way to get started here is to use tables. It will help you with a lot of the layout issues.
As a first step, create a table manually. Insert a table and remove headings and other automatic coloring. Set the border line to maximum width and to match the background color before applying it to all cells.
Now you can merge cells to create larger boxes (like Cars in your example). Deleting cells isn't possible, but you can set the background color.
If this is good enough, then you have something to automate.
If not, then you can perhaps use this to find coordinates for boxes to create.
Related
Align shapes flush/stacked/touching
I'm trying to get a selection of shapes in order from right to left. I found a routine by John Wilson on vbaexpress on which I based my code. The sorting works perfectly when I select item by item by clicking on the shapes but it doesn't respect the "visible order" of shapes if I select them by "lassoing" with my mouse. In case of dragging my mouse over the shapes to select them, the routine should respect the visible order of shapes. Thanks in advance. Sub AlignFlush() Dim oshpR As ShapeRange Dim oshp As Shape Dim L As Long Dim rayPOS() As Single Set oshpR = ActiveWindow.Selection.ShapeRange ReDim rayPOS(1 To oshpR.Count) 'add to array For L = 1 To oshpR.Count rayPOS(L) = oshpR(L).Left Next L 'sort Call sortray(rayPOS) 'apply For L = 1 To oshpR.Count If L = 1 Then Set oshp = Windows(1).Selection.ShapeRange(1) PosTop = oshp.Top PosNext = oshp.Left + oshp.Width Else Set oshp = Windows(1).Selection.ShapeRange(L) oshp.Top = PosTop oshp.Left = PosNext PosNext = oshp.Left + oshp.Width End If Next L End Sub Sub sortray(ArrayIn As Variant) Dim b_Cont As Boolean Dim lngCount As Long Dim vSwap As Long Do b_Cont = False For lngCount = LBound(ArrayIn) To UBound(ArrayIn) - 1 If ArrayIn(lngCount) > ArrayIn(lngCount + 1) Then vSwap = ArrayIn(lngCount) ArrayIn(lngCount) = ArrayIn(lngCount + 1) ArrayIn(lngCount + 1) = vSwap b_Cont = True End If Next lngCount Loop Until Not b_Cont End Sub
Some comments on your existing code: Array counts always start at 0 unless you use the Option Base statement to set it to a different number. When you use ReDim, most of the time, you want to use the Preserve keyword, or the ReDim obliterates the existing array contents. But in this case, we know the array size ahead of time, so Preserve is not necessary. You call sortray, but didn't include it in your listing. I've added a sorting routine. But then you make no use of the sorted array in the section where you position the shapes. Working macro (based on your description of what you mean by "visible order" being the left-to-right sequence): Since you use the left position of the leftmost shape to apply to the others, here's a simpler way to do that: Sub AlignFlushLeftWithLeftmostShape() Dim ShpCount As Long Dim oshpR As ShapeRange Dim L As Long Dim rayPOS() As Single Set oshpR = ActiveWindow.Selection.ShapeRange ShpCount = oshpR.Count ReDim rayPOS(ShpCount - 1) For L = 0 To ShpCount - 1 rayPOS(L) = oshpR(L + 1).Left Next L Call BubbleSort(rayPOS) For x = 1 To ShpCount oshpR(x).Left = rayPOS(0) Next x End Sub Sub BubbleSort(arr) Dim lTemp As Long Dim i As Long Dim j As Long Dim lngMin As Long Dim lngMax As Long lngMin = LBound(arr) lngMax = UBound(arr) For i = lngMin To lngMax - 1 For j = i + 1 To lngMax If arr(i) > arr(j) Then lTemp = arr(i) arr(i) = arr(j) arr(j) = lTemp End If Next j Next i End Sub
How to convert array from (x,y)(z) dimensions into (x,y) dimensions?
I am working with Bloomberg's API in VBA and I want to be able to take in the arrays that the API gives out from requesting historical data and put it into a table that has field names. However, the array that the API gives me is given in this format: (x,y)(Z) but I cannot use that for inserting into a table. I also want to be able to add another piece of data into the array while I convert from one form to another I have tried just going through the Bloomberg array and replacing each element in a different array, but the main issues I have are not being able to know how big I need the array to be and how I am going to loop through the bloomberg API without going out of index and getting an error. I have tried using Ubound, but it does not work the way I have intended. This is the code I have tried using to convert my array and then insert it. It just puts in blank values and does not put in anything into the table Sub mWriteToTable(vTableName As String, ByVal vArray As Variant, vCUSIPS As Variant, vFields As Variant) On Error GoTo ErrorHandler Dim db As DAO.Database Dim rs As DAO.Recordset Dim x As Long, y As Long Dim TEST As String Dim DataArray() As Variant Set db = CurrentDb Set rs = db.OpenRecordset(vTableName, dbOpenDynaset, dbSeeChanges) TEST = "" Dim xBound As Integer, yBound As Integer, ThirdBound As Integer, fieldcount As Integer, NewBoundY As Integer, Z As Integer Dim Boundarynum As Integer Boundarynum = 0 Dim Boundarynum1 As Integer Boundarynum1 = 0 fieldcount = UBound(vFields, 1) + 1 xBound = UBound(vArray, 1) yBound = UBound(vArray, 2) NewBoundY = fieldcount * (fieldcount + 1) ReDim DataArray(0 To 20, 0 To (xBound + 1)) 'using a static size for the array for now. Will try and make it the same size as the bloomberg array 'TRANSFORMING ARRAY FROM BLOOMBERG For x = 0 To xBound For y = 0 To NewBoundY For Boundarynum1 = 0 To yBound On Error Resume Next DataArray(Boundarynum, Boundarynum1) = vArray(x, y)(Boundarynum1) Next Boundarynum = Boundarynum + 1 Next Next 'TRANSFORMING ARRAY FROM BLOOMBERG 'set CUSIP in array y = 0 Dim counter As Integer counter = 0 For Z = 0 To 20 If DataArray(Z, 0) = "" Then Debug.Print ("") counter = 1 ElseIf counter = 1 And DataArray(Z, 0) <> "" Then y = y + 1 DataArray(Z, 3) = vCUSIPS(y) counter = 0 Else DataArray(Z, 3) = vCUSIPS(y) End If Next 'set CUSIP in array For x = 0 To 20 With rs .AddNew For y = 0 To yBound ' On Error GoTo Line1 ' If vArray(x, y) = "NA" Then ' TEST = "This is a test" ' End If 'Line1: .fields(y) = DataArray(x, y) Next .Update End With Next 'Call fImmediateWindow(vArray) ErrorHandler: If Err.Number <> 0 Then Dim vMsg As String vMsg = "Error # " & Str(Err.Number) & " was generated by " & Err.Source & Chr(13) & "Error Line: " & Erl & Chr(13) & Err.Description MsgBox vMsg, , "Error", Err.HelpFile, Err.HelpContext End If rs.Close Set rs = Nothing db.Close Set db = Nothing End Sub ''' This is the way the Bloomberg Array looks when I get it. I am unsure of how to really work around this. The array from the program above just becomes blank.
Each element of the Bloomberg array is returning 2 sets of data. The key is to have your array have double the number of elements of the top level Bloomberg array. Sub ConvertBloombergTestData() Dim r As Variant r = getBloombergTestData Dim Values As Variant Dim n As Long Dim j As Long Dim Item ReDim Values(1 To (UBound(r) + 1) * 2, 1 To 2) For n = LBound(r) To UBound(r) j = j + 1 Item = r(n, 0) Values(j, 1) = Item(0) Values(j, 2) = Item(1) Item = r(n, 1) j = j + 1 Values(j, 1) = Item(0) Values(j, 2) = Item(1) Next End Sub Not knowing the the array nesting but knowing that we are returning pairs of data, we could add all the data to a collection and create our array bu iterating over the collection. Sub Test() Dim r As Variant, Values As Variant r = getBloombergTestData Values = ConvertBloombergArrayTo2d(r) End Sub Function ConvertBloombergArrayTo2d(BloombergArray) Dim Map As New Collection FlattenArray Map, BloombergArray Dim Results As Variant ReDim Results(1 To Map.Count / 2, 1 To 2) Dim n As Long, j As Long For n = 1 To Map.Count Step 2 j = j + 1 Results(j, 1) = Map.Item(n) Results(j, 2) = Map.Item(n + 1) Next ConvertBloombergArrayTo2d = Results End Function Sub FlattenArray(Map As Collection, Element As Variant) If Right(TypeName(Element), 2) = "()" Then Dim Item For Each Item In Element FlattenArray Map, Item Next Else Map.Add Element End If End Sub
Show dimensions of Excel shape in column widths and row heights vba
I have a spreadsheet that involves the user resizing some rectangular shapes, which are set on a background of an Excel grid with column width = row height = 10pixels. The purpose of this background is to give a scale to the plan, which is made by the shapes; in this case, one column or row represents 10cm - there is a thick border after every 10 cells to represent a metre: When the user resizes the rectangle, I would like the text inside the rectangle to display the dimensions, according to the scale of the plan. I have read many articles about how the shapes dimensions are provided in points, and the columns and rows in pixels (or a unit based on the font), and have found the conversion function between them, but it does not seem to give the results I would expect - the values for the width and height depend on the level of zoom, giving smaller and smaller results as I zoom out, even though the displayed pixel width remains the same. Is there a way to consistently convert the pixel units of the grid to the points unit of the shapes such that I can essentially count how many column widths and row heights comprise the shape dimensions? Here is the macro I have written so far: Option Explicit Dim sh As Shape Dim dbPx_Per_Unit As Double Dim strUnit As String Dim UserSelection As Variant Dim strText As String Dim strWidth As String Dim strHeight As String Sub LabelShapeSize() Set UserSelection = ActiveWindow.Selection 'is selection a shape? On Error GoTo NoShapeSelected Set sh = ActiveSheet.Shapes(UserSelection.Name) On Error Resume Next 'pixels are the units for the columns and rows 'dbPx_Per_Unit = InputBox("there are this many pixels per unit:", "Conversion Rate", 10) dbPx_Per_Unit = 100 'strUnit = InputBox("Unit Name:", "Units", "M") strUnit = "M" With sh 'Width and length is measured in points, so we need to convert the points to pixels to get the actual size strWidth = Format(Application.ActiveWindow.PointsToScreenPixelsX(.Width) / dbPx_Per_Unit, "#,##0.0") strHeight = Format(Application.ActiveWindow.PointsToScreenPixelsY(.Height) / dbPx_Per_Unit, "#,##0.0") 'this is our message that will be in the shape strText = strWidth & strUnit & " x " & strHeight & strUnit With .TextFrame2 .VerticalAnchor = msoAnchorMiddle With .TextRange.Characters .ParagraphFormat.FirstLineIndent = 0 .ParagraphFormat.Alignment = msoAlignCenter .Text = strText 'I'll sort something out for dark shapes at some point, but for now let's just write in black ink With .Font .Fill.Visible = msoTrue .Fill.ForeColor.ObjectThemeColor = msoThemeColorDark1 .Fill.Solid .Size = 10 End With End With End With End With Exit Sub 'No shape error NoShapeSelected: MsgBox "You must select a shape to calculate dimensions!", vbCritical, "Object not set to an instance of a Nobject" End Sub ****** for completeness, here is the final script I wrote implementing the solution in the answer below ****** Option Explicit Dim sh As Shape Dim db_Cols_Per_Unit As Double Dim strUnit As String Dim strText As String Dim userSelection As Variant Dim ws As Worksheet Dim clrBackground As Long Dim leftCol As Integer Dim colWidth As Integer Dim topRow As Integer Dim rowHeight As Integer Sub LabelShapeSize() Set userSelection = ActiveWindow.Selection Set ws = ActiveSheet db_Cols_Per_Unit = 10 strUnit = "M" 'is selection a shape? On Error GoTo NoShapeSelected Set sh = ActiveSheet.Shapes(userSelection.Name) On Error Resume Next topRow = 1 rowHeight = 0 leftCol = 1 colWidth = 0 With sh While ws.Cells(1, leftCol).Left <= .Left 'Move left until we find the first column the shape lies within leftCol = leftCol + 1 Wend While ws.Cells(1, leftCol + colWidth).Left <= .Left + .Width 'Continue moving left until we find the first column the shape does not lie within colWidth = colWidth + 1 Wend While ws.Cells(topRow, 1).Top <= .Top 'Move down until we find the first row the shape lies within topRow = topRow + 1 Wend While ws.Cells(topRow + rowHeight, 1).Top <= .Top + .Height 'Continue moving down until we find the first row the shape does not lie within rowHeight = rowHeight + 1 Wend 'this is our message that will be in the shape strText = Format(colWidth / db_Cols_Per_Unit & strUnit, "#,##0.0") & " x " & rowHeight / Format(db_Cols_Per_Unit, "#,##0.0") & strUnit clrBackground = .Fill.ForeColor.RGB With .TextFrame2 .VerticalAnchor = msoAnchorMiddle With .TextRange.Characters .ParagraphFormat.FirstLineIndent = 0 .ParagraphFormat.Alignment = msoAlignCenter .Text = strText With .Font .Fill.Visible = msoTrue .Fill.ForeColor.RGB = ContrastColor(clrBackground) .Fill.Solid .Size = 10 End With End With End With End With Exit Sub 'No shape error NoShapeSelected: MsgBox "You must select a shape to calculate dimensions!", vbCritical, "Object not set to an instance of a Nobject" End Sub Function ContrastColor(clrBackground As Long) As Long Dim brightness As Integer Dim luminance As Double Dim r As Integer Dim g As Integer Dim b As Integer r = clrBackground Mod 256 g = (clrBackground \ 256) Mod 256 b = (clrBackground \ 65536) Mod 256 luminance = ((0.199 * r) + (0.587 * g) + (0.114 * b)) / 255 If luminance > 0.5 Then brightness = 0 Else brightness = 255 End If ContrastColor = RGB(brightness, brightness, brightness) End Function thanks to #Gacek answer in this question for the luminance function.
I believe your best bet would be to make use of the Left, Top, Width, and Height cell properties. They'll tell you the value in Excel's weird format (the same units as used by the shapes), so you won't need to do any conversions. The downside is that as far as I know of, there's no way to get the row/column that exists at a given top/left value, so you need to search through all the rows/columns until you find the one that matches your shape's boundaries. Here's a quick example (there's probably an off-by-one error in here somewhere) Dim UserSelection As Variant Dim ws As Worksheet Dim sh As Shape Dim leftCol As Integer Dim colWidth As Integer Dim topRow As Integer Dim rowHeight As Integer Set ws = ActiveSheet Set UserSelection = ActiveWindow.Selection Set sh = ActiveSheet.Shapes(UserSelection.Name) leftCol = 1 colWidth = 0 While ws.Cells(1, leftCol).Left <= sh.Left 'Move left until we find the first column the shape lies within leftCol = leftCol + 1 Wend While ws.Cells(1, leftCol + colWidth).Left <= sh.Left + sh.width 'Continue moving left until we find the first column the shape does not lie within colWidth = colWidth + 1 Wend topRow = 1 rowHeight = 0 While ws.Cells(topRow, 1).Top <= sh.Top 'Move down until we find the first row the shape lies within topRow = topRow + 1 Wend While ws.Cells(topRow + rowHeight, 1).Top <= sh.Top + sh.height 'Continue moving down until we find the first row the shape does not lie within rowHeight = rowHeight + 1 Wend MsgBox "Shape is " & colWidth & " columns wide by " & rowHeight & " rows high"
chartArea position properties don't work in vba Excel 2013
I'm noob in vba (Excel macros). I need to add somes charts automatically in the same WorkSheet. This is my code: Sub runChart() Dim xchart As Chart Dim nameSheet As String nameSheet = ActiveSheet.Name Dim x As Integer Dim firstIndex As Integer Dim firstValue As Integer Dim actualValue As Integer Dim actualIndex As Integer Dim rChart1 As Range Dim rChart2 As Range MsgBox nameSheet firstIndex = 2 actualIndex = 2 firstValue = Cells(2, 1) actualValue = Cells(2, 1) Do Do actualIndex = actualIndex + 1 actualValue = Sheets(nameSheet).Cells(actualIndex, 1) Loop Until firstValue <> actualValue Set rChart1 = Range(Sheets(nameSheet).Cells(firstIndex, "E"), Sheets(nameSheet).Cells(actualIndex - 1, "E")) Set rChart1 = Union(rChart1, Range(Sheets(nameSheet).Cells(firstIndex, "J"), Sheets(nameSheet).Cells(actualIndex - 1, "J"))) Dim nameChart As String nameChart = CStr(Sheets(nameSheet).Cells(firstIndex, 5)) & " - " & Sheets(nameSheet).Cells(actualIndex, 5) & " " & CStr(Sheets(nameSheet).Cells(firstIndex, 1)) Set xchart = Charts.Add With xchart .Name = nameChart .ChartType = xlColumnClustered .SetSourceData rChart1 .Location Where:=xlLocationAsObject, Name:=nameSheet 'position and size chart .ChartArea.Top = 10 'this position is a example .ChartArea.Left = 1700 'this position is a example .ChartArea.Height = 400 'this size is a example .ChartArea.Width = 750 'this size is a example End With firstValue = Sheets(nameSheet).Cells(actualIndex, 1) firstIndex = actualIndex Loop Until (Sheets(nameSheet).Cells(actualIndex, 1) = vbNullString) End Sub So, my problem happens is in .ChartArea.left = 1700. The program says : The specified dimension is not valid for the current chart type anyone has any idea what 's happening? Thanks for your time :)
The ChartArea is the overall rectangle containing the chart within its parent ChartObject (the shape that contains the embedded chart). The position and size of the ChartArea are read only. But that's okay, you want to position and resize the ChartObject, which is the chart's .Parent. With xchart 'position and size chart .Parent.Top = 10 'this position is a example .Parent.Left = 1700 'this position is a example .Parent.Height = 400 'this size is a example .Parent.Width = 750 'this size is a example End With
Easy generic print Dictionary to Excel Worksheet using VBA
I'm working with really intricate data. Because of this I wrote this really nice function to print data to the debug area - the imediate window you can reach with Ctrl + G on the VBA, inside Excel. I need a similar function to print this generic data (that has numbers, strings, dictionarys and arrays) to a worksheet. 'call using: Call PrintDict(data) ' Where data can be a number, a string, a dictionary or an Array, ' with any of these inside. Sub PrintDict(ByVal dicttoprint As Variant, Optional indent As Integer = 0, Optional wasdict As Boolean = False) Dim i As Long Dim j As Long Dim indentStr As String indentStr = "" i = 0 Do While i < indent indentStr = indentStr + " " i = i + 1 Loop Dim key If (TypeName(dicttoprint) = "Dictionary") Then If (wasdict = True) Then Debug.Print vbNewLine; End If For Each key In dicttoprint.Keys: Debug.Print indentStr & key & " "; Call PrintDict(dicttoprint.Item(key), indent + 2, True) Next ElseIf (TypeName(dicttoprint) = "Variant()") Then If (wasdict = True) Then Debug.Print vbNewLine; End If For j = LBound(dicttoprint) To UBound(dicttoprint) Call PrintDict(dicttoprint(j), indent + 2) Next j Else Debug.Print indentStr & dicttoprint & " " End If End Sub Edit1: Ok, been thinking about, I have an idea, but can't solve some corner cases... Example expected output below: key1:____|__________|__________|__________|_________| _________|key1.1:___|_numvalue_|__________|_________| _________|__________|_numvalue_|__________|_________| _________|__________|_arr1Indx1|_numvalue_|_________| _________|__________|_arr1Indx2|_numvalue_|_________| _________|__________|_arr1Indx3|_numvalue_|_________| _________|key1.2:___|_numvalue_|__________|_________| _________|__________|_numvalue_|__________|_________| key2:____|_numvalue_|__________|__________|_________| key3:____|__________|__________|__________|_________| _________|_arr2Indx1|keyA.1:___|_numvalue_|_________| _________|__________|keyA.2:___|_strvalue_|_________| _________|_arr2Indx2|_numvalue_|__________|_________| Ok, I think now this output solves some corner cases. Any ideas on how to implement it? I'm thinking on having the function be able to pass X,Y parameters, that are optional and to return last Y. When working with text, the cursor naturally goes down, I don't know how to do this through recursion in a worksheet. Edit 2: Ok, this is pseudo code idea - is almost VBA, but I don't know how to make this work... Function PrintToWS(ByVal data As Variant, _ Optional rowi As Integer = 0, _ Optional coli As Integer = 0) As Integer If (TypeName(data) = "Dictionary") Then For Each key In data.Keys: Cells(rowi, coli).Value = key coli = coli + PrintToWS(data.Item(key), rowi+1, coli) Next ElseIf (TypeName(data) = "Variant()") Then For j = LBound(data) To UBound(data) coli = coli + PrintToWS(data(j), rowi+1, coli) Next j Else Cells(rowi, coli).Value = data coli = coli + 1 End If PrintToWS = coli End Function Edit2: Added it in a gist here
Solved. Code is below: 'usage: PrintToWS(yourdata) ' Optional parameters are to be used internally by the function, 'leave optional parameters blank. Function PrintToWS(ByVal data As Variant, _ Optional rowi As Integer = 1, _ Optional coli As Integer = 1, _ Optional wasdict As Integer = 0) As Integer Dim key Dim j As Integer If (TypeName(data) = "Dictionary") Then For Each key In data.Keys: Cells(rowi + wasdict, coli).Value = key rowi = PrintToWS(data.Item(key), rowi + wasdict, coli + 1, 1) wasdict = 0 Next ElseIf (TypeName(data) = "Variant()") Then For j = LBound(data) To UBound(data) rowi = PrintToWS(data(j), rowi, coli + 1) Next j Else Cells(rowi, coli).Value = data rowi = rowi + 1 End If PrintToWS = rowi End Function