How to plot data imported from a csv? - excel

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.

Related

Running a calculation function for n number of rows

I want to automate a calculation for a measurement data Excel file.
While I made the function work, I could only figure out how to do that for one row at a time. The calculation is specific to the data on every row.
I thought I could change the Range("J3") value to Range("J3:J52") for a capacity of 50 calculations.
How do I make the function calculate for every row, separately, using the data of said specific row?
It doesn't matter if it runs for all 50 rows or if I have to figure out some loop function to find how many rows to calculate for, as long as one button press in the end will make the magic happen.
I have included a screenshot of the sheet for reference, the main calculation is done in Excel, but what this is doing is choosing the correct option out of a few different correction calculations and explaining to the user why.
I think there are some unnecessary Dim lines at the start but if it runs, I wasn't going to remove them.
'The main function, activated by a simple button Sub'
Function ISO16032()
'DeltaL Range'
Dim DeltaL As Range
Set DeltaL = Range("F3")
'Result is the corrected value in G column'
Dim Result As Long
'Note is the calc note in H column'
Dim Note As String
'X is the DeltaL between noise and background noise'
Dim x As Long
x = Range("F3").Value
Select Case Range("F3").Value
'No correction when X = > 10'
Case 10.6 To 200
Result = Range("J3")
Range("G3").Value = Result
Note = "No correction"
Range("H3").Value = Note
'Correction according to ISO16032 when X = between 4 and 10'
Case 3.6 To 10.5
Result = Range("K3")
Range("G3").Value = Result
Note = "Correction per ISO16032"
Range("H3").Value = Note
'Maximal correction value set to 2,2 dB if X < 4'
Case 0.1 To 3.5
Result = Range("L3")
Range("G3").Value = Result
Note = "Correction limit set to 2,2 dB"
Range("H3").Value = Note
'If x = < 0, the measurement is invalid'
Case Else
Note = "Repeat measurement!"
Range("H3").Value = Note
End Select
End Function
Hi and welcome to stackoverflow, I think that a simple loop, with the addition of an argument to your ISO function allows you to solve your problem like this
Sub Looping()
For i = 3 To 52
' Convert i to String because we need to concatenate with the letter F, G, H...
Call ISO16032(CStr(i))
Next
End Sub
Function ISO16032(Cell_X)
'DeltaL Range'
Dim DeltaL As Range
Set DeltaL = Range("F" + Cell_X)
'Result is the corrected value in G column'
Dim Result As Long
'Note is the calc note in H column'
Dim Note As String
'X is the DeltaL between noise and background noise'
Dim x As Long
x = Range("F" + Cell_X).Value
Select Case Range("F" + Cell_X).Value
'No correction when X = > 10'
Case 10.6 To 200
Result = Range("J" + Cell_X)
Range("G" + Cell_X).Value = Result
Note = "No correction"
Range("H" + Cell_X).Value = Note
'Correction according to ISO16032 when X = between 4 and 10'
Case 3.6 To 10.5
Result = Range("K" + Cell_X)
Range("G" + Cell_X).Value = Result
Note = "Correction per ISO16032"
Range("H" + Cell_X).Value = Note
'Maximal correction value set to 2,2 dB if X < 4'
Case 0.1 To 3.5
Result = Range("L" + Cell_X)
Range("G" + Cell_X).Value = Result
Note = "Correction limit set to 2,2 dB"
Range("H" + Cell_X).Value = Note
'If x = < 0, the measurement is invalid'
Case Else
Note = "Repeat measurement!"
Range("H" + Cell_X).Value = Note
End Select
End Function
EDIT: think indented your code like I did to make it more readable too (or like #Darren Bartrup-Cook did when editing your question)
This is more of an example of how to calculate each row.
Just getting your code to work across multiple rows.
Cells
With...End With Statement
'No need for Sub to call function that doesn't return anything.... just write a sub.
Public Sub ISO16032()
Dim LastRow As Long
With ThisWorkbook.Worksheets("Sheet1")
LastRow = .Cells(.Rows.Count, 10).End(xlUp).Row
Dim RowCounter As Long
Dim Result As Long
Dim Note As String
'Only calculate if there is data in rows 3 onwards.
If LastRow >= 3 Then
'Reset results on each pass.
'Assuming -1 is an impossible answer so code knows
'not to put anything on Case Else.
Result = -1
Note = ""
'Cycle through each row and calculate.
For RowCounter = 3 To LastRow
Select Case .Cells(RowCounter, 6) 'Look at column F(column 6) on each row.
Case 10.6 To 200
Result = .Cells(RowCounter, 10)
Note = "No corrections."
Case 3.6 To 10.5
Result = .Cells(RowCounter, 11)
Note = "Correction per ISO16032"
Case 0.1 To 3.5
Result = .Cells(RowCounter, 12)
Note = "Correction limit set to 2,2 dB"
Case Else
Note = "Repeat measurement!"
End Select
'Place results on sheet.
.Cells(RowCounter, 7) = IIf(Result >= 0, Result, "")
.Cells(RowCounter, 8) = Note
Next RowCounter
End If
End With
End Sub

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

VBA Making chart from array - problem with missing values

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

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.

Excel VBA, having multiple column inputs as XYscatter chart Series name in dynamically generated chart

I'm trying to automate the generation of a dynamic XYscatter chart using VBA. My data are in sets of 12 individual data per trial and the number of trial will vary. Firstly by scanning the csv file to locate the last set of data, and by removing the first line of the tag, i divided it by 12 to determine the number of sets available and then populate the data into to graph with the corresponding data points and series name. I've got most of the code working but I'm having some syntax issue on including a range data for series name. The series name will only work if I am selecting a column instead of a range of column data.
How can I modify my code to have multiple column input as my Series Name?
Sub PlotSelect()
Dim myChart As Chart
DataRow = 1
SelectRow = 2
With ActiveSheet
'To count the number of rows to determine number of sets of data
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
Row = LastRow - 1
N = Row / 12
'Creating a dummy chart before repopulating the data points
Range("A1:B2").Select
Set myChart = ActiveSheet.Shapes.AddChart2(240, xlXYScatterSmooth).Chart
' delete all the dummy series
For i = myChart.SeriesCollection.Count To 1 Step -1
myChart.SeriesCollection(i).Delete
Next
'Populating chart with data
Do While DataRow <= N
If DataRow <> -1 Then
myChart.SeriesCollection.NewSeries
myChart.SeriesCollection(DataRow).Name = Range(ActiveSheet.Cells(SelectRow, 2), ActiveSheet.Cells(SelectRow, 5))
myChart.SeriesCollection(DataRow).Values = Range(ActiveSheet.Cells(SelectRow, 9), ActiveSheet.Cells(SelectRow + 11, 9))
myChart.SeriesCollection(DataRow).XValues = Range(ActiveSheet.Cells(SelectRow, 8), ActiveSheet.Cells(SelectRow + 11, 8))
End If
DataRow = DataRow + 1
SelectRow = SelectRow + 12
Loop
End Sub
Instead of using a range of cells to generate the Name for the data entry, I merge the data in the cells and copy the content into another cell and select the new cell with the concatenated cells. Later i delete the temporary cell.
Do While DataRow <= 2
If DataRow <> -1 Then
myChart.SeriesCollection.NewSeries 'To add new data entries
**Range("B9999").Select
ActiveCell.FormulaR1C1 = ((Cells(SelectRow, 4)) & "_" & (Cells(SelectRow, 5)) & "_" & (Cells(SelectRow, 6)))
myChart.FullSeriesCollection(DataRow).Name = Range("B9999")**
myChart.SeriesCollection(DataRow).Values = Range(Cells(SelectRow, 2), ActiveSheet.Cells(SelectRow + 600, 2))
myChart.SeriesCollection(DataRow).XValues = Range(Cells(SelectRow, 1), ActiveSheet.Cells(SelectRow + 600, 1))
End If
DataRow = DataRow + 1
SelectRow = SelectRow + 601
Loop
Range("B9999").Delete 'to delete temp data
ActiveWindow.ScrollRow = 2 'to reset spreadsheet view
The modifications are located in the ** ** range

Resources