Home > Mobile >  Secondary axis not graphing values
Secondary axis not graphing values

Time:03-18

I have a list of serial numbers with run times that I am trying to graph via a combo graph in excel then push it to PowerPoint. I am using array operations to get the data for the graph, setting up 3 seriescollections and trying to get the serial numbers in a bar chart with counts, then line graphs correlating durations (aveage and total) of run times. The data is getting to the graph, and the values are correct in the select data window. Each series is also assigned the correct axisgroup (primary or secondary) in the graph's select data window. Any ideas why the plotted points for both lines are "0" (double clicking the data point on the graph also says the value is 0)?

I am dim-ing stuff() as variants. I know it's not right. I should either dim them as an arr() of type or arr as variant. IDK why it breaks for me when I do it another way, but it does. I'm also ears abouth that. lol. I appreciate any help!!!!

Here's the whole sub and functions (I leached the QuickSortArray from stack somewhere) as it is currently. I have formatting in ppt to do:

Code Updated with @FaneDuru's help:

Option Explicit


Sub Top_5_Graph()

Application.DisplayAlerts = False
Application.ScreenUpdating = False
Application.EnableEvents = False

Dim Exl As Excel.Application
Dim wbk As Workbook
Dim wks As Worksheet
Dim EXLfn As String

Dim i As Variant
Dim j As Variant
Dim rng As Range
Dim lastrow As Long
Dim dur As Variant

Dim serialNum() As Variant
Dim arr As Variant
Dim SerialNumCnt() As Variant
Dim top5() As Variant
Dim duration() As Variant
Dim aveDur() As Variant
Dim hidur As Variant

Dim counter As Integer
Dim cnt As Integer

Dim chrt As Variant
Dim chrtname As String
Dim xvalues() As Variant
Dim yvalues() As Variant


'Ask user for file
ChDir "\\myURL\"

Set wbk = Workbooks.Open(Application.GetOpenFilename("Microsoft Excel Files, *.xls*"))

