Excel XY图表(散点图)数据标签无重叠

Sch*_*ude 6 charts vba scatter-plot excel-vba overlap

所以过去一周我一直在研究这个问题.虽然它不能创造奇迹,但我可以说我有一个非常好的结果: 之前和之后 在更严肃的图表之前和之后
我只是想把这个代码放在那里为所有像我一样的穷人寻找某种vba宏来帮助他们避免散点图中的标签重叠,因为在对这个主题进行研究时,我无法找到任何有用的东西.

Sch*_*ude 4

Const PIXEL_TO_POINT_RATIO As Double = 0.72 '1 Pixel = 72/96*1 Point
Const tStep As Double = 0.1
Const rStep As Double = 0.1
Dim pCount As Integer

Sub ExampleMain()

        RearrangeScatterLabels Sheet5 

        RearrangeScatterLabels Sheet25

End Sub

Sub RearrangeScatterLabels(sht As Worksheet)
    Dim plot As Chart
    Dim sCollection As SeriesCollection
    Dim dLabels() As DataLabel
    Dim dPoints() As Point
    Dim xArr(), yArr(), stDevX, stDevY As Double
    Dim x0, x1, y0, y1 As Double
    Dim temp() As Double
    Dim theta As Double
    Dim r As Double
    Dim isOverlapped As Boolean
    Dim safetyNet, validEntry, currentPoint As Integer

    Set plot = sht.ChartObjects(1).Chart 'XY chart (scatter plot)
    Set sCollection = plot.SeriesCollection 'All points and labels
    safetyNet = 1
    pCount = (sCollection.Count - 1)

    ReDim dLabels(1 To 1)
    ReDim dPoints(1 To 1)
    ReDim xArr(1 To 1)
    ReDim yArr(1 To 1)

    For pt = 1 To sCollection(1).Points.Count
        For i = 1 To pCount
            If sCollection(i).Points.Count <> 0 Then
                'Dynamically expand the arrays
                validEntry = validEntry + 1
                If validEntry <> 1 Then
                    ReDim Preserve dLabels(1 To UBound(dLabels) + 1)
                    ReDim Preserve dPoints(1 To UBound(dPoints) + 1)
                    ReDim Preserve xArr(1 To UBound(xArr) + 1)
                    ReDim Preserve yArr(1 To UBound(yArr) + 1)
                End If

                Set dLabels(i) = sCollection(i).Points(pt).DataLabel 'Store all label objects
                Set dPoints(i) = sCollection(i).Points(pt)           'Store all point objects
                temp = getElementDimensions(, dPoints(i))
                xArr(i) = temp(0) 'Store all points x values
                yArr(i) = temp(2) 'Store all points y values
            End If
        Next
    Next

    If UBound(dLabels) < 2 Then Exit Sub

    pCount = UBound(dLabels)
    stDevX = Application.WorksheetFunction.StDev(xArr) 'Get standard deviation for x
    stDevY = Application.WorksheetFunction.StDev(yArr) 'Get standard deviation for y
    If stDevX = 0 Then stDevX = 1
    If stDevY = 0 Then stDevY = 1
    r = 0

    For currentPoint = 1 To pCount
        theta = Rnd * 2 * Application.WorksheetFunction.Pi()
        x0 = xArr(currentPoint)
        y0 = yArr(currentPoint)
        x1 = xArr(currentPoint)
        y1 = yArr(currentPoint)
        isOverlapped = True

        Do Until Not isOverlapped
            safetyNet = safetyNet + 1

            If safetyNet < 500 Then
                If Not checkForOverlap(dLabels(currentPoint), dLabels, dPoints, plot) Then
                    'No label is within bounds and not overlapping
                    isOverlapped = False
                    r = 0
                    theta = Rnd * 2 * Application.WorksheetFunction.Pi()
                    safetyNet = 1
                Else
                    'Move label so it does not overlap
                    theta = theta + tStep
                    r = r + rStep * tStep / (2 * Application.WorksheetFunction.Pi())
                    x1 = x0 + stDevX * r * Cos(theta)
                    y1 = y0 + stDevY * r * Sin(theta)
                    dLabels(currentPoint).Left = x1
                    dLabels(currentPoint).Top = y1
                End If
            Else
                safetyNet = 1
                Exit Do
            End If
        Loop
    Next
End Sub

