Copy and pasting a line into a chart using VBA - excel

First time using this site. Borrowing an idea from the SpreadSheetGuru, I am copy and pasting bunch of drawn shapes into a temporary chart such that I can save them as a PNG image. I copy and paste different shapes, one by one, and then move them using Top and Left properties so they look like the original arrangement. It works great for rectangles and textboxes but gives me an error for lines (straight connectors). It says “the item with specified name wasn’t found” but I do not use anything different. I appreciate your help to solve this problem. Here is that part of the code below. The lines are copied and pasted, as I can see them when I step through the code, but cannot be "addressed" to be moved to their correct location on the chart
k = 0
For Each sh In ActiveSheet.Shapes ' ---------------------------------------- select one shape at a time
a1 = InStr(1, Trim(sh.Name), "TextBox", 1)
A2 = InStr(1, Trim(sh.Name), "Rectangle", 1)
a3 = InStr(1, Trim(sh.Name), "Straight", 1)
a4 = InStr(1, Trim(sh.Name), "Line", 1)
If a1 > 0 Or A2 > 0 Or a3 > 0 Or a4 > 0 Then
sh.Select
k = k + 1
Else: GoTo NextShape:
End If
sh.Name = sh.Name & k
'Copy/Paste Shape inside temporary chart
ActiveShape.Copy
cht.Activate
ActiveChart.Paste
ActiveChart.Shapes(UserSelection.Name).Top = ActiveShape.Top - Top0 ' === ERROR IS HERE
ActiveChart.Shapes(UserSelection.Name).Left = ActiveShape.Left - Left0
NextShape:
Next sh ' ----------------------------------------------------------------------

Here's a couple of different approaches. Both are pasting the shapes into a chart inserted on the worksheet (ie. I'm not using a chart sheet)
Sub CopyShapesToChart()
Dim sh, cht As Chart, e, shp, col As New Collection
Dim minY, minX, maxY, maxX, v
Set cht = ActiveSheet.ChartObjects(1).Chart
minY = 1000000# 'set starting points (random high number)
minX = 1000000#
'First collect all of the shapes we're interested in,
' and figure out the "bounding box" for them
For Each shp In ActiveSheet.Shapes
For Each e In Array("TextBox", "Rectangle", "Straight", "Line")
If InStr(1, shp.Name, e) > 0 Then
'tracking bounding box for all shapes
If shp.Top < minY Then minY = shp.Top
If shp.Left < minX Then minX = shp.Left
v = shp.Top + shp.Height
If v > maxY Then maxY = v
v = shp.Left + shp.Width
If v > maxX Then maxX = v
col.Add shp
Exit For
End If
Next e
Next shp
'resize the chartobject to fit the collection of shapes
cht.Parent.Height = maxY - minY
cht.Parent.Width = maxX - minX
'copy each shape into the chart
For Each shp In col
shp.Copy
cht.Paste
With cht.Shapes(cht.Shapes.Count)
.Top = shp.Top - minY
.Left = shp.Left - minX
End With
Next shp
'now export the chart...
End Sub
EDIT: a second approach with a little less work - instead of copying shapes one-by-one group all of them and copy them in one operation
Sub CopyShapesToChart2()
Dim sh, cht As Chart, e, shp
Dim arr(), ws As Worksheet, i As Long, rng
Set ws = ActiveSheet
Set cht = ws.ChartObjects(1).Chart
ReDim arr(0 To ws.Shapes.Count)
For Each shp In ws.Shapes
For Each e In Array("TextBox", "Rectangle", "Straight", "Line")
If InStr(1, shp.Name, e) > 0 Then
arr(i) = shp.Name
i = i + 1
Exit For
End If
Next e
Next shp
If i > 0 Then 'matched any shapes?
If i > 1 Then 'matched>1 shape?
ReDim Preserve arr(0 To i - 1)
Set rng = ws.Shapes.Range(arr).Group() 'group the matched shapes
Else
Set rng = ws.Shapes(arr(0)) 'just matched a single shape
End If
'resize the chartobject to fit the [collection of] shape[s]
cht.Parent.Height = rng.Height
cht.Parent.Width = rng.Width
rng.Copy 'copy the shape or group
cht.Paste
If i > 1 Then rng.Ungroup 'ungroup if we grouped anything
End If
End Sub