'pulls file name to use for the ppt file name later
EXLfn = Split(Split(wbk.Name, "\")(UBound(Split(wbk.Name, "\"))), ".xlsx")(0)

Set wks = wbk.Worksheets("SN_Overview")
wks.Activate

lastrow = wks.Cells(Rows.count, "a").End(xlUp).Row

'removes blanks from AO
wks.AutoFilterMode = False
wks.Range("A1:BO" & lastrow).AutoFilter Field:=41, Criteria1:="<>", Operator:=xlFilterValues

Set rng = wks.Range("AO2:AO" & lastrow).SpecialCells(xlCellTypeVisible)

'loads SNs into array
serialNum = contArrayFromDscRng(rng)

'returns an array of just unique values
serialNum = GetUniqueDict(serialNum)

'adds counts to the array(sn, count)
For i = LBound(serialNum) To UBound(serialNum)
ReDim Preserve SerialNumCnt(LBound(serialNum) To UBound(serialNum), 0 To 1)

    cnt = 0
    For Each j In rng
        If serialNum(i) = j.Value Then
            cnt = cnt   1
        End If
    Next j
    
    SerialNumCnt(i, 0) = serialNum(i)
    SerialNumCnt(i, 1) = cnt
Next i

'orders array from largest
Call QuickSortArray(SerialNumCnt(), , , 2)

'add duration of each SN by adding column AL, "Duration"
For i = LBound(serialNum) To UBound(serialNum)
    ReDim Preserve duration(LBound(serialNum) To UBound(serialNum))
    For j = 1 To rng.Rows.count
        If serialNum(i) = rng.Cells(j, 1) And duration(i) = "" Then
                dur = Format(CDate(rng.Cells(j, -2)), "hh:mm:ss")
                duration(i) = dur
        ElseIf serialNum(i) = rng.Cells(j, 1) And duration(i) <> "" Then
                dur = Format(CDate(rng.Cells(j, -2)), "hh:mm:ss")
                duration(i) = Format(CDate(duration(i))   CDate(dur), "hh:mm:ss")
        End If
    Next j
Next i

'sets average durations
ReDim aveDur(LBound(duration) To UBound(duration))
For i = LBound(duration) To UBound(duration)
    aveDur(i) = Format(CDate(duration(i)) / SerialNumCnt(i, 1), "hh:mm:ss")
Next i

'grabs the top 5 SNs and counts
If LBound(SerialNumCnt)   5 >= UBound(SerialNumCnt) Then
    For i = LBound(SerialNumCnt) To UBound(SerialNumCnt)
        ReDim Preserve top5(LBound(SerialNumCnt) To UBound(SerialNumCnt), 0 To 1)
        top5(i, 0) = SerialNumCnt(i, 0)
        top5(i, 1) = SerialNumCnt(i, 1)
    Next i
Else
    For i = LBound(SerialNumCnt) To LBound(SerialNumCnt)   5
        ReDim Preserve top5(LBound(SerialNumCnt) To UBound(SerialNumCnt), 0 To 1)
        top5(i, 0) = SerialNumCnt(i, 0)
        top5(i, 1) = SerialNumCnt(i, 1)
    Next i
End If

'gets highest duration to set secondary axis
For i = LBound(duration) To UBound(duration)
    If Format(CDate(duration(i)), "hh:mm:ss") > Format(CDate(hidur), "hh:mss") Then
        hidur = Format(duration(i), "hh:mm:ss")
    End If
Next i

'trying to get percentage the highest duration is of 24 hours in decimal to set as secondary axis maximum
'hidur = Format(hidur, "hh:mm:ss") / Format("23:59:59", "hh:mm:ss")


'puts serial numbers as xvalues and counts as y values
ReDim xvalues(LBound(top5) To UBound(top5))
ReDim yvalues(LBound(top5) To UBound(top5))

For i = LBound(top5) To UBound(top5)
    xvalues(i) = top5(i, 0)
    yvalues(i) = top5(i, 1)
Next i


'graphs the arrays
wks.ShowAllData

'initialize chart
Set chrt = wks.ChartObjects.Add(Cells(7, "AP").Left, Cells(7, "AP").Top, 600, 300)

' chart properties
With chrt.Chart
    .SeriesCollection.NewSeries
    With .SeriesCollection(1)
        .Values = yvalues
        .xvalues = yvalues
        .HasDataLabels = True
        .Name = "Top 5 Serial Numbers"
        .ChartType = xlColumnClustered
        .AxisGroup = 1
        .Interior.Color = RGB(255, 0, 0)
    End With
    .Axes(xlCategory, xlPrimary).CategoryNames = xvalues
    .SeriesCollection.NewSeries
    With .SeriesCollection(2)
        .Values = aveDur
        .xvalues = aveDur
        .AxisGroup = 2
        .Name = "Ave Duration"
        .ChartType = xlLine
        .Format.Line.ForeColor.RGB = RGB(0, 0, 0)
        .Border.Weight = 4
        .HasDataLabels = True
        With .DataLabels
            .NumberFormat = "[hh]:mm:ss;@"
            .Position = xlLabelPositionCenter
        End With
    End With
    .SeriesCollection.NewSeries
    With .SeriesCollection(3)
        .Values = duration
        .xvalues = duration
        .AxisGroup = 2
        .Name = "Duration of SNs"
        .ChartType = xlLine
        .Format.Line.ForeColor.RGB = RGB(66, 128, 92)
        .Border.Weight = 3
        .HasDataLabels = True
        With .DataLabels
            .NumberFormat = "[hh]:mm:ss;@"
            .Position = xlLabelPositionAbove
        End With
    End With
    .ChartArea.Format.Fill.ForeColor.RGB = RGB(255, 255, 255)
    .Axes(xlValue, xlSecondary).TickLabels.NumberFormat = "[hh]:mm:ss;@"
    .Axes(xlValue, xlSecondary).MaximumScale = 0.0005
    .HasTitle = True
    .ChartTitle.Text = "Top 5 Serial Numbers with Durations"
End With

'With chrt.PlotArea.Format.Fill.GradientStops
'    .InsertRGB = RGB(255, 255, 255)
'    .Position = 0
'    .Brightness = 0.9
'    .InsertRGB = RGB(241, 249, 249)
'    .Position = 0.74
'    .InsertRGB = RGB(241, 249, 249)
'    .Position = 0.83
'    .InsertRGB = RGB(249, 252, 253)
'    .Position = 1
'End With

'sends chart to powerpoint

Call PubToPPT(chrt, EXLfn)

Application.DisplayAlerts = True
Application.ScreenUpdating = True
Application.EnableEvents = True

End Sub

Public Sub PubToPPT(chrt As Variant, FN As String)

Application.DisplayAlerts = False
Application.ScreenUpdating = False
Application.EnableEvents = False

Dim i As Variant

Dim PPTpath As String
Dim PPTtfn As String
Dim PPTfn As String

Dim PPTapp As PowerPoint.Application
Dim PPTtemp As PowerPoint.Presentation
Dim PPTpres As PowerPoint.Presentation

Dim top5 As PowerPoint.Shape
  
'opens the template
PPTpath = "\\myURL\"
PPTtfn = "CURRENT_TEMPLATE_MASTER.pptx"
PPTfn = FN   ".pptx"

Set PPTapp = CreateObject("PowerPoint.Application")

Set PPTtemp = PPTapp.Presentations.Open(PPTpath & PPTtfn, msoCTrue)

'saves template as new file name in the same folder
PPTtemp.SaveAs (PPTpath & PPTfn)

Set PPTpres = PPTtemp
'PPTapp.WindowState = ppWindowMinimized

'put chart image on slide

chrt.Copy

If chrt.Chart.ChartTitle.Text = "Top 5 Serial Numbers with Durations" Then GoTo top5

If chrt.Chart.ChartTitle.Text = "Confidence Levels with Durations" Then GoTo confidence

If chrt.Chart.ChartTitle.Text = "Activity by Platform" Then GoTo platform

If chrt.Chart.ChartTitle.Text = "Activity by Week" Then GoTo week

If chrt.Chart.ChartTitle.Text = "Activity by Weekday" Then GoTo weekday

If chrt.Chart.ChartTitle.Text = "Activity by Time of Day" Then GoTo tod


top5:

For i = 1 To PPTpres.Slides(1).Shapes.count
    If PPTpres.Slides(1).Shapes(i).Name = "Chart*" Then
        PPTpres.Slides(1).Shapes(i).Delete
    End If
Next i

PPTpres.Slides(1).Shapes.Paste


confidence:

platform:

week:

weekday:

tod:





'pptpres.slides(


Application.DisplayAlerts = True
Application.ScreenUpdating = True
Application.EnableEvents = True

End Sub

Public Sub QuickSortArray(ByRef SortArray As Variant, Optional lngMin As Long = -1, Optional lngMax As Long = -1, _
Optional lngColumn As Long = 0)
    On Error Resume Next
    Dim i As Long
    Dim j As Long
    Dim varMid As Variant
    Dim arrRowTemp As Variant
    Dim lngColTemp As Long
    If IsEmpty(SortArray) Then
        Exit Sub
    End If
    If InStr(TypeName(SortArray), "()") < 1 Then  'IsArray() is somewhat broken: Look for brackets in the type name
        Exit Sub
    End If
    If lngMin = -1 Then
        lngMin = LBound(SortArray, 1)
    End If
    If lngMax = -1 Then
        lngMax = UBound(SortArray, 1)
    End If
    If lngMin >= lngMax Then    ' no sorting required
        Exit Sub
    End If
    i = lngMin
    j = lngMax
    varMid = Empty
    varMid = SortArray((lngMin   lngMax) \ 2, lngColumn)
    ' We  send 'Empty' and invalid data items to the end of the list:
    If IsObject(varMid) Then  ' note that we don't check isObject(SortArray(n)) - varMid *might* pick up a valid default member or property
        i = lngMax
        j = lngMin
    ElseIf IsEmpty(varMid) Then
        i = lngMax
        j = lngMin
    ElseIf IsNull(varMid) Then
        i = lngMax
        j = lngMin
    ElseIf varMid = "" Then
        i = lngMax
        j = lngMin
    ElseIf VarType(varMid) = vbError Then
        i = lngMax
        j = lngMin
    ElseIf VarType(varMid) > 17 Then
        i = lngMax
        j = lngMin
    End If
    While i <= j
        While SortArray(i, lngColumn) < varMid And i < lngMax
            i = i   1
        Wend
        While varMid < SortArray(j, lngColumn) And j > lngMin
            j = j - 1
        Wend
        If i <= j Then
            ' Swap the rows
            ReDim arrRowTemp(LBound(SortArray, 2) To UBound(SortArray, 2))
            For lngColTemp = LBound(SortArray, 2) To UBound(SortArray, 2)
                arrRowTemp(lngColTemp) = SortArray(i, lngColTemp)
                SortArray(i, lngColTemp) = SortArray(j, lngColTemp)
                SortArray(j, lngColTemp) = arrRowTemp(lngColTemp)
            Next lngColTemp
            Erase arrRowTemp
            i = i   1
            j = j - 1
        End If
    Wend
    If (lngMin < j) Then Call QuickSortArray(SortArray, lngMin, j, lngColumn)
    If (i < lngMax) Then Call QuickSortArray(SortArray, i, lngMax, lngColumn)
End Sub

Private Function contArrayFromDscRng(rng As Range) As Variant 'makes 2D array from a discontinuous range i.e. filtered worksheet
    Dim a As Range, arr, count As Long, i As Long
    
    ReDim arr(1 To rng.Cells.count, 1 To 1): count = 1
    For Each a In rng.Areas
            For i = 1 To a.Cells.count
                arr(count, 1) = a.Cells(i).Value: count = count   1
            Next
    Next
    contArrayFromDscRng = arr
End Function

Function GetUniqueDict(arr As Variant) As Variant

   Dim dict As Object, i As Long
   Set dict = CreateObject("Scripting.Dictionary")
   
   For i = LBound(arr) To UBound(arr)
        dict(arr(i, 1)) = 1
   Next i
   GetUniqueDict = dict.Keys
End Function

CodePudding user response:

Please, use the next function to build a continuous array from a discontinuous range:

Private Function contArrayFromDscRng(rng As Range) As Variant 'makes 2D array from a discontinuous range
    Dim a As Range, arr, count As Long, i As Long
    
    ReDim arr(1 To rng.cells.count, 1 To 1): count = 1
    For Each a In rng.Areas
            For i = 1 To a.cells.count
                arr(count, 1) = a.cells(i).value: count = count   1
            Next
    Next
    contArrayFromDscRng = arr
End Function

You can use it in your code as:

serialNum = contArrayFromDscRng(rng)

The next function, will extract an array of unique values from another array:

Function GetUniqueDict(arr As Variant) As Variant
   Dim dict As Object, i As Long
   Set dict = CreateObject("Scripting.Dictionary")
   For i = LBound(arr) To UBound(arr)
        dict(arr(i, 1)) = 1
   Next i
   GetUniqueDict = dict.Keys
End Function

But it will return a 1D array. It can also be used like data sources for a chart.

But if you like your way of processing a 2D array, you can easily transform the returned 1D array. Inside the function, or outside. Something like this:

  Dim arr
  arr = GetUniqueDict(serialNum)
  
  'transform it as a 2D array:
  Dim i As Long
  ReDim serialNum(1 To UBound(arr)   1, 1 To 1)
  For i = 0 To UBound(arr)
    serialNum(i   1, 1) = arr(i)
  Next i
  • Related