Function checkForOverlap(ByRef dLabel As DataLabel, ByRef dLabels() As DataLabel, ByRef dPoints() As Point, ByRef dChart As Chart) As Boolean
    checkForOverlap = False 'Return false by default

    'Detect label going over chart area
    If detectOverlap(dLabel, , , dChart) Then
        checkForOverlap = True
        Exit Function
    End If

    'Detect labels overlap
    For i = 1 To pCount
        If Not dLabel.Left = dLabels(i).Left Then
            If detectOverlap(dLabel, dLabels(i)) Then
                checkForOverlap = True
                Exit Function
            End If
        End If
    Next

    'Detect label overlap with point
    For i = 1 To pCount
        If detectOverlap(dLabel, , dPoints(i)) Then
            checkForOverlap = True
            Exit Function
        End If
    Next
End Function

Function getElementDimensions(Optional dLabel As DataLabel, Optional dPoint As Point, Optional dChart As Chart) As Double()
    'Get element dimensions and compensate slack
    Dim eDimensions(3) As Double

    'Working in IV quadrant
    If dPoint Is Nothing And dChart Is Nothing Then
        'Get label dimensions and compensate padding
        eDimensions(0) = dLabel.Left + PIXEL_TO_POINT_RATIO * 3                'Left
        eDimensions(1) = dLabel.Left + dLabel.Width - PIXEL_TO_POINT_RATIO * 3 'Right
        eDimensions(2) = dLabel.Top + PIXEL_TO_POINT_RATIO * 6                 'Top
        eDimensions(3) = dLabel.Top + dLabel.Height - PIXEL_TO_POINT_RATIO * 3 'Bottom
    End If
    If dLabel Is Nothing And dChart Is Nothing Then
        'Get point dimensions
        eDimensions(0) = dPoint.Left - PIXEL_TO_POINT_RATIO * 5 'Left
        eDimensions(1) = dPoint.Left + PIXEL_TO_POINT_RATIO * 5 'Right
        eDimensions(2) = dPoint.Top - PIXEL_TO_POINT_RATIO * 5  'Top
        eDimensions(3) = dPoint.Top + PIXEL_TO_POINT_RATIO * 5  'Bottom
    End If
    If dPoint Is Nothing And dLabel Is Nothing Then
        'Get chart dimensions
        eDimensions(0) = dChart.PlotArea.Left + PIXEL_TO_POINT_RATIO * 22                         'Left
        eDimensions(1) = dChart.PlotArea.Left + dChart.PlotArea.Width - PIXEL_TO_POINT_RATIO * 22 'Right
        eDimensions(2) = dChart.PlotArea.Top - PIXEL_TO_POINT_RATIO * 4                           'Top
        eDimensions(3) = dChart.PlotArea.Top + dChart.PlotArea.Height - PIXEL_TO_POINT_RATIO * 4  'Bottom
    End If

    getElementDimensions = eDimensions 'Return dimensions array in Points
End Function

Function detectOverlap(ByVal dLabel1 As DataLabel, Optional ByVal dLabel2 As DataLabel, Optional ByVal dPoint As Point, Optional ByVal dChart As Chart) As Boolean
    'Left, Right, Top, Bottom
    Dim AxL, AxR, AyT, AyB As Double 'First label coordinates
    Dim BxL, BxR, ByT, ByB As Double 'Second label coordinates
    Dim eDimensions() As Double 'Element dimensions

    eDimensions = getElementDimensions(dLabel1)
    AxL = eDimensions(0)
    AxR = eDimensions(1)
    AyT = eDimensions(2)
    AyB = eDimensions(3)

    If dPoint Is Nothing And dChart Is Nothing Then
        'Compare with another label
        eDimensions = getElementDimensions(dLabel2)
    End If
    If dLabel2 Is Nothing And dChart Is Nothing Then
        'Compare with a point
        eDimensions = getElementDimensions(, dPoint)
    End If
    If dPoint Is Nothing And dLabel2 Is Nothing Then
        'Compare with chart area
        eDimensions = getElementDimensions(, , dChart)
    End If
    BxL = eDimensions(0)
    BxR = eDimensions(1)
    ByT = eDimensions(2)
    ByB = eDimensions(3)

    If dChart Is Nothing Then
        detectOverlap = (AxL <= BxR And AxR >= BxL And AyT <= ByB And AyB >= ByT) 'Reverse De Morgan's Law
    Else
        detectOverlap = Not (AxL >= BxL And AxR <= BxR And AyT >= ByT And AyB <= ByB) 'Is in chart bounds (working in IV quadrant)
    End If
End Function
Run Code Online (Sandbox Code Playgroud)


我意识到代码有点粗糙并且没有优化,但我不能在这个项目上花费更多时间。我留下了很多注释来帮助阅读它,如果有人选择继续这个项目的话。

希望这可以帮助。
最美好的祝愿,幸灾乐祸。