A friend of mine (Ray Hayes) provided the answer off line. To make the original code work, all is needed is to change "UserSelection.Name" to "Selection.Name".

Related

Excel VBA How to insert picture of a file as a static/embedded picture [duplicate]

I need to embed an image to a spreadsheet via Excel VBA, such that whenever I relocate my excel file, the image will still show up. How can I do this?
This code will insert an image on the current sheet and position it at at cell E10:
Set oPic = Application.ActiveSheet.Shapes.AddPicture("d:\temp\mypic.jpg", False, True, 1, 1, 1, 1)
oPic.ScaleHeight 1, True
oPic.ScaleWidth 1, True
oPic.Top = Range("E10").Top
oPic.Left = Range("E10").Left
Did you try using the macro recorder?
This is what it produced for me:
Sub Macro1()
ActiveSheet.Pictures.Insert ("C:\mypicture.jpg")
End Sub
Also tons of info using google search terms: "Insert Picture Using VBA Excel". The below code is taken from ExcelTip all credit to the original author Erlandsen Data Consulting.
With the macro below you can insert pictures at any range in a worksheet and they will remain as long as the picture itself remains in its original location.
The picture can be centered horizontally and/or vertically.
Sub TestInsertPicture()
InsertPicture "C:\FolderName\PictureFileName.gif", _
Range("D10"), True, True
End Sub
Sub InsertPicture(PictureFileName As String, TargetCell As Range, _
CenterH As Boolean, CenterV As Boolean)
' inserts a picture at the top left position of TargetCell
' the picture can be centered horizontally and/or vertically
Dim p As Object, t As Double, l As Double, w As Double, h As Double
If TypeName(ActiveSheet) <> "Worksheet" Then Exit Sub
If Dir(PictureFileName) = "" Then Exit Sub
' import picture
Set p = ActiveSheet.Pictures.Insert(PictureFileName)
' determine positions
With TargetCell
t = .Top
l = .Left
If CenterH Then
w = .Offset(0, 1).Left - .Left
l = l + w / 2 - p.Width / 2
If l < 1 Then l = 1
End If
If CenterV Then
h = .Offset(1, 0).Top - .Top
t = t + h / 2 - p.Height / 2
If t < 1 Then t = 1
End If
End With
' position picture
With p
.Top = t
.Left = l
End With
Set p = Nothing
End Sub
With the macro below you can insert pictures and fit them to any range in a worksheet.
Sub TestInsertPictureInRange()
InsertPictureInRange "C:\FolderName\PictureFileName.gif", _
Range("B5:D10")
End Sub
Sub InsertPictureInRange(PictureFileName As String, TargetCells As Range)
' inserts a picture and resizes it to fit the TargetCells range
Dim p As Object, t As Double, l As Double, w As Double, h As Double
If TypeName(ActiveSheet) <> "Worksheet" Then Exit Sub
If Dir(PictureFileName) = "" Then Exit Sub
' import picture
Set p = ActiveSheet.Pictures.Insert(PictureFileName)
' determine positions
With TargetCells
t = .Top
l = .Left
w = .Offset(0, .Columns.Count).Left - .Left
h = .Offset(.Rows.Count, 0).Top - .Top
End With
' position picture
With p
.Top = t
.Left = l
.Width = w
.Height = h
End With
Set p = Nothing
End Sub

Change chart marker color if there is continuous value higher than a baseline value

