VBA Making chart from array - problem with missing values - excel

I'm making a line chart with markers. The data is being created in code into array and this array put into chart.
Problem is that there's missing values represented in array as EMPTY.
When plotting the two point that do exist are being connected with line.
The option is selected to plot gaps if cells is empty.
Serie formula shows EMPTY as #N/A.
XValues=={"11/28/2016","12/5/2016","12/12/2016","12/19/2016","12/26/2016","1/2/2017","1/9/2017","1/16/2017","1/23/2017","1/30/2017","2/6/2017","2/13/2017","2/20/2017","2/27/2017"}
Values ={125.15,93.875,#N/A,#N/A,#N/A,#N/A,42,125,48.5714285714285,137,127.285714285714,81.6428571428571,89.9375,69.5,65.6428571428571,75.5,47.1666666666666}
Tried replacing with 0, "", NaN, nothing works. I want to have a break in plotting line.
I have existing value then serie of missing values and then some value.
I noticed that if serie starts with missing values it's plotting fine with gap.
Otherwise not working.
For i = LBound(p_data, 1) + 1 To UBound(p_data, 1)
sSerieName = p_data(i, 0)
If sSerieName <> "" Then
Dim serie() As Variant
Dim w As Long
w = 0
For j = LBound(labels) To UBound(labels)
ReDim Preserve serie(j): serie(j) = p_data(i, j + 1)
Next j
If Not Len(Join(serie, "")) = 0 Then
On Error Resume Next
With p_chart.Chart.SeriesCollection.NewSeries
.XValues = labels
.Values = serie
.Name = sSerieName
End With
On Error GoTo 0
End If
End If
Next i

I don't think this is possible when setting the values etc by arrays. It seems to resolve Empty as N/A. I have tried the following solution, which would involve you keeping a tally of where these empty's happen. So for example, in this case, I have substituted the previous value for my empty, at point 3, and used the following
Values wanted are 10,20,EMPTY,40
With s.NewSeries
.XValues = Array("a", "b", "c", "d")
.Values = Array(10, 20, 20, 40)
.Points(3).Format.Line.Visible = msoFalse
End With
The full solution I tried is as follows
Sub x()
Dim s As SeriesCollection
Dim lc As Long
Dim aTest() As Variant
Dim serie As String
aTest = Array(15, 20, Empty, 40)
Set s = ActiveChart.SeriesCollection
For lc = 0 To UBound(aTest)
If aTest(lc) = "" Then ' <--- record these lc values to hide points
If lc = 0 Then
aTest(lc) = 0
Else
aTest(lc) = aTest(lc - 1)
End If
Else
End If
Next lc
With s.NewSeries
.XValues = Array("a", "b", "c", "d")
.Values = aTest
.Points(3).Format.Line.Visible = msoFalse
End With
End Sub

Related

Plot XY-Scatter diagram with Excel-VBA using arrays and change horizontal (category) Axis Label

I am relatively new to Excel VBA and I am trying to learn how to plot charts with VBA script. I have the following data table (Figure1):
From this data table I would like to plot a graph (Figure2) as follows. I have plotted the chart with Excel's inbuilt function using line with markers chart type:
I have written a simple code in VBA to plot a similar type of chart, but with arrays instead of directly selecting the range from the sheet. My VBA code is as follows:
Sub plot_test2()
Dim ws2 As Worksheet
Dim i, j, c, m, n, a, lrow As Long
Dim frist_code, frist_value, frist_name As Variant
Dim xychart As Chart
Set ws2 = Worksheets("Sheet2")
i = 1: j = 1: a = 1
c = 4: lrow = 6: m = 2: n = 1
ReDim frist_code(i To lrow - 1, j To c)
ReDim frist_value(i To lrow - 1, j To c)
ReDim frist_name(i To lrow - 1, j To c)
For i = 1 To lrow - 1
For j = 1 To c
frist_value(i, j) = ws2.Cells(m, n)
frist_code(i, j) = j
frist_name(i, j) = ws2.Cells(1, n)
n = n + 1
Next j
n = 1
m = m + 1
Next i
Set xychart = ws2.Shapes.AddChart2(332, xlXYScatter, Left:=0, Top:=0, Width:=400, Height:=300).Chart
For i = 1 To lrow - 1
For j = 1 To c
xychart.SeriesCollection.NewSeries
With xychart.SeriesCollection(a)
.name = frist_name(i, j) 'series names are assigned by frist_name array
.Values = frist_value(i, j) 'series values are assigned by frist_value array
.XValues = frist_code(i, j) 'series XValues are assigned by frist_code array
.MarkerSize = 15
End With
a = a + 1
Next j
j = 1
Next i
xychart.Axes(xlCategory).TickLabelPosition = xlLow
End Sub
When I run my macro, I get the following plot (Figure3):
I wish to rename the horizontal axis labels to A, B, C, D (just like figure 2) instead of 1,2,3,4 using in the VBA macro using arrays. I have been trying to solve this problem, but I could not find the right solutions elsewhere.
I have also tried to plot the chart using xlLineMarkers instead of xlXYScatter Chart type. But the points were plotted on the same line.
I have also tried to use xychart.Axes(xlCategory).CategoryNames = Array ("A", "B", "C", "D"). But there is a compilation error.
Please let me know if anyone is able to figure out the solution or mistake is my VBA code. Thanks a ton!
To use text labels, it can't be an XY scatter chart.
What you have is line chart type data. You need to plot as a line chart, plotted by row, lines with markers, then hide the lines. Also I made the markers larger since that's how it looked in your chart.
This is a quickie I recorded and modified:
Sub Macro1()
ActiveSheet.Shapes.AddChart2(332, xlLineMarkers).Select
ActiveChart.SetSourceData Source:=Range("Sheet1!$A$1:$D$6"), PlotBy:=xlRows
Dim srs As Series
For Each srs In ActiveChart.SeriesCollection
srs.Format.Line.Visible = msoFalse
srs.MarkerSize = 8
Next
End Sub

Add a single data point to a series and specify its properties in VBA

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

How to plot data imported from a csv?

I am trying to import CSV output data from a simulation into an Excel sheet to plot the data in a regular XY scatter chart.
I managed everything except the end result. The data is imported from csv to Excel, points are replaced by commas as a decimal separator.
It is plotting a straight horizontal line at 0 y-coordinates.
I noticed that the values in Excel are stored as texts, although I specified the format for numerical contents of csv as numbers like following:
DataSheet.Cells(Row, col).NumberFormat = "0.E+00"
When I test Isnumeric(cell.value), it turns out positive.
My complete code:
CsvFile = Application.GetOpenFilename()
Set DataSheet = Worksheets("CSV_Plot")
nrow = 10 'data starts at 10th row
Open CsvFile For Input As #1
Do Until EOF(1)
Line Input #1, CsvLine
CsvItem = Split(CsvLine, ",")
If Not CsvItem(0) <> "" Then GoTo 10 'ignores first line
ncol = UBound(CsvItem) 'ncol = number of data columns
If IsNumeric(CsvItem(0)) Then
For i = 0 To ncol
CsvItem(i) = Replace(CsvItem(i), ".", ",") 'replace point with comma in 'numerical values
Next i
End If
Add1 = DataSheet.Cells(nrow, LBound(CsvItem) + 1).Address
Add2 = DataSheet.Cells(nrow, ncol + 1).Address
DataSheet.Range(Add1 & ":" & Add2) = CsvItem
nrow = nrow + 1
10:
Loop
nrowlast = nrow
Close #1
For Row = 11 To nrowlast
For col = 1 To ncol
DataSheet.Cells(Row, col).Select
DataSheet.Cells(Row, col).NumberFormat = "0.E+00"
Next col
Next Row
Set ChtObj = DataSheet.ChartObjects.Add(50, 50, 500, 300)
Set Cht = ChtObj.Chart
With Cht
.ChartType = xlXYScatterLines
.SeriesCollection.NewSeries
.SeriesCollection(1).XValues = DataSheet.Range("A11:A35")
.SeriesCollection(1).Values = DataSheet.Range("N11:N35")
End With
EDIT: I think your problem is this line: CsvItem = Split(CsvLine, ","). The return type of Split should be a String. I don't know what your data looks like but you could try casting it as Double if decimal places are relevant. Otherwise cast it as Long: CsvItem = CLng(Split(CsvLine, ",")).
Original answer:
I updated the relevant parts of your code:
For Row = 11 To nrowlast
For col = 1 To ncol
DataSheet.Cells(Row, col).NumberFormat = "0.E+00"
Next col
Next Row
I took out the .Select statement as it is unnecessary and might lead to errors. Here is the main part part:
Dim ChtObj As Object
Set ChtObj = DataSheet.Shapes.AddChart2(, xlXYScatterLines, 50, 50, 500, 300)
With ChtObj.Chart
.SeriesCollection.NewSeries
.SeriesCollection(1).XValues = DataSheet.Range("A11:A35")
.SeriesCollection(1).Values = DataSheet.Range("N11:N35")
End With
This should work as intended.
After some effort, i was able to locate the source of problem: I had defined CsvItem() as string and that's why when I execute the statement DataSheet.Range(Add1 & ":" & Add2) = CsvItem, the values in excel were always text irrespective of the format.
The solution to this was to define a variable to hold numerical values of CsvItem and assign this value to excel cells:
Dim CsvVal() As Double
CsvVal(i) = CDbl(CsvItem(i))
DataSheet.Range(Add1 & ":" & Add2).Value = CsvVal
this eventually produced the result I wanted.

Excel 2007, How to avoid scatter chart data points overlap

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.

Borders Object in Excel 2010, pass by reference?

I've been working on a formats encoder that grabs the format of a given range, and then provides the ability to paste that format on another given range (basically emulating "Paste Formats", but storable). I'm trying to set the Borders object of a range (in the set routine), but it seems that a Borders Object is passed by value, and not reference?
I can get the current values of the Borders object just fine, but if I try to set any value to it, it's straight up ignored (without even an "Read only" error). Follows is a snippet of my code:
Sub SetBorders(sInput As String, ByRef Target As Borders)
Dim resultPart() As String
'Border indexes go from 5 to 12
For i = 5 To 12
'Set resultPart
resultPart = Split(Split(sInput, CharEOList)(i - 5), CharEORecord)
If Len(resultPart(0)) > 0 Then
Target(i).ColorIndex = CLng(resultPart(0))
...
What am I doing wrong? Should I be using a higher Range object and drilling down to the Borders object inside it?
Apparently, access order is very important to the Borders object. I had been setting .LineStyle at the top, when it should have been more at the bottom. Setting LineStyle first (ie, to None), and then setting a Color, reverts the LineStyle to the one in the Borders Object.
Finished portion of that code:
Sub SetBorders(sInput As String, ByRef Target As Borders)
Dim resultPart() As String
'Border indexes go from 5 to 12
For i = 5 To 12
'Set resultPart
resultPart = Split(Split(sInput, CharEOList)(i - 5), CharEORecord)
'If index is empty, set that property to Variant/Null
If Len(resultPart(0)) > 0 Then Target(i).ColorIndex = CLng(resultPart(0)) Else Target(i).ColorIndex = Null
If Len(resultPart(1)) > 0 Then Target(i).color = CDbl(resultPart(1)) Else Target(i).color = Null
If Len(resultPart(2)) > 0 Then Target(i).ThemeColor = CDbl(resultPart(2)) Else Target(i).ThemeColor = Null
If Len(resultPart(3)) > 0 Then Target(i).TintAndShade = CDbl(resultPart(3)) Else Target(i).TintAndShade = Null
'Weight and LineStyle seem to always be set
Target(i).Weight = CLng(resultPart(4))
Target(i).LineStyle = CLng(resultPart(5))
On Error GoTo 0
Next i
End Sub
Function GetBorders(b As Borders)
Dim Result As String
Result = ""
Dim resultPart() As String
'Border indexes go from 5 to 12
For i = 5 To 12
'Reset resultPart
resultPart = Split(",,,,,", ",")
'Skip errors. This will leave that index blank, which equates to a Null when using Borders
On Error Resume Next
resultPart(0) = b(i).ColorIndex
resultPart(1) = b(i).color
resultPart(2) = b(i).ThemeColor
resultPart(3) = b(i).TintAndShade
resultPart(4) = b(i).Weight
resultPart(5) = b(i).LineStyle
On Error GoTo 0
Result = Result + Join(resultPart, CharEORecord) + CharEOList
Next i
GetBorders = Result
End Function
This worked for me (XL2010), so there must be some other issue going on with your code.
EDIT - I see you figured it out.
Sub tester()
SetBorders Selection.Borders
End Sub
Sub SetBorders(obj As Borders)
Dim arr, x
arr = Array(xlEdgeBottom, xlEdgeLeft, xlEdgeRight, xlEdgeTop)
For x = LBound(arr) To UBound(arr)
With obj(arr(x))
.LineStyle = xlContinuous
End With
Next x
End Sub

Resources