1

Часть 1

Я ищу способ автоматического поворота (с VBA) круговой диаграммы в зависимости от данных. Данные изменяются динамически в зависимости от выбранного месяца. Вот пример результата, который я могу получить:

Круговая диаграмма

Как видите, метки, даже с помощью встроенной автоматической настройки (лучше всего подходят) в Excel, выглядят не очень хорошо. Это потому, что под диаграммой не так много места для размещения меток. На самом деле, поскольку это квадрат в квадрате, в углах всегда будет больше места. Вот как это выглядит при повороте на 30 ° вручную, все еще с метками наилучшего соответствия :

Круговая диаграмма

Если вы похожи на меня, вы увидите, что вторая круговая диаграмма выглядит лучше, чем первая.

Теперь это легко, все, что мне нужно было сделать, чтобы решить мою проблему, это добавить поворот на 30 ° к диаграмме, но данные диаграммы загружаются динамически, иногда мне понадобится 30 °, в других случаях это будет 270 °. Проблема возникает только тогда, когда есть несколько маленьких кусочков, как в примере выше.

Есть ли способ, программно, в зависимости от данных, найти, куда Excel собирается поместить маленькие кусочки в круговую диаграмму 360 °, а затем применить соответствующее вращение, когда есть 3 последовательных маленьких кусочка (3 или более кусочков с общим количеством менее 10%)?

Это выглядит сложно сделать, и я не понимаю, почему Excel не делает это автоматически, но должен быть способ.


Часть 2

Программно решить эту проблему:

Круговая диаграмма 2

Мне пришлось переместить ярлыки вручную, чтобы получить что-то красивое:

Круговая диаграмма 2

Вот некоторый кусок кода, чтобы переместить метки, чтобы избежать контакта друг с другом. Я мог бы, вероятно, заставить это работать для ситуации # 2, но это все еще довольно сложно. Он обнаруживает коллизии между метками, рекурсивно зацикливая и перемещая метки друг от друга на пару пикселей, пока они больше не будут в коллизии. Но даже смотря на то, какая сторона находится в столкновении с другой этикеткой, перемещение ее на противоположную сторону может привести к худшему результату. И если в одной зоне 4-5 меток, рекурсивная функция работает вечно с некоторыми случайными результатами. Я не хочу потратить 100 часов на разработку лучшего алгоритма, если есть лучшее или уже существующее решение.

Sub MoveLabels(chartID As Integer)
    On Error Resume Next
    Dim sh As Worksheet
    Dim ch As Chart
    Dim sers As SeriesCollection
    Dim ser As Series
    Dim i As Long, pt As Long
    Dim dLabels() As DataLabel

    Set sh = ActiveSheet
    Set ch = sh.ChartObjects("Chart " & chartID).Chart

    Set sers = ch.SeriesCollection
    sers(1).DataLabels.Position = xlLabelPositionBestFit

    ReDim dLabels(1 To sers(1).Points.Count)
    For i = 1 To sers(1).Points.Count
        Set dLabels(i) = sers(1).Points(i).DataLabel
    Next
    AdjustLabels dLabels
    On Error GoTo 0
End Sub

Sub AdjustLabels(ByRef v() As DataLabel)
    Dim i As Long, j As Long
    Dim ptMove As Integer

    'A label will be moved recursively by that many pixel until it avoids contact
    'More pixels is faster, less pixels is more accurate
    ptMove = 10 

    For i = LBound(v) To UBound(v) - 1
    For j = LBound(v) + 1 To UBound(v)
        If v(i).Left <= v(j).Left Then
            If v(i).Top <= v(j).Top Then
                If (v(j).Top - v(i).Top) < v(i).Height And (v(j).Left - v(i).Left) < v(i).Width And v(j).Text <> v(i).Text And v(j).Text <> "" And v(i).Text <> "" Then
                    v(i).Left = v(i).Left - ptMove
                    v(j).Left = v(j).Left + ptMove
                    AdjustLabels v
                End If
            Else
                If (v(i).Top - v(j).Top) < v(j).Height And (v(j).Left - v(i).Left) < v(i).Width And v(j).Text <> v(i).Text And v(j).Text <> "" And v(i).Text <> "" Then
                    v(i).Left = v(i).Left - ptMove
                    v(j).Left = v(j).Left + ptMove
                    AdjustLabels v
                End If
            End If
        Else
            If v(i).Top <= v(j).Top Then
                If (v(j).Top - v(i).Top) < v(i).Height And (v(i).Left - v(j).Left) < v(j).Width And v(j).Text <> v(i).Text And v(j).Text <> "" And v(i).Text <> "" Then
                    v(i).Left = v(i).Left + ptMove
                    v(j).Left = v(j).Left - ptMove
                    AdjustLabels v
                End If
            Else
                If (v(i).Top - v(j).Top) < v(j).Height And (v(i).Left - v(j).Left) < v(j).Width And v(j).Text <> v(i).Text And v(j).Text <> "" And v(i).Text <> "" Then
                    v(i).Left = v(i).Left + ptMove
                    v(j).Left = v(j).Left - ptMove
                    AdjustLabels v
                End If
            End If
        End If
    Next j, i
End Sub

0