I am currently creating a VBA macro to change the color of the marker of a chart if the value in the chart consists of 3 continuous spikes that exceeds a baseline value of 0.7.
For example, in the picture below, I've create a macro to change all the marker colors to red if the value is higher than the baseline value, but not if there are 3 continuous values higher than baseline value.
My Code
This is what I've tried - changing the marker color to red if the value exceeds 0.7
Sub Tester()
Dim cht As Chart, s As Series, p As point
Dim vals, x As Integer
Set cht = ThisWorkbook.Worksheets("mySheet3").ChartObjects("Chart 1").Chart
Set s = cht.SeriesCollection(1)
vals = s.Values
For x = LBound(vals) To UBound(vals)
If vals(x) > 0.7 Then
With s.Points(x)
.MarkerBackgroundColor = RGB(255, 0, 0)
.MarkerForegroundColor = RGB(255, 0, 0)
End With
End If
Next x
End Sub
For that you would need to work with a window size to check always 3 dots in a row if they are above basline if the are color them and move 1 further to check the next 3 in a row.
Option Explicit
Sub Tester()
Dim cht As Chart
Set cht = ThisWorkbook.Worksheets("mySheet3").ChartObjects("Chart 1").Chart
Dim s As Series
Set s = cht.SeriesCollection(1)
Dim vals As Variant
vals = s.Values
Const WindowSize As Long = 3
Dim Colorize As Boolean
Dim x As Long
For x = LBound(vals) To UBound(vals)
If x + WindowSize - 1 <= UBound(vals) Then
Colorize = True
Dim w As Long
For w = x To x + WindowSize - 1
If Not vals(w) > 0.7 Then
Colorize = False
Exit For
End If
Next w
If Colorize Then
For w = x To x + WindowSize - 1
With s.Points(w)
.MarkerBackgroundColor = RGB(255, 0, 0)
.MarkerForegroundColor = RGB(255, 0, 0)
End With
Next w
End If
End If
Next x
End Sub

Positioning pictures in worksheet

