I have a chart sheet with 2 axis, I need to extract the maximum value of the primary axis to set the secondary one accurately. I cant seem to get the function working that will simply read the primary axis max and place it in a cell, any ideas?
This will return the max value of the left axis which is usually <> max value of original data
Public Function getAxisMaxScale(cht As Chart) As Single
Dim ax As Axis
Set ax = cht.Axes(xlValue)
getAxisMaxScale= ax.MaximumScale
End Function
To adjust the secondary axes you can use
Public sub adjustAxisForChartXYZ()
Dim cht As Chart
Set cht = ThisWorkbook.Sheets("XYZ") 'insert sheetname of your chart
adjustSecondaryAxis cht
End Sub
Public Sub adjustSecondaryAxis(cht As Chart)
Dim axPrimary As Axis, axSecondary As Axis
Set axPrimary = cht.Axes(xlValue)
Set axSecondary = cht.Axes(xlValue, xlSecondary)
With axSecondary
.MajorUnit = axPrimary.MajorUnit
.MaximumScale = axPrimary.MaximumScale
End With
End Sub
I adjust the MajorUnit as well ... to be aligned as well
This is how you get the min/max of the two series in a chart:
have chart selected and then run the following
Dim ser as SeriesCollection
' `ChartArea.Parent` = `Chart` object
Set ser = Selection.Parent.SeriesCollection()
Dim temp1() As Variant, temp2() As Variant
temp1 = ser(1).values
temp2 = ser(2).values
' Get the data limits
Dim a_1 As Double, a_2 As Double, b_1 As Double, b_2 As Double
a_1 = WorksheetFunction.Min(temp1): a_2 = WorksheetFunction.Min(temp2)
b_1 = WorksheetFunction.Max(temp1): b_2 = WorksheetFunction.Max(temp2)
This is part of a macro I developed to scale the secondary axis of chart such as the tickmarks fall on the grid of the first axis.
Before
Notice how on the secondary axis the grid for 10 through 60 is not on the gridlines shown. This makes it hard to read the chart.
After
Afterward, the grid spacing on the secondary axis is adjusted in order to match the primary axis. Also, the limits are adjusted to fill the area better.
Code
Here is the code I am using to do this.
Public Sub DualYAxisChart(ByRef ch As Chart)
Dim x_axis_1 As Axis, x_axis_2 As Axis, n As Long
Dim ser As SeriesCollection
Set ser = ch.SeriesCollection()
If ser.Count <> 2 Then
MsgBox "Please select a chart with two series", vbOKOnly, "Dual y-axis Chart"
Exit Sub
End If
If UBound(ser(1).values) <> UBound(ser(2).values) Then
MsgBox "Both series must share the same number of points", vbOKOnly, "Dual y-axis Chart"
Exit Sub
End If
n = UBound(ser(1).values)
ser(1).AxisGroup = xlPrimary
ser(2).AxisGroup = xlSecondary
' Enable Both Y-axes
ch.SetElement msoElementPrimaryValueAxisShow
ch.SetElement msoElementSecondaryValueAxisShow
' Enable Both X-axes
ch.SetElement msoElementPrimaryCategoryAxisShow
ch.SetElement msoElementSecondaryCategoryAxisShow
' Delete Secondary x-axis
ch.SetElement msoElementSecondaryCategoryAxisNone
Set x_axis_1 = ch.Axes(xlValue, xlPrimary)
Set x_axis_2 = ch.Axes(xlValue, xlSecondary)
x_axis_1.format.Line.ForeColor.RGB = ser(1).format.Line.ForeColor.RGB
x_axis_1.format.Line.EndArrowheadStyle = msoArrowheadTriangle
x_axis_2.format.Line.ForeColor.RGB = ser(2).format.Line.ForeColor.RGB
x_axis_2.format.Line.EndArrowheadStyle = msoArrowheadTriangle
' Auto Scale All
x_axis_1.MajorUnitIsAuto = True
x_axis_1.MaximumScaleIsAuto = True
x_axis_1.MinimumScaleIsAuto = True
x_axis_2.MajorUnitIsAuto = True
x_axis_2.MaximumScaleIsAuto = True
x_axis_2.MinimumScaleIsAuto = True
Dim a_1 As Double, a_2 As Double, b_1 As Double, b_2 As Double
Dim x_1 As Double, s_1 As Double, x_2 As Double, s_2 As Double, g_1 As Double, g_2 As Double
Dim n_1 As Long, n_2 As Long, sense As Long
' Get the axis limits
s_1 = x_axis_1.MinimumScale
x_1 = x_axis_1.MaximumScale
g_1 = x_axis_1.MajorUnit
n_1 = CLng((x_1 - s_1) / g_1)
s_2 = x_axis_2.MinimumScale
x_2 = x_axis_2.MaximumScale
g_2 = x_axis_2.MajorUnit
n_2 = CLng((x_2 - s_2) / g_2)
Dim temp1() As Variant, temp2() As Variant
temp1 = ser(1).values
temp2 = ser(2).values
' Get the data limits
a_1 = WorksheetFunction.Min(temp1): a_2 = WorksheetFunction.Min(temp2)
b_1 = WorksheetFunction.Max(temp1): b_2 = WorksheetFunction.Max(temp2)
sense = Sgn((b_2 - a_2) / (b_1 - a_1))
If sense < 0 Then
x_axis_2.ReversePlotOrder = True
Swap a_2, b_2
x_axis_2.format.Line.EndArrowheadStyle = msoArrowheadNone
x_axis_2.format.Line.BeginArrowheadStyle = msoArrowheadTriangle
End If
x_axis_1.MinimumScale = a_1: x_axis_1.MaximumScale = b_1
g_1 = x_axis_1.MajorUnit
n_1 = CLng((x_1 - s_1) / g_1)
x_axis_2.MinimumScale = a_2: x_axis_2.MaximumScale = b_2
g_2 = x_axis_2.MajorUnit
n_2 = CLng((x_2 - s_2) / g_2)
g_2 = (x_2 - s_2) / n_1
x_axis_2.MajorUnit = g_2
Dim i_1 As Long, i_2 As Long
i_1 = WorksheetFunction.Floor_Math(a_1 / g_1)
s_1 = i_1 * g_1
i_2 = WorksheetFunction.Floor_Math(a_2 / g_2)
s_2 = i_2 * g_2
Dim j_1 As Long, j_2 As Long
j_1 = WorksheetFunction.Ceiling_Math(b_1 / g_1)
x_1 = j_1 * g_1
j_2 = WorksheetFunction.Ceiling_Math(b_2 / g_2)
x_2 = j_2 * g_2
x_axis_1.MinimumScale = s_1: x_axis_1.MaximumScale = x_1
x_axis_2.MinimumScale = s_2: x_axis_2.MaximumScale = x_2
x_axis_1.MajorUnitIsAuto = True
g_1 = x_axis_1.MajorUnit
n_1 = CLng((x_1 - s_1) / g_1)
g_2 = (x_2 - s_2) / n_1
x_axis_2.MajorUnit = g_2
End Sub
Welcome to the board. Have a read of ASK and MCVE.
Recording a macro while manually changing the axis will give you:
Sub Macro4()
'
' Macro4 Macro
'
'
ActiveSheet.ChartObjects("Chart 2").Activate
ActiveChart.Axes(xlValue).Select
ActiveChart.Axes(xlValue).MaximumScale = 60
ActiveChart.Axes(xlValue, xlSecondary).Select
ActiveChart.Axes(xlValue, xlSecondary).MaximumScale = 90
Range("L2").Select
End Sub
The code shows that you need to reference the chart within the chart object within the sheet to get to the axis.
Sub Test()
Sheet1.ChartObjects("Chart 2").Chart.Axes(xlValue, xlSecondary).MaximumScale = _
Sheet1.ChartObjects("Chart 2").Chart.Axes(xlValue).MaximumScale
End Sub
Note - this code can be shorted using variables and a With...End With block.
Related
Background
I'm receiving StdOut from python in and parsing the information so I have a set have N many (x,y) coordinates belonging to M many series. I would like to be able to plot each point individually and control for their respective properties I.e color, shape, marker size, etc.
However, all the examples I have seen so far involve having to plot all the values at once and then loop through the series. My desired functionality would look like the below pseudocode.
.SeriesCollection(seriesNumber).AddPoint(PtNum).xvalues = x
.SeriesCollection(seriesNumber).AddPoint(PtNum).values = y
.SeriesCollection(seriesNumber).Point(PtNum).markersize = some integer
Further Details
The data I'm receiving from python follows this format, stored as a variant.
SeriesName, Point# in the series, X or O, SeriesNumber, xcoord,ycoord, instance number
O represents there needs to be a new series, X represents an already existing series
instance number is the number of times a point was repeated
Point# Is the point index of a coordinate in the series
Example lines of data
series1,1,O,1,0.25,64,1
series1,2,X,1,0.25,64,2
series2,1,O,2,0.3,90,1
Current Code
Dim pyData() As Variant
pyData = Connect_2py.recv_Data(xArgs, yArgs, seriesArgs) 'exec python script and returns data
For i = 0 To UBound(pyData) - 1
py_lineData = Split(pyData(i), ",")
createSeries = StrComp(py_lineData(2), "O", vbBinaryCompare) = 0
seriesName = py_lineData(0)
seriesNumber = CInt(py_lineData(3))
If createSeries Then
.SeriesCollection.NewSeries
.SeriesCollection(seriesNumber).Name = seriesName
Debug.Print ("Hooray new series has been made")
End If
'ENTER CODE HERE TO ADD EACH DATAPOINT TO A SPECIFIED SERIES
Next
OK so it does seem to be do-able. I was surprised that extending a series' point arrays didn't lose the existing marker properties.
Sub Tester()
Const N = 10
Dim i As Long, x(), y(), sz(), cht As Chart, ser As Series
ReDim x(1 To N)
ReDim y(1 To N)
ReDim sz(1 To N)
'create some initial data
For i = 1 To 10
x(i) = i
y(i) = i ^ 1.5
sz(i) = 2 + (Rnd() * 10)
Next i
'create a series, populate with the initial data
' and set some marker sizes
Set cht = ActiveSheet.ChartObjects(1).Chart
Set ser = cht.SeriesCollection.NewSeries()
With ser
.XValues = x
.Values = y
For i = 1 To 10
.Points(i).MarkerSize = sz(i)
Next i
End With
'extend that series one point at a time
For i = 1 To 5
ExtendSeries ser, i * 10, i * 10, i * 5
Next i
End Sub
'Extend the data in a series by adding a new X/Y pair
'Also shows how to configure (eg) the added point size
Sub ExtendSeries(ser As Series, xVal, yVal, sz)
Dim x, y, newlen As Long
With ser
x = .XValues
y = .Values
newlen = UBound(x) + 1
ReDim Preserve x(newlen)
x(newlen) = xVal
ReDim Preserve y(UBound(x) + 1)
y(newlen) = yVal
.XValues = x
.Values = y
'.Points(newlen).MarkerSize = sz 'edit: for some reason this doesn't work...
.Points(.Points.Count).MarkerSize = sz '...but this does
End With
End Sub
I have the following code which attempts to add a datalabel to a point in a combined donut/pie-chart:
For Each co In .ChartObjects
With co.Chart.FullSeriesCollection("Grøn pil").Points(2)
.HasDataLabel = True
With .DataLabel
.Position = xlLabelPositionOutsideEnd
.Format.AutoShapeType = msoShapeRectangle
.Format.Line.Visible = msoTrue
End With
End With
Next co
However, the code aborts on the line .Position = xlLabelPositionOutsideEnd with the error message "Run-time error 2147467259 (80004005)". Method 'Position' of object 'DataLabel' failed".
Looking at the chart, the label has been added, but it is still positioned inside the chart.
As you can see I've already positioned a label outside the chart for a different series, which is represented as a pie chart. While the series I am trying to add the label to is represented as a donut-chart.
Can't I have both the labels for the donut- and pie-chart on the outside? Isn't xlLabelPositionOutsideEnd a valid position for labels of a donut-chart? Or is the problem something else which eludes me?
Any help would be greatly appreciated!
I don't think it's possible to do exactly you want to do the way you want to do it! The option to place the labels outside the chart is not available on the doughnut chart options:
like they do on a pie chart:
However, you could perform a trick using a pie chart and a white circle to make it look like a doughnut by doing the following:
Sub AddCircle()
'Get chart size and position:
Dim CH01 As Chart: Set CH01 = ThisWorkbook.Sheets("Sheet1").ChartObjects("Chart1").Chart
Dim OB01 As ChartObject: Set OB01 = CH01.Parent
Dim x As Double: x = 0 'horizontal coordinate
Dim y As Double: y = 0 'vertical coordinate
Dim w As Double: w = 0 'width
Dim h As Double: h = 0 'height
x = OB01.Left
y = OB01.Top
w = OB01.Width
h = OB01.Height
'Adding the circle:
ThisWorkbook.Sheets("Sheet1").Shapes.AddShape(msoShapeOval, x + w / 2 - 20, y + h / 2 - 20, 40, 40).Name = "Circle01"
'Formatting the circle:
With ThisWorkbook.Sheets("Sheet1").Shapes("Circle01")
.LINE.Visible = msoFalse
.Fill.ForeColor.RGB = RGB(255, 255, 255)
End With
End Sub
And it works very nicely:
Had some fun "solving" this one...
Working with sinus and cosinus we can also calculate the outside position of the label. Following a VB snippet, how this can be done:
Sub Macro1()
Dim cx
Dim cy
Dim x
Dim y
Dim radius
Dim angle
Dim new_radius
Dim new_x
Dim new_y
ActiveSheet.ChartObjects("Chart 1").Activate
ActiveSheet.ChartObjects("Chart 1").Select
cx = Selection.width / 2
cy = Selection.height / 2
For i = 1 To ActiveChart.FullSeriesCollection(1).Points.Count Step 1
ActiveChart.FullSeriesCollection(1).Points(i).DataLabel.Select
x = Selection.left + (Selection.width / 2)
y = Selection.top + (Selection.height / 2)
radius = Sqr(((x - cx) ^ 2) + ((y - cy) ^ 2))
angle = WorksheetFunction.Atan2(y - cy, x - cx)
new_radius = radius + 40
new_x = cx + (Sin(angle) * new_radius)
new_y = cy + (Cos(angle) * new_radius)
Selection.left = new_x - (Selection.width / 2)
Selection.top = new_y - (Selection.height / 2)
Next i
End Sub
I have a workbook and the following sheets
Dashboard, IImpactchart.
Dashboard, which have candidate name, influence reference and impact reference
Candidate | Impact | Influence
Which have values of
Candidate1, Impact value = 3, Influence value = 2
Candate 2, Impact value = 3, Influence value =2
In the chart, we need to display the corresponding row number in the coordinate of (3,2). Its plotting for only single candidate. If we have more candidate with same value, the data-points are overlapping one above the other. How can we shift the data-points separated by commas ?? or any other way.
Chart attached
Please click here to see the Chart output
Chart Required
Please click here to see the required chart
VBA used
Dim Counter As Integer, ChartName As String, xVals As String
Application.ScreenUpdating = False
Dim c As ChartObject
Set c = Sheets("IImpactchart").ChartObjects("Chart 1")
c.Activate
xVals = ActiveChart.SeriesCollection(1).Formula
xVals = Mid(xVals, InStr(InStr(xVals, ","), xVals, _
Mid(Left(xVals, InStr(xVals, "!") - 1), 9)))
xVals = Left(xVals, InStr(InStr(xVals, "!"), xVals, ",") - 1)
Do While Left(xVals, 1) = ","
xVals = Mid(xVals, 2)
Loop
For Counter = 1 To Range(xVals).Cells.Count
If (Range(xVals).Cells(Counter, 1).Offset(0, -1).Value = 0) Then
Exit Sub
End If
ActiveChart.SeriesCollection(1).Points(Counter).HasDataLabel = _
True
ActiveChart.SeriesCollection(1).Points(Counter).DataLabel.Text = Counter + 5
Next Counter
(Counter is to increment by 5 to get the correct row number) - its working
Now i need to solve the overlapping.
Help appreciated..
Thanks
Assuming that your current code works and that the only problem is the overlap, the code below should solve your problem.
This solution involves the use of an array named LabelArray that stores the point number of the first point to occupy the spot on the grid. Then, instead of creating a new label for the new points, it simply adds to the existing label of that first point.
Sub LabelsNoOverlap()
Dim Counter As Integer, ChartName As String, xVals As String, yVals As String
Application.ScreenUpdating = False
Dim c As ChartObject
Set c = Sheets("IImpactchart").ChartObjects("Chart 2")
c.Activate
'Find address of the X values
xVals = ActiveChart.SeriesCollection(1).Formula
xVals = Mid(xVals, InStr(InStr(xVals, ","), xVals, _
Mid(Left(xVals, InStr(xVals, "!") - 1), 9)))
xVals = Left(xVals, InStr(InStr(xVals, "!"), xVals, ",") - 1)
'Not sure why this loop from your code is useful, but let's leave it.
Do While Left(xVals, 1) = ","
xVals = Mid(xVals, 2)
Loop
'Find address of the Y values
yVals = ActiveChart.SeriesCollection(1).Formula
yVals = Mid(yVals, InStr(InStr(yVals, ","), yVals, _
Mid(Left(yVals, InStr(yVals, "!") - 1), 9)))
yVals = Right(yVals, Len(yVals) - InStr(yVals, ","))
yVals = Left(yVals, InStr(InStr(yVals, "!"), yVals, ",") - 1)
'Again, not sure why this loop from your code is useful, but let's leave it.
Do While Left(yVals, 1) = ","
yVals = Mid(yVals, 2)
Loop
Dim DimY As Long, DimX As Long
DimY = 10
DimX = 10
Dim LabelArray() As Long
ReDim LabelArray(1 To DimX, 1 To DimY)
Dim src As Series, pts As Points
Set src = ActiveChart.SeriesCollection(1)
Set pts = src.Points
'Clear labels
src.HasDataLabels = False
For Counter = 1 To Range(xVals).Cells.Count
If (Range(xVals).Cells(Counter, 1).Offset(0, -1).Value = 0) Then
Exit Sub
End If
Dim xCoord As Long, yCoord As Long
xCoord = Range(xVals).Cells(Counter, 1).Value2
yCoord = Range(yVals).Cells(Counter, 1).Value2
If LabelArray(xCoord, yCoord) = 0 Then 'No overlap
LabelArray(xCoord, yCoord) = Counter
pts(Counter).HasDataLabel = True
pts(Counter).DataLabel.Text = Counter + 5
Else 'Overlap
pts(LabelArray(xCoord, yCoord)).DataLabel.Text = _
pts(LabelArray(xCoord, yCoord)).DataLabel.Text & "," & Counter + 5
End If
Next Counter
Application.ScreenUpdating = True
End Sub
Note that the code would work as long as the values for your X and Y values are ranging from 1 to 10. You could also change the upper bound by changing the value of DimX and DimY.
Additionally, I should mention that this code has limitations:
In its current version, it cannot handle whole numbers equal or smaller than 0 for the X and Y values.
The method to parse the SERIES formula is not robust to the presence of certain characters such as a comma in the sheet name (yes, that's allowed for some reason).
The way the code is specified assumes that the data series are vertically orientated. Maybe, for a more general solution, you would have to test for the orientation of the data or you could implement something using src.XValues and src.Values (for Y values) which returns arrays instead of a range.
I am trying to get a specific value from an excel chart. This is the code which creates my chart (I created a reversed binomial distribution plot) :
Dim lim As String
Dim N As Long
N = Range("C4").Value
Dim x, s, p As Double
x = Range("C6") 'event number
s = Range("C5") 'sample size
Dim g() As Long
Dim h() As Double
Dim k() As Double
Dim prob() As Double
ReDim g(N)
ReDim prob(N)
ReDim h(N)
ReDim k(N)
For i = 1 To N
g(i) = i
h(i) = i / N
k(i) = 1 - h(i)
prob(i) = WorksheetFunction.BinomDist(x, s, h(i), False) * 100
End If
And here is chart:
I need the point where y is 0 on distribution curve second time.
At the end of your For Loop, you could check if prob(i) = 0 And Prob(i-1) > 0, and save the index of this point. It's "too" simple, yet if this is just for this kind of distribution, it do the job :
Dim targetIndex As Integer
For i = 1 To N
g(i) = i
h(i) = i / N
k(i) = 1 - h(i)
prob(i) = WorksheetFunction.BinomDist(x, s, h(i), False) * 100
If i > 1 Then 'check if this is not the first point
If prob(i) = 0 And prob(i-1) <> 0 Then targetIndex = i
End If
Next
'// Now your point is the couple (targetIndex, prob(targetIndex))
I have a chart embedded on a sheet (X, Y scatter). I also have a mouseover event on the chart which - when you hover over a datapoint - prints the selected value of the corresponding datapoint to a cell. This works with using the .GetChartElement method.
However, I'd also like to add a feature with you can add an arrow (or line) connecting a datapoint and a predefined, named cell. I also managed to do this but unfortunately my calculations aren't accurate enough because the datapoint end of the line is never at the point exactly but somewhere around it (See picture).
The way I did this:
Private Sub myChartClass_MouseMove(ByVal Button As Long, ByVal Shift As Long, ByVal X As Long, ByVal Y As Long)
Me.myChartClass.GetChartElement X, Y, ElementID, Arg1, Arg2
Set chrt = ActiveSheet.ChartObjects(1).Chart
Set ser = ActiveSheet.ChartObjects(1).Chart.SeriesCollection(1)
chart_data = ser.Values
chart_label = ser.XValues
YValue = chart_data(Arg2)
XValue = chart_label(Arg2)
'[Code to print corresponding values]
If addComment = True Then Call wks35.addComment(XValue, YValue)
This way I can either pass the coordinates (X, Y variables) and the actual values on the axes (XValue, YValue). Above, the latter is used.
Public Sub addComment( _
ByVal X As Double, _
ByVal Y As Double _
)
Dim chartObj As Chart
Dim chartWidth As Double
Dim chartHeight As Double
Dim l1 As Long, l2 As Long, r1 As Long, r2 As Long
With wks35
Set chartObj = .ChartObjects(1).Chart
chartWidth = chartObj.PlotArea.Width
chartHeight = chartObj.PlotArea.Height
Y = chartHeight - (chartHeight * ((Y - chartObj.Axes(xlValue).MinimumScale) _
/(chartObj.Axes(xlValue).MaximumScale - chartObj.Axes(xlValue).MinimumScale)))
X = chartWidth * ((X - chartObj.Axes(xlCategory).MinimumScale) / _
(chartObj.Axes(xlCategory).MaximumScale - chartObj.Axes(xlCategory).MinimumScale))
l1 = Range("Comment1").Left
l2 = Range("Comment1").Top
r1 = X + ActiveSheet.ChartObjects(1).Left + chartObj.PlotArea.InsideLeft
r2 = Y + ActiveSheet.ChartObjects(1).Top + chartObj.PlotArea.InsideTop
With ActiveSheet.Shapes.AddLine(l1, l2, r1, r2).Line
.ForeColor.RGB = RGB(255, 0, 0)
End With
End With
End Sub
So basically the idea was to have the values on the axes and by calculating its relative position considering the minimum and maximum values on the axis get the datapoint's absolute top and left value from the borders of the plotarea. Then adding the difference of the chart and the plotarea and then the charts top and left.
It may be long and difficult to follow, but I appreciate any help.
Whatever, I figured it out.
I used this
chartWidth = chartObj.PlotArea.InsideWidth
chartHeight = chartObj.PlotArea.InsideHeight
instead of this
chartWidth = chartObj.PlotArea.Width
chartHeight = chartObj.PlotArea.Height
It was almost there so I added some constants to the end (10 to left, 3 to top) the lines are at their place.