i'm trying to fit the user form to screen on diifernet screens.
the userform was first managed in my work screen and i fit it to my screen but when i'm trying the userform on other screens part of it vanished.
i can't put the whole code in here but i will put just the sub that suppose to fit to screen:
Private Sub UserForm_Initialize()
Dim w As Long, h As Long
Application.Visible = False
With Me
rMaxHeight = Application.Height
rMaxWidth = Application.Width
If .Height > Application.Height - 10 Then
rNormalHeight = rMaxHeight * 0.85
Else
rNormalHeight = Me.Height
End If
If .Width > Application.Width - 10 Then
rNormalWidth = rMaxWidth * 0.85
Else
rNormalWidth = Me.Width
End If
.StartUpPosition = 1
.Left = 0
.Top = 0
FitSize
...
Private Sub FitSize()
Dim h, w
Dim c As Control
Dim PHeight, PWidth As Double
PHeight = rNormalHeight / Me.Height
PWidth = rNormalWidth / Me.Width
h = 0: w = 0
If PHeight = 1 And PWidth = 1 Then Exit Sub
For Each c In Me.Controls
If c.Visible Then
If c.Top + c.Height > h Then h = (c.Top + c.Height) ' * PHeight
If c.Left + c.Width > w Then w = (c.Left + c.Width) ' * PWidth
If Not TypeName(c) = "Image" Or TypeName(c) = "ListBox" Then c.FontSize = c.FontSize * ((PHeight + PWidth) / 2)
End If
Next c
If h > 0 And w > 0 Then
With Me
.Width = w + 40
.Height = h + 40
End With
End If
End Sub
hope you could help me with that
Thank you all
sefi
You can either Re-position every single control in the UserForm with VBA or simply enable ScrollBars for the UserForm object so they can access all the elements with a bit of scrolling.
Change the ScrollBars property of the UserForm to like 3 - fmScrollBarsBoth as the default is 0 - fmScrollBarsNone
Then you need to figure out how tall and wide it needs to be:
ScrollHeight
ScrollWidth
Hello and thank for everyone that tried to help me.
I found the solution to this problem by fitting the controls to the proportion of the screen copared with the original form.
At first step you need to calculate the proportion:
Dim PHeight, PWidth As Double
'define form size compared with the original size of the form
rMaxHeight = Application.Height
rMaxWidth = Application.Width
If Me.Height > Application.Height Then
rNormalHeight = rMaxHeight * 0.85
Else
rNormalHeight = Me.Height
End If
If Me.Width > Application.Width Then
rNormalWidth = rMaxWidth * 0.85
Else
rNormalWidth = Me.Width
End If
'normal is the size needed in normal mode before the form get to maximize mode
'we want to calculate the needed divided to the orignal
PHeight = rNormalHeight / Me.Height
PWidth = rNormalWidth / Me.Width
now we call fitsize()
Private Sub FitSize()
Dim h, w
Dim c As Control
h = 0: w = 0
If PHeight = 1 And PWidth = 1 Then Exit Sub ' if the it is the original size of the form- don't bother...
'loop on the form controls
For Each c In Me.Controls
If c.Visible Then ' just visible controls
c.Top = c.Top * PHeight ' fit to proportion of the screen compared with the original form
c.Height = c.Height * PHeight
If c.Top + c.Height > h Then h = c.Top + c.Height ' collect the height needed from the controls
c.Left = c.Left * PWidth ' fit to proportion of the screen compared with the original form
c.Width = c.Width * PWidth
If c.Left + c.Width > w Then w = c.Left + c.Width ' collect the height needed from the controls
'fit the font for the text controls
If Not TypeName(c) = "Image" Or TypeName(c) = "ListBox" Then c.FontSize = c.FontSize * ((PHeight + PWidth) / 2)
End If
Next c
'define the size needed form the specific screen
If h > 0 And w > 0 Then
With Me
.Width = w + 40
.Height = h + 40
.StartUpPosition = 0
.Left = Application.Left + (0.5 * Application.Width) - (0.5 * .Width)
.Top = Application.Top + (0.5 * Application.Height) - (0.5 * .Height)
End With
End If
End Sub
this code will define the size needed in each screen by the proportion that calculated in the needed value divded to the original value.
Try it and tell me if it works.
thank you all
sefi
Related
I'm building my first user interface in VBA on Microsoft Access.
I am trying to get the .Left variable to show up in the drop down selection (library?).
The only thing that pops up is LeftPadding, which I'm pretty sure that isn't what I need. Why am I not able to declare the Left position of the rectangles?
Is there another type of variable that I should be using to declare the position of rectangles?
My follow up issue, if I'm doing that correctly, is about a nested If statement. I'm trying to calculate whether a newly visible rectangle's position + its dimensions exceeds the Left position of an already visible rectangle, and if so, position it elsewhere.
Dim ctl As Control
For Each ctl In [Forms]![frmBuilder]
If Left(ctl.Name, 3) = "box" And Box1.Visible = True Then
If ctl.Visible = True Then
NextCaseNum = Int(Right(ctl.Name, (Len(ctl.Name)) - 3) + 1)
NextCasePosition = (ctl.lef + ctl.Width) + 1440 / 60
NextCaseName = "box" & NextCaseNum
Else
CurCaseLeft = ctl.Left
CurCaseWidth = ctl.Width
CurCaseHeight = ctl.Height
With ctl
.Top = UprightBottom - HInch
.Left = NextCasePosition
.Width = WInch
.Height = HInch
.Visible = True
End With
If CurCaseLeft + CurCaseWidth > Upright2.Left Then
With Beam1
.Top = (((5.5 + 6) * 60) + Box1.Top) / 1440
.Left = Upright1.Left
.Height = (5.5 * 60) / 1440
.Width = ((4 * 60) / 1440) + Upright2.Left - Upright1.Left
.Visible = True
End With
End If
I think the problem lies with CurCaseLeft and CurCaseWidth, because I don't know how to define them in the function due to the current box's ctl.Left not showing up.
Do I have to separate the nested If statement in to a different function and call that function from the current function?
Try to be more explicit:
Dim ctl As Control
Dim rct As Rectangle
For Each ctl In [Forms]![frmBuilder]
If Left(ctl.Name, 3) = "box" And Box1.Visible = True Then
If ctl.Visible = True Then
Set rct = ctl
NextCaseNum = Int(Right(rct.Name, (Len(rct.Name)) - 3) + 1)
NextCasePosition = (rct.Left + rct.Width) + 1440 / 60
My program constantly updates a shape's position, based on another manipulable shape. Without DoEvents, GotoSlide, .AddShape, or increasing slideshowwindow, the screen will not refresh, and will only show the end result of the shape's position. I can't use DoEvents because it slows down too much when the mouse is moved, and I can't use GotoSlide, .AddShape, or similar methods because they don't allow the user to click in the PowerPoint (will either ignore or crash the program).
Please note, the workarounds here How to refresh the active Slide in a slide show? cause the problems I noted above (.AddShape, GotoSlide, and increasing slideshowwindow all crash the program if the mouse clicks)
I have experimented with GetQueueStaus and GetInputState as a means to filter out certain events from DoEvents, but neither seem to apply. And using them to only DoEvents when necessary obviously isn't an option because it will always be necessary when the shape is moving, and the movement will always slow down based on mouse movement during DoEvents.
Finally, I have also experimented with charts because they are the only shape in PowerPoint that has .refresh functionality, but I both was unable to get this to work, and decided that it wasn't worth the time because the shape of the chart will always be restricted to a rectangle (too limited for what I want my program to do).
Here is my code: (I am currently using GotoSlide method)
Public Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Sub Aloop()
Dim Q As Shape
Dim B As Shape
Dim TotalTime As Long
Dim StartTime As Long
Dim TimerTextRange As TextRange
Dim A As Shape
Const PI = 3.14159265359
Set A = ActivePresentation.Slides(1).Shapes("A")
Set SldOne = ActivePresentation.Slides(1)
Set Q = ActivePresentation.Slides(1).Shapes("Q")
Set B = ActivePresentation.Slides(1).Shapes("B")
Set TimerTextRange = ActivePresentation.Slides(1).Shapes("TimerTextRange") _
.TextFrame.TextRange
TotalTime = 0
StartTime = Timer
With TimerTextRange
.Text = Int(TotalTime + (Timer - StartTime))
End With
Do While TimerTextRange.Text < 10
With TimerTextRange
.Text = Int(TotalTime + (Timer - StartTime))
End With
If Q.Left < A.Left Then
Q.Left = Q.Left + 1
ElseIf Q.Left > A.Left Then
Q.Left = Q.Left - 1
Else
End If
If Q.Top < A.Top Then
Q.Top = Q.Top + 1
ElseIf Q.Top > A.Top Then
Q.Top = Q.Top - 1
Else
End If
If GetAsyncKeyState(vbKeyD) Then
A.Left = A.Left + 4
Else
End If
If GetAsyncKeyState(vbKeyW) Then
A.Top = A.Top - 4
Else
End If
If GetAsyncKeyState(vbKeyS) Then
A.Top = A.Top + 4
Else
End If
If GetAsyncKeyState(vbKeyA) Then
A.Left = A.Left - 4
Else
End If
With Q
If (-A.Top + (.Top + .Width / 2)) > 0 Then
.Rotation = ((Atn(((A.Left + A.Width / 2) - ((.Left + .Width / 2))) / (-(A.Top + A.Height / 2) + ((.Top + .Width / 2))))) * 180 / PI)
ElseIf (-A.Top + (.Top + .Width / 2)) < 0 Then
.Rotation = ((Atn(((A.Left + A.Width / 2) - ((.Left + .Width / 2))) / (-(A.Top + A.Height / 2) + ((.Top + .Width / 2))))) * 180 / PI) + 180
Else
End If
End With
ActivePresentation.SlideShowWindow.View.GotoSlide (1)
Loop
End Sub
The code makes shape Q follow shape A around the screen, and the user can control shape a with W A S D keyboard inputs.
!!Be careful not to click the slide while the code is running, or the program will crash!!
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
Form 2 is to enter ladder base and its off set value and for snakes where the snake head is and skakes off set value.
Not able to figure out why its not working . When the values are entered to show simulation it show's up error sandl is private and the other one is the validation one .
Public Class Form2
Dim sandl(99) As Integer
Dim snakeshead As TextBox()
Dim snakesoffset As TextBox()
Dim ladderfoot As TextBox()
Dim ladderoffset As TextBox()
Dim rnd As Random = New Random
Sub initialise()
For i = 0 To 99
sandl(i) = 0 ' reset data
Next
End Sub
Sub snake()
snakeshead = {txthead1, txthead2, txthead3, txthead4, txthead5, txthead6, txthead7, txthead8, txthead9, txthead10}
snakesoffset = {txtoffset1, txtoffset2, txtoffset3, txtoffset4, txtoffset5, txtoffset6, txtoffset7, txtoffset8, txtoffset9, txtoffset10}
' SnakeHead(i).Text = (i + 81).ToString
' SnakeOffset(i).Text = "10" '(i + 10).ToString
For i As Integer = 0 To 9
While True
Dim base = rnd.Next(90) + 11
If sandl(base - 1) <> 0 Then
Continue While
End If
Dim offset = rnd.Next(20) + 10
If base - offset < 1 Then
Continue While
End If
snakeshead(i).Text = base.ToString
snakesoffset(i).Text = offset.ToString
sandl(base - 1) = -offset
Exit While
End While
Next
End Sub
Sub ladders()
ladderfoot = {txtladder1, txtladder2, txtladder3, txtladder4, txtladder5, txtladder6, txtladder7, txtladder8, txtladder9, txtladder10}
ladderoffset = {txtladderoffset1, txtladderoffset2, txtladderoffset3, txtladderoffset4, txtladderoffset5, txtladderoffset6, txtladderoffset7, txtladderoffset8, txtladderoffset9, txtladderoffset10}
'For i As Integer = 0 To 9
' LadderFoot(i).Text = (i + 11).ToString
' LadderOffset(i).Text = "10"
For i As Integer = 0 To 99
sandl(i) = 0 'reset data
Next
For i As Integer = 0 To 9
While True
Dim base = rnd.Next(90) + 1
If sandl(base - 1) <> 0 Then
Continue While
End If
Dim offset = rnd.Next(20) + 10
If base + offset > 100 Then
Continue While
End If
ladderfoot(i).Text = base.ToString
ladderoffset(i).Text = offset.ToString
sandl(base - 1) = offset
Exit While
End While
Next
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
For i As Integer = 0 To 99
sandl(i) = 0 'reset data
Next
Dim valid = Validate(ladderfoot, ladderoffset, +1, "Ladder")
If (valid) Then
valid = Validate(snakeshead, snakesoffset, -1, "Snake")
End If
If (valid) Then
'Form3 = New Form3
Form3.ShowDialog()
End If
End Sub
Private Function Validate(tbBase() As TextBox, tbOffset() As TextBox, delta As Integer, s As String) As Boolean
For i As Integer = 0 To 9
Dim base As Integer
If ((Not Integer.TryParse(tbBase(i).Text.Trim(), base)) OrElse (base < 1) OrElse (base > 100) OrElse (sandl(base - 1) <> 0)) Then
MessageBox.Show(s & (i + 1).ToString() & " base is invalid.")
tbBase(i).Select()
tbBase(i).SelectAll()
Return False
End If
base -= 1 'zero based
Dim offset As Integer
If ((Not Integer.TryParse(tbOffset(i).Text.Trim(), offset)) OrElse (offset < 10) OrElse (offset > 30) OrElse (base + offset * delta < 0) OrElse (base + offset * delta >= 100)) Then
MessageBox.Show(s & (i + 1).ToString() & " offset is invalid.")
tbOffset(i).Select()
tbOffset(i).SelectAll()
Return False
End If
sandl(base) = offset * delta 'write offset
Next
Return True
End Function
End Class
Public Class Form3
Enum EState
Dice
Move
Slide
Wait
Win
End Enum
Dim Fnt = New Font("Arial", 16)
Dim FntBig = New Font("Arial", 256)
Dim Frame As Integer = -1 'counter
Dim State = EState.Dice
Dim Rnd As Random = New Random
Dim Dice As Integer
Dim Pos As Point = New Point(32, 640 + 32)
Dim CurrentIndex As Integer = -1
Dim NextIndex As Integer
Dim TargetIndex As Integer
Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dice = 0
Frame = -1
State = EState.Dice
Pos = New Point(32, 640 + 32)
CurrentIndex = -1
End Sub
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
DrawBackground(e.Graphics)
Frame += 1
Dim oldState = State
Select Case State
Case EState.Dice
If Frame = 0 Then
Dice = Rnd.Next(6) + 1 'roll dice
TargetIndex = CurrentIndex + Dice
NextIndex = CurrentIndex
ElseIf Frame >= 63 Then
If CurrentIndex + Dice < 100 Then
State = EState.Move 'valid dice
Else
State = EState.Wait 'invalid dice
End If
Dice = 0
End If
Case EState.Move
If Frame Mod 64 = 0 Then
CurrentIndex = NextIndex
If CurrentIndex = TargetIndex Then
If CurrentIndex < 99 Then 'not win
If Form2.sandl(CurrentIndex) <> 0 Then
State = EState.Slide 'snake or ladder
Else
State = EState.Dice 'empty tile
End If
TargetIndex = CurrentIndex + Form2.sandl(CurrentIndex)
Else
State = EState.Win 'win
End If
Else
NextIndex = CurrentIndex + 1 'move
End If
Else
Dim c = GetCoordinate(CurrentIndex)
Dim n = GetCoordinate(NextIndex)
Dim dx = (n.X - c.X)
Dim dy = (n.Y - c.Y)
Pos.X = c.X * 64 + (dx * (Frame Mod 64)) + 32
Pos.Y = c.Y * 64 + (dy * (Frame Mod 64)) + 32
End If
Case EState.Slide
If Frame >= 63 Then
CurrentIndex = TargetIndex
If CurrentIndex < 99 Then
State = EState.Dice 'not win
Else
State = EState.Win 'win
End If
Else
Dim c = GetCoordinate(CurrentIndex)
Dim n = GetCoordinate(TargetIndex)
Dim dx = (n.X - c.X)
Dim dy = (n.Y - c.Y)
Pos.X = c.X * 64 + (dx * (Frame Mod 64)) + 32
Pos.Y = c.Y * 64 + (dy * (Frame Mod 64)) + 32
End If
Case EState.Wait
If Frame >= 63 Then
State = EState.Dice
End If
End Select
e.Graphics.FillEllipse(Brushes.Blue, Pos.X - 16, Pos.Y - 16, 32, 32) 'draw player
If Dice > 0 Then
Dim size = e.Graphics.MeasureString(Dice.ToString, FntBig)
e.Graphics.DrawString(Dice.ToString, FntBig, Brushes.Black, 320 - size.Width / 2, 320 - size.Height / 2) 'print dice
End If
If State <> oldState Then
Frame = -1 'reset counter
End If
If State <> EState.Win Then
PictureBox1.Invalidate() 'schedule next paint
End If
End Sub
Private Sub DrawBackground(g As Graphics)
For y As Integer = 0 To 9
For x As Integer = 0 To 9
If (((x + y) Mod 2) = 0) Then
g.FillRectangle(Brushes.LightGray, x * 64, y * 64, 64, 64) 'dark rectangle
End If
Dim z = (9 - y) * 10 + x + 1
If y Mod 2 = 0 Then
z = (9 - y) * 10 + (9 - x) + 1
End If
g.DrawString(z.ToString, Fnt, Brushes.Black, x * 64, y * 64) 'number
Next
Next
For i As Integer = 0 To 99
If Form2.sandl(i) <> 0 Then
Dim base = GetCoordinate(i)
Dim offset = GetCoordinate(i + Form2.sandl(i))
If Form2.sandl(i) > 0 Then 'ladder
Dim delta = Math.Abs(base.X - offset.X) + 4
g.DrawLine(Pens.Green, base.X * 64 + 32 - delta, base.Y * 64 + 32, offset.X * 64 + 32 - delta, offset.Y * 64 + 32) 'left part
g.DrawLine(Pens.Green, base.X * 64 + 32 + delta, base.Y * 64 + 32, offset.X * 64 + 32 + delta, offset.Y * 64 + 32) 'right part
Else 'snake
g.DrawLine(Pens.Red, base.X * 64 + 32, base.Y * 64 + 32, offset.X * 64 + 32, offset.Y * 64 + 32) 'red line
End If
End If
Next
End Sub
Private Function GetCoordinate(i As Integer) As Point
Dim result As Point
result.Y = 9 - (i \ 10)
result.X = i Mod 10
If result.Y Mod 2 = 0 Then
result.X = 9 - result.X
End If
Return result
End Function
End Class
In Form2, change your declaration from
Dim sandl(99) As Integer
to
Public sandl(99) As Integer
This would allow Form3 to access your integer array
Rename your Validate method to something else, like ValidateTextBoxes, or if you intend to overload the base.Validate, then declare as
Private Overloads Function Validate
So I've been working on this for the past week. Although it can't do miracles, I can say I've got a pretty good result:
I just wanted to put this code out there for all the poor souls like me that are looking for some kind of vba macro that helps them avoid label overlaps in a scatter plot, because while doing my research on the subject, I wasn't able to find anything helpful.
Const PIXEL_TO_POINT_RATIO As Double = 0.72 '1 Pixel = 72/96*1 Point
Const tStep As Double = 0.1
Const rStep As Double = 0.1
Dim pCount As Integer
Sub ExampleMain()
RearrangeScatterLabels Sheet5
RearrangeScatterLabels Sheet25
End Sub
Sub RearrangeScatterLabels(sht As Worksheet)
Dim plot As Chart
Dim sCollection As SeriesCollection
Dim dLabels() As DataLabel
Dim dPoints() As Point
Dim xArr(), yArr(), stDevX, stDevY As Double
Dim x0, x1, y0, y1 As Double
Dim temp() As Double
Dim theta As Double
Dim r As Double
Dim isOverlapped As Boolean
Dim safetyNet, validEntry, currentPoint As Integer
Set plot = sht.ChartObjects(1).Chart 'XY chart (scatter plot)
Set sCollection = plot.SeriesCollection 'All points and labels
safetyNet = 1
pCount = (sCollection.Count - 1)
ReDim dLabels(1 To 1)
ReDim dPoints(1 To 1)
ReDim xArr(1 To 1)
ReDim yArr(1 To 1)
For pt = 1 To sCollection(1).Points.Count
For i = 1 To pCount
If sCollection(i).Points.Count <> 0 Then
'Dynamically expand the arrays
validEntry = validEntry + 1
If validEntry <> 1 Then
ReDim Preserve dLabels(1 To UBound(dLabels) + 1)
ReDim Preserve dPoints(1 To UBound(dPoints) + 1)
ReDim Preserve xArr(1 To UBound(xArr) + 1)
ReDim Preserve yArr(1 To UBound(yArr) + 1)
End If
Set dLabels(i) = sCollection(i).Points(pt).DataLabel 'Store all label objects
Set dPoints(i) = sCollection(i).Points(pt) 'Store all point objects
temp = getElementDimensions(, dPoints(i))
xArr(i) = temp(0) 'Store all points x values
yArr(i) = temp(2) 'Store all points y values
End If
Next
Next
If UBound(dLabels) < 2 Then Exit Sub
pCount = UBound(dLabels)
stDevX = Application.WorksheetFunction.StDev(xArr) 'Get standard deviation for x
stDevY = Application.WorksheetFunction.StDev(yArr) 'Get standard deviation for y
If stDevX = 0 Then stDevX = 1
If stDevY = 0 Then stDevY = 1
r = 0
For currentPoint = 1 To pCount
theta = Rnd * 2 * Application.WorksheetFunction.Pi()
x0 = xArr(currentPoint)
y0 = yArr(currentPoint)
x1 = xArr(currentPoint)
y1 = yArr(currentPoint)
isOverlapped = True
Do Until Not isOverlapped
safetyNet = safetyNet + 1
If safetyNet < 500 Then
If Not checkForOverlap(dLabels(currentPoint), dLabels, dPoints, plot) Then
'No label is within bounds and not overlapping
isOverlapped = False
r = 0
theta = Rnd * 2 * Application.WorksheetFunction.Pi()
safetyNet = 1
Else
'Move label so it does not overlap
theta = theta + tStep
r = r + rStep * tStep / (2 * Application.WorksheetFunction.Pi())
x1 = x0 + stDevX * r * Cos(theta)
y1 = y0 + stDevY * r * Sin(theta)
dLabels(currentPoint).Left = x1
dLabels(currentPoint).Top = y1
End If
Else
safetyNet = 1
Exit Do
End If
Loop
Next
End Sub
Function checkForOverlap(ByRef dLabel As DataLabel, ByRef dLabels() As DataLabel, ByRef dPoints() As Point, ByRef dChart As Chart) As Boolean
checkForOverlap = False 'Return false by default
'Detect label going over chart area
If detectOverlap(dLabel, , , dChart) Then
checkForOverlap = True
Exit Function
End If
'Detect labels overlap
For i = 1 To pCount
If Not dLabel.Left = dLabels(i).Left Then
If detectOverlap(dLabel, dLabels(i)) Then
checkForOverlap = True
Exit Function
End If
End If
Next
'Detect label overlap with point
For i = 1 To pCount
If detectOverlap(dLabel, , dPoints(i)) Then
checkForOverlap = True
Exit Function
End If
Next
End Function
Function getElementDimensions(Optional dLabel As DataLabel, Optional dPoint As Point, Optional dChart As Chart) As Double()
'Get element dimensions and compensate slack
Dim eDimensions(3) As Double
'Working in IV quadrant
If dPoint Is Nothing And dChart Is Nothing Then
'Get label dimensions and compensate padding
eDimensions(0) = dLabel.Left + PIXEL_TO_POINT_RATIO * 3 'Left
eDimensions(1) = dLabel.Left + dLabel.Width - PIXEL_TO_POINT_RATIO * 3 'Right
eDimensions(2) = dLabel.Top + PIXEL_TO_POINT_RATIO * 6 'Top
eDimensions(3) = dLabel.Top + dLabel.Height - PIXEL_TO_POINT_RATIO * 3 'Bottom
End If
If dLabel Is Nothing And dChart Is Nothing Then
'Get point dimensions
eDimensions(0) = dPoint.Left - PIXEL_TO_POINT_RATIO * 5 'Left
eDimensions(1) = dPoint.Left + PIXEL_TO_POINT_RATIO * 5 'Right
eDimensions(2) = dPoint.Top - PIXEL_TO_POINT_RATIO * 5 'Top
eDimensions(3) = dPoint.Top + PIXEL_TO_POINT_RATIO * 5 'Bottom
End If
If dPoint Is Nothing And dLabel Is Nothing Then
'Get chart dimensions
eDimensions(0) = dChart.PlotArea.Left + PIXEL_TO_POINT_RATIO * 22 'Left
eDimensions(1) = dChart.PlotArea.Left + dChart.PlotArea.Width - PIXEL_TO_POINT_RATIO * 22 'Right
eDimensions(2) = dChart.PlotArea.Top - PIXEL_TO_POINT_RATIO * 4 'Top
eDimensions(3) = dChart.PlotArea.Top + dChart.PlotArea.Height - PIXEL_TO_POINT_RATIO * 4 'Bottom
End If
getElementDimensions = eDimensions 'Return dimensions array in Points
End Function
Function detectOverlap(ByVal dLabel1 As DataLabel, Optional ByVal dLabel2 As DataLabel, Optional ByVal dPoint As Point, Optional ByVal dChart As Chart) As Boolean
'Left, Right, Top, Bottom
Dim AxL, AxR, AyT, AyB As Double 'First label coordinates
Dim BxL, BxR, ByT, ByB As Double 'Second label coordinates
Dim eDimensions() As Double 'Element dimensions
eDimensions = getElementDimensions(dLabel1)
AxL = eDimensions(0)
AxR = eDimensions(1)
AyT = eDimensions(2)
AyB = eDimensions(3)
If dPoint Is Nothing And dChart Is Nothing Then
'Compare with another label
eDimensions = getElementDimensions(dLabel2)
End If
If dLabel2 Is Nothing And dChart Is Nothing Then
'Compare with a point
eDimensions = getElementDimensions(, dPoint)
End If
If dPoint Is Nothing And dLabel2 Is Nothing Then
'Compare with chart area
eDimensions = getElementDimensions(, , dChart)
End If
BxL = eDimensions(0)
BxR = eDimensions(1)
ByT = eDimensions(2)
ByB = eDimensions(3)
If dChart Is Nothing Then
detectOverlap = (AxL <= BxR And AxR >= BxL And AyT <= ByB And AyB >= ByT) 'Reverse De Morgan's Law
Else
detectOverlap = Not (AxL >= BxL And AxR <= BxR And AyT >= ByT And AyB <= ByB) 'Is in chart bounds (working in IV quadrant)
End If
End Function
I realize the code is kinda rough and not optimized, but I can't spend more time on this project. I've left quite a few notes around to help read it, should anyone choose to continue this project. Hope this helps.
Best wishes, Schadenfreude.
Building on your function, I made a routine to randomly reposition the labels, assigning a score according to how much overlap it would cause, and thusly optimize. The results aren't great for my own data set, but I think it can be tuned easily for most usages.
There are some issues with the borders and the axis labels which maybe I'll account for later.
Option Explicit
Sub ExampleUsage()
RearrangeScatterLabels ActiveSheet.ChartObjects(1).Chart, 3
End Sub
Sub RearrangeScatterLabels(plot As Chart, Optional timelimit As Double = 5)
Dim sCollection As SeriesCollection
Set sCollection = plot.SeriesCollection
Dim pCount As Integer
pCount = sCollection(1).Points.Count
If pCount < 2 Then Exit Sub
Dim dPoints() As Point
Dim xArr() As Double ' Label center position X
Dim yArr() As Double ' Label center position Y
Dim wArr() As Double ' Label width
Dim hArr() As Double ' Label height
Dim pArr() As Double ' Marker position X
Dim qArr() As Double ' Marker position Y
Dim mArr() As Double ' Markersize
ReDim dPoints(1 To pCount)
ReDim xArr(1 To pCount)
ReDim yArr(1 To pCount)
ReDim wArr(1 To pCount)
ReDim hArr(1 To pCount)
ReDim pArr(1 To pCount)
ReDim qArr(1 To pCount)
ReDim mArr(1 To pCount)
Dim theta As Double
Dim i As Integer
Dim j As Integer
Dim dblStart As Double
' Loop through all points to get their handles and coordinates
For i = 1 To pCount
' Store all point objects
Set dPoints(i) = sCollection(1).Points(i)
' Extract their coordinates and size
pArr(i) = dPoints(i).Left
qArr(i) = dPoints(i).Top
mArr(i) = dPoints(i).MarkerSize
' Store the size of the corresponding labels
wArr(i) = dPoints(i).DataLabel.Width
hArr(i) = dPoints(i).DataLabel.Height
' Starting position (center of label) is middle below
xArr(i) = pArr(i)
yArr(i) = qArr(i) + mArr(i)
Next
Dim newX As Double
Dim newY As Double
Dim dE As Double
Dim wgtOverlap As Double
Dim wgtDistance As Double
Dim wgtClose As Double
wgtOverlap = 10000 ' Extra penalty for overlapping
wgtDistance = 10000 ' Penalty for being nearby other labels
wgtClose = 10 ' Penalty for being further from marker
' Limit the function by time
dblStart = Timer
Do Until TimerDiff(dblStart, Timer) > timelimit
' Pick a random label to move around
i = Int(Rnd * pCount + 1)
' Pick a new random position by angle
theta = Rnd * 2 * Application.WorksheetFunction.Pi()
' Determine the position it would shift to
If Abs(Sin(theta) * wArr(i)) > Abs(hArr(i) * Cos(theta)) Then
' above or below
If Sin(theta) > 0 Then
' above
newX = pArr(i) + wArr(i) * Cos(theta) / 2
newY = qArr(i) - hArr(i) / 2 - mArr(i) / 2
Else
' below
newX = pArr(i) + wArr(i) * Cos(theta) / 2
newY = qArr(i) + hArr(i) / 2 + mArr(i) / 2
End If
Else
' left or right side
If Cos(theta) < 0 Then
' left
newX = pArr(i) - wArr(i) / 2 - mArr(i) / 2
newY = qArr(i) - hArr(i) * Sin(theta) / 2
Else
' right
newX = pArr(i) + wArr(i) / 2 + mArr(i) / 2
newY = qArr(i) - hArr(i) * Sin(theta) / 2
End If
End If
' Determine increase in energy caused by this shift
dE = 0
For j = 1 To pCount
If i <> j Then
' Current overlap with labels
If 2 * Abs(xArr(i) - xArr(j)) < wArr(i) + wArr(j) _
And 2 * Abs(yArr(i) - yArr(j)) < hArr(i) + hArr(j) Then
dE = dE - Abs(xArr(i) - xArr(j) + (wArr(i) + wArr(j)) / 2) _
* Abs(yArr(i) - yArr(j) + (hArr(i) + hArr(j)) / 2)
dE = dE - wgtOverlap
End If
' New overlap with labels
If 2 * Abs(newX - xArr(j)) < wArr(i) + wArr(j) _
And 2 * Abs(newY - yArr(j)) < hArr(i) + hArr(j) Then
dE = dE + Abs(newX - xArr(j) + (wArr(i) + wArr(j)) / 2) _
* Abs(newY - yArr(j) + (hArr(i) + hArr(j)) / 2)
dE = dE + wgtOverlap
End If
' Current overlap with labels
If Abs(xArr(i) - pArr(j)) < wArr(i) / 2 + mArr(j) _
And Abs(yArr(i) - qArr(j)) < hArr(i) / 2 + mArr(j) Then
dE = dE - wgtOverlap
End If
' New overlap with points
If Abs(newX - pArr(j)) < wArr(i) / 2 + mArr(j) _
And Abs(newY - qArr(j)) < hArr(i) / 2 + mArr(j) Then
dE = dE + wgtOverlap
End If
' We like the neighbours to be far away
dE = dE - wgtDistance / ((xArr(i) - xArr(j)) ^ 2 + (yArr(i) - yArr(j)) ^ 2)
dE = dE + wgtDistance / ((newX - xArr(j)) ^ 2 + (newY - yArr(j)) ^ 2)
End If
' We like the offsets to be low
dE = dE - wgtClose * (Abs(xArr(i) - pArr(i)) + Abs(yArr(i) - qArr(i)))
dE = dE + wgtClose * (Abs(newX - pArr(i)) + Abs(newY - qArr(i)))
Next
' If it didn't get worse, adjust to new position
If dE <= 0 Then
xArr(i) = newX
yArr(i) = newY
End If
Loop
' Actually adjust the labels
For i = 1 To pCount
dPoints(i).DataLabel.Left = xArr(i) - wArr(i) / 2
dPoints(i).DataLabel.Top = yArr(i) - hArr(i) / 2
Next
End Sub
' Timer function from Peter Albert
' http://stackoverflow.com/questions/15634623
Function TimerDiff(dblTimerStart As Double, dblTimerEnd As Double)
Dim dblTemp As Double
dblTemp = dblTimerEnd - dblTimerStart
If dblTemp < -43200 Then
dblTemp = dblTemp + 86400
End If
TimerDiff = dblTemp
End Function
NET developers.
I'm trying to put 20 labels on a form and place them line by line (I do this by the .Top method). I am sure there is a way I can program declaring and formatting by looping through more general code 20 times.
The below is what I've done for the first label.
Thanks in advance for help!
Dim Label1 As New Label
Me.Controls.Add(Label1)
For m = 1 To OutlookManager3.GlobalVariables.SelectedAppointmentsNo
With Label1
.Width = 512
.Height = 18
.Top = subject.Top + subject.Height + m * 6 + (m - 1) * 18 + (m - 1) * 6
.Left = 12
.Text = "label" & m
End With
Next
You should place this as the first line inside your loop:
Dim Label1 As New Label
And this as the last line insde your loop:
Me.Controls.Add(Label1)
Example 1
For m = 1 To OutlookManager3.GlobalVariables.SelectedAppointmentsNo
Dim Label1 As New Label
With Label1
.Width = 512
.Height = 18
.Top = subject.Top + subject.Height + m * 6 + (m - 1) * 18 + (m - 1) * 6
.Left = 12
.Text = "label" & m
End With
Me.Controls.Add(Label1)
Next
Example 2
For m = 1 To OutlookManager3.GlobalVariables.SelectedAppointmentsNo
Me.Controls.Add(New Label() With {.Width = 512, .Height = 18, .Top = (subject.Top + subject.Height + m * 6 + (m - 1) * 18 + (m - 1) * 6), .Left = 12, .Text = ("label" & m)})
Next
you can use your code by place declare statement inside loop
For m = 1 To OutlookManager3.GlobalVariables.SelectedAppointmentsNo
Dim Label1 As New Label
With Label1
.Width = 512
.Height = 18
.Top = subject.Top + subject.Height + m * 6 + (m - 1) * 18 + (m - 1) * 6
.Left = 12
.Text = "label" & m
End With
Me.Controls.Add(Label1)
Next
or use panel just like this but you have to place declare statement inside loop
or should make label array for future reference by
Dim label(yoursize) As Label
For m = 1 To OutlookManager3.GlobalVariables.SelectedAppointmentsNo
label(m) = new label
label(m).ID="future referece id"
With Label1
.Width = 512
.Height = 18
.Top = subject.Top + subject.Height + m * 6 + (m - 1) * 18 + (m - 1) * 6
.Left = 12
.Text = "label" & m
End With
by this you can use that next time
Me.Controls.Add(Label1)
Next