Below code count pictures that are pasted (by other macro) as msorectangle shape in excel worksheet and position them in 1 row in specific distance beetween each of them. I need to add another restrctions to positioning and im struggling with coding it. Question is how to upgrade this code if:
If number of pictures is <=6 than 1 row of pictures and set size to h:7,25cm w:4,7cm
If number of pictures is >6 and <=11 then 1 row of pictures with size h:5,9cm w:3,8cm
If number of pictures is =12 than 2 rows with size from 1 point h:7,25cm w:4,7cm.
If number of pictures is >12 than every (7, 13, 19, 25 etc. pic) is starting from next row with size from point nr 2 h:5,9cm w:3,8cm
The list of pictures is dynamic.
Sub Sample2()
Dim shp As Shape, shp2 As Shape
Dim ws As Worksheet
Dim lstShp As Integer
Dim shpLft As Double, shpTop As Double, shpWidth As Double, shpHeight As Double
Dim inBetweenMargin As Double
Dim i As Long
'~~> In betwen margin
inBetweenMargin = 8
Set ws = ThisWorkbook.Worksheets("wk")
With ws
'~~> Get the max shape number(name)
For Each shp In .Shapes
If shp.AutoShapeType = msoShapeRectangle Then
If Val(shp.Name) > 1 And Val(shp.Name) > lstShp Then _
lstShp = Val(shp.Name)
End If
Next
'~~> Loop through the shapes
For i = 1 To lstShp
'~~> This is required in case you delete shape 3
'~~> and have only shapes 1,2,4,5 etc...
On Error Resume Next
Set shp = .Shapes(CStr(i))
'shp2 = first photo
Set shp2 = ws.Shapes("1")
On Error GoTo 0
'~~> position them
If Not shp Is Nothing And shp.AutoShapeType = msoShapeRectangle Then
If shpLft = 0 And shpTop = 0 And shpWidth = 0 Then
shpLft = shp.Left
shpTop = shp.Top
shpWidth = shp.Width
Else
shp.Top = shpTop
shp.Left = shpLft + shpWidth + inBetweenMargin
shpLft = shp.Left
shpWidth = shp.Width
End If
End If
'position picture nr 7 and above in second row
If Val(shp.Name) = 7 Then
shp.Top = shp2.Top + shp2.Height + inBetweenMargin
shp.Left = shp2.Left
shpLft = shp.Left
shpWidth = shp.Width
End If
If Val(shp.Name) >= 8 Then
shp.Top = shp2.Top + shp2.Height + inBetweenMargin
End If
Next i
End With
End Sub
For the 2nd last condition, if the total picture count is 12 then I am safely assuming that you need 6 per line. And for the last condition you want 7 per line. For these two we will use a Counter and then we will do either Counter Mod 6 or Counter Mod 7 for that purpose. You can read about Mod operator in MS KB.
The logic is to reset the .Top and .Left in the next line for the last 2 conditions.We will use a boolean variable for that.
Is this what you are trying?
Option Explicit
Sub Sample()
Dim shp As Shape, shp2 As Shape
Dim ws As Worksheet
Dim lstShp As Integer
Dim shpLft As Single, shpTop As Single, shpWidth As Single, shpHeight As Single
Dim oldLeft As Single, oldTop As Single
Dim inBetweenMargin As Single
Dim i As Long, counter As Long, picCount As Long
Dim nextLine As Boolean, MultipleRows As Boolean
Dim ModByNumber As Long
'~~> In betwen margin
inBetweenMargin = 8
Set ws = ThisWorkbook.Worksheets("Sheet1")
With ws
'~~> Get the max shape number(name)
For Each shp In .Shapes
If shp.AutoShapeType = msoShapeRectangle Then
If Val(shp.Name) > 1 And Val(shp.Name) > lstShp Then _
lstShp = Val(shp.Name)
picCount = picCount + 1
End If
Next
Select Case picCount
Case 1 To 6
'~~> Set your default height and Width
shpHeight = 7.25 * 28.34646 '<~~ Cm to Points
shpWidth = 4.7 * 28.34646 '<~~ Cm to Points
Case 7 To 11
'~~> Set your default height and Width
shpHeight = 5.9 * 28.34646 '<~~ Cm to Points
shpWidth = 3.8 * 28.34646 '<~~ Cm to Points
Case 12
'~~> Set your default height and Width
shpHeight = 7.25 * 28.34646 '<~~ Cm to Points
shpWidth = 4.7 * 28.34646 '<~~ Cm to Points
MultipleRows = True
ModByNumber = 6
Case Is > 12
'~~> Set your default height and Width
shpHeight = 5.9 * 28.34646 '<~~ Cm to Points
shpWidth = 3.8 * 28.34646 '<~~ Cm to Points
MultipleRows = True
ModByNumber = 7
End Select
nextLine = False
'~~> Loop through the shapes
For i = 1 To lstShp
'~~> This is required in case you delete shape 3
'~~> and have only shapes 1,2,4,5 etc...
On Error Resume Next
Set shp = .Shapes(CStr(i))
On Error GoTo 0
'~~> position them
If Not shp Is Nothing Then
If shp.AutoShapeType = msoShapeRectangle Then
If shpLft = 0 And shpTop = 0 Then
shpLft = shp.Left
shpTop = shp.Top
shp.Height = shpHeight
shp.Width = shpWidth
'~~> Storing the top and left for resetting
'~~> when moving to next line
oldTop = shp.Top
oldLeft = shp.Left
counter = counter + 1
Else
shp.Top = shpTop
oldTop = shpTop
If nextLine = True Then
shp.Left = shpLft
nextLine = False
counter = 1
Else
shp.Left = shpLft + shpWidth + inBetweenMargin
counter = counter + 1
End If
shp.Height = shpHeight
shp.Width = shpWidth
shpLft = shp.Left
If MultipleRows = True Then
If counter Mod ModByNumber = 0 Then
shpLft = oldLeft
shpTop = oldTop + shpHeight + inBetweenMargin
nextLine = True
End If
End If
End If
End If
End If
'~~> This is required if there is no shape between 4 and 6.
'~~> 5 gets deleted? Also the reason why we are not using "i Mod 7"
'~~> and using "counter Mod 7"
Set shp = Nothing
Next i
End With
End Sub
Screenshots
If number of pictures is 6 than 1 row and set size to h:7,25cm w:4,7cm
If number of pictures is >7 and <=10 then 1 row of pictures with size h:5,9cm w:3,8cm
If number of pictures is <12 than 2 rows with size from 1 point.
If number of pictures is >12 than every 7 pic is starting from next row with size from point nr 2
So if we take i as the amount of pictures:
We can do some simple calculations to check which condition is met and use Select Case
to identify and assign each of your 4 cases like so:
Select Case i
Case IS >= 12
numberofrows = i \ 7 '(this only gives whole numbers)
Formatting = 2
Case IS > 10
numberofrows = 2
Formatting = 1
Case IS >= 7
numberofrows = 1
Formatting = 2
Case ELSE
numberofrows = 1
Formatting = 1
End Select

Rotate line about a point

