I am trying to create a Sankey-diagram in Excel, and as a start to this, I am trying to create some "entry arrows" for the left part of the diagram, which will look roughly like this:
I created it by making a chevron arrow, and dragging the rightmost points of it to line up with the tip of the arrow.
Now, to do this for all the arrows I need, I want to do this programmatically, but I can't figure out if there is any way to do much with the nodes (?) of the shape. Trying to record a macro gave me nothing.
This is what I have so far, the macro aborts on the Debug.Print line, probably because the node object doesn't have a Left property :P
Sub energiInn()
Dim r As Range, c As Range
Dim lo As ListObject
Dim topp As Double, høgde As Double
Dim i As Long, farge As Long
Dim nd As Object
Set lo = Tabell.ListObjects("Energi_inn_elektrolyse")
Set r = lo.DataBodyRange
topp = 50
With SankeyDiagram.Shapes
For i = 1 To r.Rows.Count
høgde = Application.WorksheetFunction.Max(10, r.Cells(i, 2) / 50#)
With .AddShape(Type:=msoShapeChevron, Left:=50, top:=topp, Width:=200, Height:=høgde)
.Name = r.Cells(i, 1)
farge = fargekart((i - 1) Mod UBound(fargekart))
.Fill.ForeColor.RGB = RGB(farge Mod 256, (farge \ 256) Mod 256, farge \ 65536)
For Each nd In .Nodes
Debug.Print nd.Left
Next nd
End With
topp = topp + høgde
Next i
End With
Debug.Print r.Address
End Sub
Honestly, I am unsure if this can be done at all, but even if it is impossible, it would be nice to get it confirmed :)
What you're looking for is .Nodes.SetPosition. Because it's relative positioning, this can be a challenge. You need to use the objects position elements to make sure the points are moving in relation to the shape.
With .AddShape(Type:=msoShapeChevron, Left:=50, Top:=topp, Width:=200, Height:=høgde)
.Name = r.Cells(i, 1)
.Nodes.SetPosition 2, .Left + .Width, .Top
.Nodes.SetPosition 4, .Left + .Width, .Top + .Height
First argument is the node index. Next is the x position, which we want all the way to the right of the graphic, so we add the shapes position left to the width of the shape. Last is the y position, first point we want in the topmost corner, so we use the shapes top. Last point, we add the height to the top position to bring to the bottom corner.
I believe it would be more simple drawing this as free form using Shapes.BuildFreeform Method and then converting to shape using FreeformBuilder.ConvertToShape Method.
Example:
Sub drawEntryArrow()
Dim x1 As Single, y1 As Single, w As Single, h As Single
Dim oShape As Shape
x1 = 10
y1 = 10
w = 200
h = 200
With ActiveSheet.Shapes.BuildFreeform(msoEditingAuto, x1, y1)
.AddNodes msoSegmentLine, msoEditingAuto, x1 + w, y1
.AddNodes msoSegmentLine, msoEditingAuto, x1 + w, y1 + h
.AddNodes msoSegmentLine, msoEditingAuto, x1, y1 + h
.AddNodes msoSegmentLine, msoEditingAuto, x1 + w / 2, y1 + h / 2
.AddNodes msoSegmentLine, msoEditingAuto, x1, y1
Set oShape = .ConvertToShape
End With
End Sub
If you just want to get rid of the point at the right, you can simply delete the node (nodes of a chevron are counted clockwise starting at the top left):
.Nodes.Delete 3
To get access to all nodes with the nodes-property of a shape, however, as long as you deal with a standard shape type, you can't access the coordinates.
When you use the "edit points", a shape changes its type to msoShapeNotPrimitive - but I couldn't figure out how to do this using VBA.
UPDATE
Played around a bit (because I'm curious) - just as an example if someone wants to change a shape manually:
' First change Shape Type:
' WILL NOT WORK: sh.AutoShapeType = msoShapeNotPrimitive
' Instead, add a node and remove it immediately. This changes the shape type.
.Nodes.Insert c, msoSegmentLine, msoEditingCorner, 100, 100
.Nodes.Delete c + 1
' Now access the x-coordinate of node 2 and the y-coordinate of node 3
' (note that we cannot access the coordinates directly)
Dim pointsArray() As Single, x As Single, y As Single
pointsArray = .Nodes(2).Points
x = pointsArray(1, 1)
pointsArray = .Nodes(3).Points
y = pointsArray(1, 2)
' Now change the x-value of node 3
sh.Nodes.SetPosition 3, x, y
Related
Background: I am the developer of IguanaTex, a Powerpoint add-in to include LaTeX displays in Powerpoint. IguanaTex can generate vector graphics displays (Powerpoint Shapes, typically Freeforms) by inserting EMF files into a slide, ungrouping them, and doing some clean up (removing extra shapes, further ungrouping, removing lines, ...). These EMF files are typically generated using an external engine (Tex2img) either from LaTeX or from a PDF file that a user wants to convert into an editable shape (not really related to LaTeX, but the whole code base is there to offer that feature, so I put it in).
Issue: I have recently noticed sporadic issues when programmatically ungrouping EMF files, while ungrouping the same file via the GUI does not lead to errors. I have confirmed this occurs on two Windows 10 machines running either Office 2010, Office 2016, or Office 365.
Let's say we insert this EMF file and obtain the following Picture object in Powerpoint:
Inserting the same file using IguanaTex's VBA code leads to the following distorted output, where the "a" and "s" letters are vertically elongated:
The VBA code essentially:
Adds the EMF file as a shape using the Shapes.AddPicture method
Ungroups the shape using the Shape.Ungroup method into a ShapeRange (equivalent to Ungrouping an inserted EMF file in the GUI)
Cleans up by doing one more Ungroup, removing the extra shapes (in our case 1 Autoshape and 1 Rectangle), selecting the group (or Freeform if there is only one) that's at the top, removing the remaining Rectangle, and setting each shape's Outline to be invisible.
Running the code in Debug mode, I could pinpoint the distortion occurring at the first Shape.Ungroup step, which should again in theory be equivalent to doing Shift+Ctrl+G in the GUI (and pressing Yes, as the GUI asks for confirmation when ungrouping EMF files). Note that the distortion still happens when I step over the Ungrouping line.
What is particularly frustrating with this bug, is that if I place in a macro essentially the exact same VBA code that handles Steps 2 and 3 above (everything except inserting the file), then stops the add-in code after the file insertion in Step 1 and run the rest using the macro, that usually doesn't lead to any distortion. I say usually, because this bug is not 100% reproducible: it will sometimes occur, and sometimes it won't. The most reliable way that I found to reproduce it has been to insert the EMF file linked above.
So there doesn't seem to be a particular issue with the code itself, but with the way Powerpoint runs it. Could there be some race condition? Note that I have also noticed that IguanaTex sometimes raises an error in random locations when grouping/ungrouping shapes, and re-running generally solves the issue, which could also point at some race condition. That however seems unlikely here because the distortion issue still occurs when stepping over the code in debug mode.
My questions are thus: does anyone have a clue what is going on, and how can I fix this?
Below is the macro mentioned earlier:
Public Sub Emftoshape()
Dim ConvertLines As Boolean
ConvertLines = False
Dim Sel As Selection
Set Sel = Application.ActiveWindow.Selection
' Get current slide, it will be used to group ranges
Dim sld As Slide
Dim SlideIndex As Long
SlideIndex = ActiveWindow.View.Slide.SlideIndex
Set sld = ActivePresentation.Slides(SlideIndex)
Dim shp As Shape
Set shp = Sel.ShapeRange(1)
' Convert EMF image to object
Dim Shr As ShapeRange
Set Shr = shp.Ungroup
Set Shr = Shr.Ungroup
' Clean up
Shr.Item(1).Delete
Shr.Item(2).Delete
Dim newShape As Shape
If Shr(3).GroupItems.count > 2 Then
Set newShape = Shr(3)
Else ' only a single freeform, so not a group
Set newShape = Shr(3).GroupItems(2)
End If
Shr(3).GroupItems(1).Delete
If newShape.Type = msoGroup Then
Dim arr_group() As Variant
arr_group = GetAllShapesInGroup(newShape)
Call FullyUngroupShape(newShape)
Set newShape = sld.Shapes.Range(arr_group).Group
Dim emf_arr() As Variant ' gather all shapes to be regrouped later on
j_emf = 0
Dim delete_arr() As Variant ' gather all shapes to be deleted later on
j_delete = 0
Dim s As Shape
For Each s In newShape.GroupItems
j_emf = j_emf + 1
ReDim Preserve emf_arr(1 To j_emf)
If s.Type = msoLine Then
If ConvertLines And (s.Height > 0 Or s.Width > 0) Then
emf_arr(j_emf) = LineToFreeform(s).name
j_delete = j_delete + 1
ReDim Preserve delete_arr(1 To j_delete)
delete_arr(j_delete) = s.name
Else
emf_arr(j_emf) = s.name
End If
Else
emf_arr(j_emf) = s.name
If s.Fill.Visible = msoTrue Then
s.Line.Visible = msoFalse
Else
s.Line.Visible = msoTrue
End If
End If
Next
newShape.Ungroup
If j_delete > 0 Then
sld.Shapes.Range(delete_arr).Delete
End If
Set newShape = sld.Shapes.Range(emf_arr).Group
Else
If newShape.Type = msoLine Then
newShapeName = LineToFreeform(newShape).name
newShape.Delete
Set newShape = sld.Shapes(newShapeName)
Else
newShape.Line.Visible = msoFalse
End If
End If
newShape.LockAspectRatio = msoTrue
End Sub
Private Sub FullyUngroupShape(newShape As Shape)
Dim Shr As ShapeRange
Dim s As Shape
If newShape.Type = msoGroup Then
Set Shr = newShape.Ungroup
For i = 1 To Shr.count
Set s = Shr.Item(i)
If s.Type = msoGroup Then
Call FullyUngroupShape(s)
End If
Next
End If
End Sub
Private Function GetAllShapesInGroup(newShape As Shape) As Variant
Dim arr() As Variant
Dim j As Long
Dim s As Shape
For Each s In newShape.GroupItems
j = j + 1
ReDim Preserve arr(1 To j)
arr(j) = s.name
Next
GetAllShapesInGroup = arr
End Function
Private Function LineToFreeform(s As Shape) As Shape
t = s.Line.Weight
Dim ApplyTransform As Boolean
ApplyTransform = True
Dim bHflip As Boolean
Dim bVflip As Boolean
Dim nBegin As Long
Dim nEnd As Long
Dim aC(1 To 4, 1 To 2) As Double
With s
aC(1, 1) = .Left: aC(1, 2) = .Top
aC(2, 1) = .Left + .Width: aC(2, 2) = .Top
aC(3, 1) = .Left: aC(3, 2) = .Top + .Height
aC(4, 1) = .Left + .Width: aC(4, 2) = .Top + .Height
bHflip = .HorizontalFlip
bVflip = .VerticalFlip
End With
If bHflip = bVflip Then
If bVflip = False Then
' down to right -- South-East
nBegin = 1: nEnd = 4
Else
' up to left -- North-West
nBegin = 4: nEnd = 1
End If
ElseIf bHflip = False Then
' up to right -- North-East
nBegin = 3: nEnd = 2
Else
' down to left -- South-West
nBegin = 2: nEnd = 3
End If
xs = aC(nBegin, 1)
ys = aC(nBegin, 2)
xe = aC(nEnd, 1)
ye = aC(nEnd, 2)
' Get unit vector in orthogonal direction
xd = xe - xs
yd = ye - ys
s_length = Sqr(xd * xd + yd * yd)
If s_length > 0 Then
n_x = -yd / s_length
n_y = xd / s_length
Else
n_x = 0
n_y = 0
End If
x1 = xs + n_x * t / 2
y1 = ys + n_y * t / 2
x2 = xe + n_x * t / 2
y2 = ye + n_y * t / 2
x3 = xe - n_x * t / 2
y3 = ye - n_y * t / 2
x4 = xs - n_x * t / 2
y4 = ys - n_y * t / 2
'End If
If ApplyTransform Then
Dim builder As FreeformBuilder
Set builder = ActiveWindow.Selection.SlideRange(1).Shapes.BuildFreeform(msoEditingCorner, x1, y1)
builder.AddNodes msoSegmentLine, msoEditingAuto, x2, y2
builder.AddNodes msoSegmentLine, msoEditingAuto, x3, y3
builder.AddNodes msoSegmentLine, msoEditingAuto, x4, y4
builder.AddNodes msoSegmentLine, msoEditingAuto, x1, y1
Dim oSh As Shape
Set oSh = builder.ConvertToShape
oSh.Fill.ForeColor = s.Line.ForeColor
oSh.Fill.Visible = msoTrue
oSh.Line.Visible = msoFalse
oSh.Rotation = s.Rotation
Set LineToFreeform = oSh
Else
Set LineToFreeform = s
End If
End Function
Edit:
Here is a visual comparison between several ways to insert the EMF file linked above or a modified version of it, where colors are added for illustration:
The EMF file cleaned by John Korchok to remove a clipping mask and a rectangle, and ungrouped with the GUI. Apart from being distorted (the curves are not smooth, and the "a" and "s" are taller than in the original file), the file indeed behaves the same when ungrouping with VBA of with the GUI. That's unfortunately not a viable solution for my problem.
The EMF file ungrouped using VBA (rectangles/autoshapes are normally removed by IguanaTex). "a" and "s" are clearly taller, as can be seen thanks to the horizontal line added as reference.
The EMF file ungrouped with the GUI. This is the desired outcome.
The corresponding PNG file (obtained by converting from PDF using Ghostscript) whose aspect ratio was modified to match the size of the inserted EMF file. Because I trust the PDF/PNG output more, IguanaTex has an option to "vectorize" a PNG display which resizes the ungrouped EMF to match the PNG's size.
When you get variable and unpredictable results, it makes it likely that it's some property of the source file causing the issue. I opened it in both Adobe Illustrator and InkScape. Your sample file has problems:
The text size is really small, about 2.5 points. This means even slight errors will have large visual results.
The top of the k is definitely clipped by the edge of the EMF. I believe the m may be clipped on the left, but the image is so small I can't zoom in enough to see. Since those are the two letters that get resized, that may be a source of the problem.
Your EMF also includes a rectangle that is 3.91" wide and 1.06" tall, enormous by comparison with the tiny text. The upper left corner of this rectangle is at the same position as the rectangle masking the text.
I think it likely that if you test with more real-world files, you'll get better results.
I have two grouped set of shapes I want to link.
I select fine with:
ActiveSheet.Shapes.Range(Array(type_of_milestone).Select
and can change individual properties etc with
'Activewindow.selection.shaperange.groupitems(1).TextFrame.Characters.Text = name_of_milestone.
But how do I link these with an arrowed connector? Specifically I want to link the middle of the shape group on the left hand side to the right hand side?
Not sure how to reference a group of shapes as a shape - if this is even possible and directly passing a group of shapes throws an error with
conn.ConnectorFormat.BeginConnect start_milestone, 1
conn.ConnectorFormat.EndConnect finish_milestone, 1
conn.RerouteConnections
Any ideas?
The macro picks from a series of predrawn groups which are squares, circles and triangles with text beneath, copies it, pastes it, dumps it onto a new sheet, renames it, then I just want to link one to the next. Yes, it would probably be better in visio.
I'm not sure you can draw connectors from groups. Instead I'd like to suggest an alternative solution:
Add invisible shapes where you need them in your shape groups and connect between them.
This is a quick hack to show the concept:
CreateHandles ActiveSheet.Shapes.Range(Array("Group 27"))
Sub CreateHandles(ShapeGroup)
Dim MaxX As Long
Dim MinX As Long
Dim MaxY As Long
Dim MinY As Long
Dim Shp As Shape
Dim LeftHandle As Shape
Dim RightHandle As Shape
MaxY = -1000000
MinY = 1000000
MaxX = -1000000
MinX = 1000000
For Each Shp In ShapeGroup
If Shp.Top + Shp.Height > MaxY Then
MaxY = Shp.Top + Shp.Height
End If
If Shp.Top < MinY Then
MinY = Shp.Top
End If
If Shp.Left + Shp.Width > MaxX Then
MaxX = Shp.Left + Shp.Width
End If
If Shp.Left < MinX Then
MinX = Shp.Left
End If
Next
Set LeftHandle = ActiveSheet.Shapes.AddShape(msoShapeRectangle, MinX, (MinY + MaxY) / 2, 10, 10) ' change the 10 to 0 once it works
Set RightHandle = ActiveSheet.Shapes.AddShape(msoShapeRectangle, MaxX, (MinY + MaxY) / 2, 10, 10) ' change the 10 to 0 once it works
ActiveSheet.Shapes.Range(Array(ShapeGroup.Name, LeftHandle.Name, RightHandle.Name)).Select
Selection.ShapeRange.Group.Select
End Sub
I made the handles bigger here to show the concept. Set the size to 0 once it works. Second thing to fix is the ugly grouping thing at the bottom.
So to answer my question, you cannot connect groups, only shapes.
I had a list of milestones I incrementally look through.
The trick was to name a shape in a group with a unique identifier.
ActiveWindow.Selection.ShapeRange.GroupItems(2).Name = id_of_milestone
Then after I'd drawn all the milestones, run the below to link (The From milestone links horizontally from right side to To milestone on the left)
for i=1 to number_of_milestones
Current_inc = i + 3 'because of row headers
id_of_milestone = Sheets("Milestones").Range("P" & current_inc).Value 'assign the from value
lookup_milestone = Sheets("Milestones").Range("P" & current_inc).Value 'assign the to value
If lookup_milestone = "" Then 'check to see there actually is a link present, because it can be blank
'do nothing
Else
ActiveSheet.Shapes.AddConnector(msoConnectorElbow, 1,1,1,1).Select
Selection.ShapeRange.ConnectorFormat.BeginConnect ActiveSheet.Shapes(id_of_milestone), 4
Selection.ShapeRange.ConnectorFormat.BeginConnect ActiveSheet.Shapes(lookup_milestone), 2
End If
I'm trying to figure out how to code a straight line to the straight part of a curve, the curve should look something like the exponential, click the link to open the image:
Straight line to Curve and determining the x-intercept
Here is the code, I'm only using the exponential as an example
`
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim s As String
Dim xl As New Excel.Application
Dim wb As Excel.Workbook
Dim ws As Excel.Worksheet
wb = xl.Workbooks.Add 'create new workbook
ws = wb.Worksheets(1) 'select sheet 1
ws.Activate()
Dim Currents() As Double
Dim PhotodiodeValues() As Double
Dim AppliedCurrent As Double
'AppliedCurrent = SerialPort1.ReadLine
AppliedCurrent = 0
If AppliedCurrent >= 0 And AppliedCurrent < 0.1 Then
Dim j As Integer = 1
For i As Double = 0 To 5 Step 0.5
ReDim Preserve Currents(j)
ReDim Preserve PhotodiodeValues(j)
MsgBox(i)
MsgBox("LDI " & CType(i, String))
s = ("LDI " & CType(i, String))
AppliedCurrent = i
If AppliedCurrent >= i And AppliedCurrent < (i + 0.1) Then
Currents(j) = CType(i, Double)
Label1.Text = Currents(j)
PhotodiodeValues(j) = CType(Math.E ^ (i), Double)
ws.Cells(j, 1) = Currents(j)
ws.Cells(j, 2) = PhotodiodeValues(j)
Else
System.Threading.Thread.Sleep(1000)
End If
j = j + 1
Next
Else
System.Threading.Thread.Sleep(1000)
End If
sfd1.ShowDialog() 'get file name
wb.SaveAs(sfd1.FileName) 'save data to file
wb.Close()
xl = Nothing 'dispose of excel
ScatterGraph1.PlotXY(Currents, PhotodiodeValues)
'SerialPort1.Close()
End Sub
End Class`
First off, I'll explain my thought process. If I have misunderstood, please let me know and I will update my answer. The slope dy/dx of the curve y = e^x is dy/dx = e^x, a monotonically increasing function of x for all real x. There is never a point at which the function becomes linear and, while it has a horizontal asymptote (y = 0) it has no vertical asymptote.
I take it that what you want is the equation of a tangent line taken at a point where the slope first becomes greater than some cutoff value m*. After that point, the graph of y = e^x "might as well" be a straight line for your intents and purposes.
So, we must first solve the equation m* = dy/dx = e^x for the x at which m* occurs. The range of e^x is all positive real numbers and e^x is monotonically increasing, so any positive real number m* will have a unique solution x*. indeed, x* = ln(m*). Our tangent line will pass through the point (x*, e^x*) and have slope m*. Recall that m* = e^x*, so the point is (ln(m*), m*) and the slope is m*.
With a point and the slope, we can figure out the equation of a line. We have that the slope from the given point to any other point must be m*; so, (y - y*)/(x - x*) = m*. Rearranging, (y - y*) = m*(x - x*), y = mx - mx* + y*, and finally y = mx + (y - mx) = mx + (m - mln(m)). The Y-intercept is therefore (m* - mln(m)). We can get the X-intercept by setting y = 0 and solving for x: 0 = mx + (m - mln(m)), mx = mln(m*) - m*, x = ln(m*) - 1.
In summary:
the equation of the line tangent to y = e^x with slope m* is y = mx + (m - mln(m)).
the Y-intercept of this line is (m* - mln(m)).
the X-intercept of this line is ln(m*) - 1
If the curve is known at compile time, I recommend hard-coding a closed form analytical solution for the derivative and any asymptotes. If the function is not known until runtime, the derivative at a given point can be approximated numerically using a variety of methods. Intuitively, the definition of the derivative as the limit of (f(x+d) - f(x)) / d as d approaches zero can be used to find approximations of the derivative where the derivative (likely) exists. For well-behaved analytic functions, you will typically be safe except in special cases.
If the function's derivative is monotonically non-decreasing, as in this example, you can find the point (if any) at which the function's slope meets or exceeds a certain cutoff using approximation (as above) in conjunction with something akin to binary search. Start at a value such as x = 0, and increase or decrease x by some multiplicatively increasing factor until you have passed your target. Now, with bounds on the values of x between which your target can be found, check the middle of the range, and then either the left or right half recursively until a suitably good x* is found.
I have a group of shapes and I can currently move it to the top left edge of a reference cell however I need the middle of the shape to line up with this left edge. There is only "Top" and "Left" parameters and no "Middle" is there any way of doing this?
I could move to left edge then increment by x amount based on the column width but this seems long winded?
Sub Macro4()
Dim x
Set x = Range("A2")
Sheet1.Shapes("Group 9").Left = Sheet1.Cells(13, x).Left
Sheet1.Shapes("Group 9").Top = Sheet1.Cells(13, x).Top
End Sub
UPDATE:
My code is now as follows:
With Sheet1
.Shapes("Group 9").Top = rng.Top
.Shapes("Group 9").Left = rng.Left - (.Shapes("Group 9").Width / 2) + (rng.Width / 2)
End With
Which achieves the following
However I need to achieve this:
Whereby the inputed date in red means the shape centre aligns to the right edge of the cell in row 11 which matches the date in this case R11
This will center your shape on the range defined, modify as appropriate.
Dim rng As Range
Set rng = Range("D15")
With Sheet1
.Shapes("Group 9").Left = rng.Left - (.Shapes("Group 9").Width / 2) + (rng.Width / 2)
.Shapes("Group 9").Top = rng.Top - (.Shapes("Group 9").Height / 2) + (rng.Height / 2)
End With
I want to get the angle of two points on a Line chart.
I know how to calculate an angle, the problem is that I need the x and y of the seriescollection.point and I have no idea how to get it.
Can someone help me with it?
EDIT:
Jean-François Corbett showed me how to get the points, I meant from top and left, and not point on the graph (on X scale and Y scale) though it can work.
I calculate it wrong. how can I calculate the angles in the picture below?
You ask how to get the (x,y) coordinates of points in a chart series. Here is how:
Dim c As Chart
Dim s As Series
Dim x As Variant
Dim y As Variant
Set c = ActiveChart
Set s = c.SeriesCollection.Item(1)
x = s.XValues
y = s.Values
EDIT As far as I can tell from the edited question, OP now wants the pixel coordinates of each point, with origin at the top left of the plot. To do so, you just need to scale by the axis width and span. The x axis is a bit tricky in the case of line plots (which I hate), because there is no min or max scale property; have to use the number of "categories" instead. The following code does this scaling:
Dim c As Chart
Dim s As Series
Dim xa As Axis
Dim ya As Axis
Dim x As Variant
Dim y As Variant
Dim i As Long
Set c = ActiveChart
Set s = c.SeriesCollection.Item(1)
Set xa = c.Axes(xlCategory)
Set ya = c.Axes(xlValue)
x = s.XValues
y = s.Values
For i = LBound(x) To UBound(x)
' Scale x by number of categories, equal to UBound(x) - LBound(x) + 1
x(i) = (i - LBound(x) + 0.5) / (UBound(x) - LBound(x) + 1) * xa.Width
' Scale y by axis span
y(i) = ya.Height - y(i) / (ya.MaximumScale - ya.MinimumScale) * ya.Height
Next i
Note that y increases along the negative y direction on the plot, since you want the origin to be at the top left.
Using this x and y, you can calculate your angle as seen on the screen.
The X and Y values are not directly accessible from the Point object, (as best as I can tell), but they represent actual values passed to the graph. Try accessing them from the worksheet where they are stored.
If that is unavailable, try Series.values, which returns an array of Y-values, and Series.XValues, which returns an array of X-values. (See MSDN Reference)