I have some code that looks at the position of a line that a user can edit. The purpose of editing this line is to get rough measurements from a plot plan which works so far. I'm working on making it much more interactive by recording measured positions as well.
Here's the issue. When this shape (a connector elbow) is on the cabinet and the access point it records its position in points using shape.top and shape.left (code below). When I resize this line to put the access point end on another access point, the cabinet position changes as well even though it didn't move. I did notice that the scale height and width changed on the drawing but I can't figure out why that would affect the initial point.
It's worth noting that as you rotate the elbow connector the value of width and height rotate. That means sometimes height is up and down and sometimes its the value you'd expect width to be. Still the left position only stays constant when the connector is rotated 180 degrees.
Is there a relation between scale height/width and the top/left value?
Sub Measure()
Set sp = ActiveSheet.Shapes("Measurement")
Msgbox(sp.Top & "//" & sp.Left)
end sub
Edit: So I realize I mentioned a lot about the program, the real problem is why do the top left measurements change despite the top left staying stationary on the screen? And only in the 270/90 rotation (happens automatically depending how you drag the line)
Add Screen Shots (Measurements are Top then left)
This is the first screen shot with a 270 rotation
This is the second, notice the top left stayed stationary but the points changed
This next group is with a 180/0 rotation (shape auto rotates, otherwise I would just lock it and be done).
Rotated 180 first screen shot
Rotated 180 second screen shot
I sympathized with your problem recognition and I did not understand this phenomenon, but after testing, it seems that the original coordinates are preserved even if the figure is rotated. This is because even if the rotation is performed again, the rotation is performed by the original coordinate value. If you artificially align the position, the coordinates appear to move accordingly. I tested it as follows.
Sub Measure()
Dim rngT As Range
Dim sp As Shape
Set sp = ActiveSheet.Shapes("Measurement")
'MsgBox (sp.Top & "//" & sp.Left)
Set rngT = Range("k" & Rows.Count).End(xlUp).Offset(1, 0)
rngT = sp.Top
rngT.Offset(, 1) = sp.Left
End Sub
Sub test()
Dim sp As Shape
Dim Ws As Worksheet
Dim vDB
Dim i As Integer, t As Single, l As Single
Dim r As Integer
vDB = Range("k2", Range("L" & Rows.Count).End(xlUp))
Set Ws = ActiveSheet
r = UBound(vDB, 1)
For i = 1 To r
t = vDB(i, 1)
l = vDB(i, 2)
Set sp = Ws.Shapes.AddShape(msoShapeRectangle, l, t, 10, 10)
Next i
End Sub
I figured it out guys.
So in orientation 0 or 180 the Shape.top and Shape.Left give accurate consistent measurements.
In the 90 or 270 the top and left have to be adjusted to give accurate consistent measurements.
The code is Shape.Top-((Shape.Width-Shape.Height)/2) and Shape.Left+((Shape.Width-Shape.Height)/2)
(NOTE: THIS ADJUSTMENT ONLY APPLIES WHEN OBJECT IS ROTATED 90 or 270//Shape.Rotation)
Also, the shape used here is an elbow connector
Related
I have create some shapes in Excel, group them together and i can not find how to apply fill color on the whole group. Not on the shapes but on the background.
Any help?
In short, this isn't possible on a shape group.
Detailed explanation:
From https://learn.microsoft.com/en-us/office/vba/api/excel.groupshapes
The GroupedShapes object "Represents the individual shapes within a
grouped shape."
Because the group itself only represents the shapes within it, it doesn't have its own fill colour as such, only the fill colours of the shapes within it.
So, as I guess you've discovered, if you do something like this:
With Sheet1.Shapes(1).GroupItems.Parent
.Fill.ForeColor.RGB = RGB(255, 0, 0)
End With
Then you are telling the group to apply the Forecolor to every shape within the group, and not the group object itself.
If you wish to have a background colour to the group, then a workaround would be to create a rectangle at the back, and set the colour of that instead.
Update: Workaround Example
If you wish to implement my suggested workaround, then the following will get you there. You will need to adjust for colours / workbook / worksheet / shape group name etc. There might be a prettier way, but I have this working...
Const shapeGroupName As String = "ShapeGroup"
Const shapeGroupBGName As String = "ShapeGroupBG"
Const shapeGroupMargin As Single = 5
Dim x As Integer
Dim y As Integer
Dim h As Integer
Dim w As Integer
Dim shapeCount As Integer
Dim shapeCol() As String
With Sheet1.shapes.Range(Array(shapeGroupName))
ReDim Preserve shapeCol(.GroupItems.Count)
For shapeCount = 1 To .GroupItems.Count
shapeCol(shapeCount) = .GroupItems.Item(shapeCount).Name
Next
y = .Top - shapeGroupMargin
x = .Left - shapeGroupMargin
h = .Height + shapeGroupMargin * 2
w = .Width + shapeGroupMargin * 2
.Ungroup
End With
shapeCol(0) = shapeGroupBGName
With Sheet1.shapes.AddShape(msoShapeRectangle, x, y, w, h)
.Name = shapeGroupBGName
.Fill.ForeColor.RGB = RGB(255, 0, 0)
.Line.DashStyle = msoLineDashDot
.ZOrder msoSendToBack
End With
With Sheet1.shapes.Range(shapeCol).Group
.Name = shapeGroupName
End With
This works by getting the dimensions of the group, and the names of the shapes within it. Next it ungroups the shapes, adds a rectangle behind the existing shapes, and then regroups accordingly.
A couple of notes:
I have created an arbitrary margin size, as the dimensions of the group are actually the top left and bottom right of the objects within it. In excel, the GUI adds a nice bit extra on to this, so its up to you to set this to what you want.
You can also implement a different shape (i.e. a rounded rectangle instead if desired etc.).
If you have multiple shape groups, then the above could be modified easily to accommodate this.
I am drawing circles in excel with a number in them, but as soon as that number hits a double digit, the second digit is not visible.
I would like the textsize to fit the shape.
I cannot change the size of the circle, because this is based on the opportunity value.
I am working on a business funnel, the relevant input of the funnel is:
- ticket number
- value of opportunity (potential revenue): used to size the circle
The circles are drawn in the funnel, which exists of four stages that are depicted as four shapes.
I have tried:
Public Function DrawCircle(x As Integer, y As Integer, tickets As Collection, ticketIndex As Integer)
Dim myCircle As Shape
Dim circleSize As Integer
circleSize = GetTicketSize(tickets, ticketIndex)
Dim name As String
name = "" & tickets.Item(ticketIndex).id
Set myCircle = ThisWorkbook.Sheets("Graphic funnel").Shapes.AddShape(msoShapeOval, x, y, circleSize, circleSize)
myCircle.Select
Selection.ShapeRange.Fill.Visible = msoTrue
Selection.ShapeRange.TextFrame.Characters.Text = name
Selection.ShapeRange.Left = Selection.ShapeRange.Left
Selection.ShapeRange.Top = Selection.ShapeRange.Top - circleSize / 2
With myCircle
.DeleteText
.wrapformat = msoWrapFormat
.WordWrap = True
.AutoSize = msoAutoSizeTextToFitShape
End With
But it returns: "object doesn't support this property or method"
I am creating a game(Bomber Man), i need my character to not go through the wall blocks which are separate images in a control array. When i use this code the player goes through the walls and goes crazy in all directions.
*NOTE:I have already found out how to do collision detection, what my main question is what to do when collision is detected?
This is the code i have so far,
'imgWhite is my player,imgWall is my block of wall.
'JUMPW is my Const variable which is set to 325
Private Sub tmrBlock_Timer()
Dim x As Integer
For x = 0 To 72
If imgWhite.Left < (imgWall(x).Left + imgWall(x).Width) Then
white.dx = JUMPW
End If
If imgWall(x).Left < (imgWhite.Left + imgWhite.Width) Then
white.dx = -JUMPW
End If
If imgWhite.Top < (imgWall(x).Top + imgWall(x).Height) Then
white.dy = JUMPW
End If
If (imgWhite.Top + imgWhite.Height) > imgWall(x).Top Then
white.dy = -JUMPW
End If
Next x
End Sub
Perhaps a better approach is to check for collision in your player chatacter movement routine?
It looks like what you are doing here is if the character is in the wall, throw them back out (by JumpW). It may be better to say that the PC can only move in a direction in the first place if there is no impediment.
Also, you should probably either use for x = imgwall.LBOUND to imgwall.UBOUND or for each wall in imgwall loop, to avoid having to make code changes every time you add (or remove) a wall.
I need to calculate the height and width of a svg text element. Is there a way to do so without actually adding it to the DOM of my page? I need only the measures not the actual element.
I am using neither d3 nor Raphael, but only plain JavaScript. (Maybe I should use one of the former for my calculations?)
What I am after is just a function like imagettfbbox in PHP, but in plain JavaScript. Is there such a thing? Or is it easy to write?
Since I am not actually using the text elements it seems strange to me to add them and hide them (I also have read somewhere that Firefox has problems with calculating the bbox of hidden elements, Calculating vertical height of a SVG text). But maybe this is the only way to go? Will I have to work with opacity in that case? Do I destroy the element somehow afterwards?
Maybe there is no good way to achieve exactly what I was after. However, giving up the "without actually adding it to the DOM of my page" part, the following function seems to achieve my goal.
function bboxText( svgDocument, string ) {
var data = svgDocument.createTextNode( string );
var svgElement = svgDocument.createElementNS( svgns, "text" );
svgElement.appendChild(data);
svgDocument.documentElement.appendChild( svgElement );
var bbox = svgElement.getBBox();
svgElement.parentNode.removeChild(svgElement);
return bbox;
}
Edit:
A note on the calculation of the height: the height of the bbox returned, i.e. bbox.height, is always the full height of the glyph, i.e. a will have the same height as A. And I could not find a way to calculate them more exactly.
However, one can calculate the height of uppercase accented characters, e.g. Ä. This will be just the negative of the y coordinate of the bbox, i.e. -bbox.y.
Using this one can calculate, for example, some coordinates for vertical alignment. For example to emulate the dominantBaseline attribute set to text-before-edge, text-after-edge, and central.
text-before-edge: dy = -bbox.y
text-after-edge: dy = -bbox.height -bbox.y
central: dy = -bbox.y -bbox.height/2
Where dy is the vertical translation. This can be used to get around limitations of some applications that do not support these alignments set by attributes.
I have encountered a similar problem in VB.Net !
I written a VB.Net program that generate a SVG file and that needs to compute width of text to compute next vertical bar position as you can see in following image
The B line vertical bar is positionned in computing max text width of A line elements.
To do that in Console VB.Net application, I execute following lines of code
Private Sub WriteTextInfo(sInfo As String)
If sInfo <> "" Then
'display Text in SVG file
sw.WriteLine("<text x ='" & (xPos + 10) & "' y='" & yPos & "' fill='black' font-family='Arial' font-size='20'>" & sInfo & "</text>")
'Create Font object as defined in <text> SVG tag
Dim font As New Font("Arial", 20.0F)
'Compute width of text in pixels
Dim xSize = TextRenderer.MeasureText(sInfo, font)
'Divide pixels witdh by a factor 1.4 to obtain SVG width !
Dim iWidth As Decimal = Math.Truncate(CDec(xSize.Width) / 1.4)
'If new vertical position is greater than old vertical position
If xPos + iWidth > xPosMax Then
xPosMax = xPos + iWidth
End If
End If
End Sub
In resume, I compute text's width in pixels using TextRenderer.MeasureText() function and I divide this number by 1.4 to obtain SVG width.
1.4 value is obtained by experiment relatively to my case !
To use TextRendered and Font objects, I have added reference to
System.Drawing
System.Windows.Forms
As you can see, I don't use any DOM method to compute text's width because I compute it in VB.Net program.
I'm looking for a way to graph multiple point within an (X,Y) plane, and have them appear (then disappear) one at a time. I have a total of about 400 (x,y) points, which are position tracking information gathered from a Inertial Measurement Unit.
Each data point is separated by approx 4 ms (I can change this interval to make it longer), so if possible, I would like to display one dot at a time, each for 4ms, until the next dot appears. The final product should display the object's movement path over the 5-6 second sample time. From my excel file, I have two columns (one for X and one for Y) which are 400 elements long (400 rows).
I'm hoping for a way to plot the X,Y scatter information one row at a time, and looking for some code that will increment through each row and plot the corresponding scatter. I am a semi-fluent programmer, but have never used MS Visual Basic before. All the solutions for Excel that I have found so far (which are similar to my problem) involve writing code for the graph in Visual Basic. I think I may be able to get that working, if someone can help me with the code... but if there is another piece of software which does this (for free! I'm on a student budget!) then I am willing to try multiple solutions.
I found this code already:
Sub Macro1()
Charts.Add
ActiveChart.ChartType = xlXYScatter
ActiveChart.SetSourceData Source:=Sheets("Sheet1").Range("A3:B3"), PlotBy _
:=xlColumns
ActiveChart.Location Where:=xlLocationAsObject, Name:="Sheet1"
With ActiveChart.Axes(xlCategory)
.MinimumScale = -30
.MaximumScale = 30
.MinorUnit = 1
.MajorUnit = 5
.Crosses = xlAutomatic
.ReversePlotOrder = False
.ScaleType = xlLinear
.DisplayUnit = xlNone
End With
With ActiveChart.Axes(xlValue)
.MinimumScale = 0
.MaximumScale = 2800
.MinorUnit = 50
.MajorUnit = 100
.Crosses = xlAutomatic
.ReversePlotOrder = False
.ScaleType = xlLinear
.DisplayUnit = xlNone
End With
For I = 3 To Worksheets(1).Range("A65536").End(xlUp).Row
ActiveChart.SetSourceData Source:=Sheets("Sheet1").Range("A3:B" & I), PlotBy _
:=xlColumns
ActiveChart.Location Where:=xlLocationAsObject, Name:="Sheet1"
ST = Timer
While Timer < ST + 1
Wend
Next I
End Sub
However, being as there was no comments or documentation, I had difficulty perusing through it and picking it apart. I feel like once I can really understand the format of Visual basic I can modify the program to adapt for my specific needs... but understanding it is the first step.
So again, my information is in the format of 3 columns:
X displacement -- Y displacement -- Timestamp
(position in mm) (position in mm) (seconds, or iteration #, whichever is easier)