I need to rotate an horizontal line about its end point in VB Excel.
I tried various solution found on internet but they don't work well.
I give you some images about what I want to do:
This is my line:
point A (72; 378)
point B (165; 378)
And I want to rotate the point A about the point B, the angle of rotation is variable. For example this is a 60 degrees rotation
The letters A and B were added after the screenshot with a photo editor
Try this.
Sub test()
Dim x As Single, y As Single
Dim nx As Single, ny As Single, l As Single
Dim i As Single, ra As Single
Dim Ws As Worksheet
Dim shp As Shape
Set Ws = ActiveSheet
For Each shp In Ws.Shapes
If shp.Type = msoLine Then
shp.Delete
End If
Next
x = 165
y = 378
l = 165 - 72
For i = 90 To 150
ra = WorksheetFunction.Radians(i)
nx = x - Sin(ra) * l
ny = y + Cos(ra) * l
Set shp = Ws.Shapes.AddLine(x, y, nx, ny)
With shp
.Line.EndArrowheadStyle = msoArrowheadTriangle
.Line.ForeColor.RGB = RGB(255, 0, 0)
.Line.Weight = 2
End With
DoEvents
Application.Wait Now + (TimeSerial(0, 0, 1) / 2)
shp.Delete
Next i
Set shp = Ws.Shapes.AddLine(x, y, nx, ny)
With shp
.Line.EndArrowheadStyle = msoArrowheadTriangle
.Line.ForeColor.RGB = RGB(255, 0, 0)
.Line.Weight = 2
End With
End Sub

Position picture horizontally in center

I want to position picture in center of range, but it just doesn't work for me. Maybe anybody knows how to do it?
Here is my code:
Sub InsertPictureInRange(PictureFileName As String, TargetCells As Range)
Dim p As Object, t As Double, l As Double, r As Double, b As Double
Dim aspect
Dim w, h
If TypeName(ActiveSheet) <> "Worksheet" Then Exit Sub
If Dir(PictureFileName) = "" Then Exit Sub
l = 1: r = 22 ' co-ordinates of top-left cell
t = 47: b = 88 ' co-ordinates of bottom-right cell
Set TargetCells = Range("A47:V88")
Set p = ActiveSheet.Pictures.Insert(PictureFileName)
With p
With .ShapeRange
.LockAspectRatio = msoTrue ' lock the aspect ratio (do not distort picture)
aspect = .Width / .Height ' calculate aspect ratio of picture
.Left = Cells(t, l).Left ' left placement of picture
.Top = Cells(t, l).Top ' top left placement of picture
End With
w = (Cells(b, r).Left + Cells(b, r).Width - Cells(t, l).Left) ' width of cell range
h = Cells(b, r).Top + Cells(b, r).Height - Cells(t, l).Top ' height of cell range
If (w / h < aspect) Then
.ShapeRange.Width = w ' scale picture to available width
Else
.ShapeRange.Height = h ' scale picture to available height
End If
.Placement = 1
End With
Set p = Nothing
End Sub
Found out answer:
Sub InsertPictureInRange(PictureFileName As String, TargetCells As Range)
Dim p As Object, t As Double, l As Double, r As Double, b As Double
Dim aspect
Dim w, h
If TypeName(ActiveSheet) <> "Worksheet" Then Exit Sub
If Dir(PictureFileName) = "" Then Exit Sub
l = 1: r = 22 ' co-ordinates of top-left cell
t = 47: b = 88 ' co-ordinates of bottom-right cell
Set TargetCells = Range("A47:V88")
Set p = ActiveSheet.Pictures.Insert(PictureFileName)
With p
With .ShapeRange
.LockAspectRatio = msoTrue ' lock the aspect ratio (do not distort picture)
aspect = .Width / .Height ' calculate aspect ratio of picture
.Left = Cells(t, l).Left + TargetCells.Width / 2 - p.Width / 2 ' left placement of picture
.Top = Cells(t, l).Top ' top left placement of picture
End With
w = (Cells(b, r).Left + Cells(b, r).Width - Cells(t, l).Left) ' width of cell range
h = Cells(b, r).Top + Cells(b, r).Height - Cells(t, l).Top ' height of cell range
If (w / h < aspect) Then
.ShapeRange.Width = w ' scale picture to available width
Else
.ShapeRange.Height = h ' scale picture to available height
End If
.Placement = 1
End With
Set p = Nothing
End Sub

Resources