[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]
『散布図のラベルデータの重なり回避について』(AKI)
シートに以下のようなデータが入っています。
X Y
A 4 7
B 6 12
C10 16
D10 16
最大で40行までのデータで、X値とY値の変域はともに最小値4、最大値16です。
このデータを散布図にします。
すると、CとDのように同じX値Y値をとるデータはプロットが重なります。
それはいいのですが、データラベルをセル値にし、「A」や「B」という表示にすると、「C」と「D」という表記も当然重なって表示されます。このラベルの重なりを自動で回避する方法はありますでしょうか。
ドラッグにて移動させればよいのですが、データによっては重複値が多いものもあり、一つひとつ手作業で行うには手間がかかります。
もしくは、シート上で重複値を検索し、重複する値にはそれぞれ順番に0.1ずつ値を加算してずらし、プロットそのものをずらす……なんてことは可能でしょうか。
X Y X Y
A 4 7 A 4 7
B 6 12 → B 6 12
C10 16 C 10.1 16.1
D10 16 D 10.2 16.2
どなたかよいお知恵がございましたら、お貸しください。
< 使用 Excel:Excel2016、使用 OS:Windows10 >
こんにちは。
【試し方】 データラベル付きの散布図を作成します。 ・データラベルには「セルの値」を表示し、 ・「引き込み線を引く」にチェックを入れておいて下さい。
「グラフをアクティブにした状態」で、マクロmainを実行して下さい。
結果が思わしくなければ、再度mainを実行してみて下さい。 別の選択肢がでます。
まずはバックアップを取ったうえで、試して見て下さい。 少々長いですが、ひとつの標準モジュールにコピーして使います。
【参考コード】
Option Explicit
Dim n_labels As Long Dim myRect() As Variant Dim myRect2() As Variant Dim rectidx1() As Long Dim rectidx2() As Long Dim insideWidth As Double Dim insideHeight As Double
'アクティブな散布図のデータラベルの重なりをできるだけ回避 Sub main() Dim myChart As Chart
Set myChart = ActiveChart
Call pointLabel(myChart)
End Sub
Function pointLabel(myChart As Chart) Dim bestDisplacement As Variant
Call getMetrics(myChart)
'ラベルの位置情報などの取得 Call getRect(myChart)
'重なりあう可能性のあるラベルのペアを取得 Call canIntersect
'焼きなまし法による最適な変位の探索 bestDisplacement = simulatedAnnealing()
'ラベルの位置を実際に動かす Call setLabelPosition(myChart, bestDisplacement)
End Function
Function getMetrics(myChart As Chart) insideWidth = myChart.PlotArea.insideWidth insideHeight = myChart.PlotArea.insideHeight End Function
'ラベルの位置情報などを取得 Sub getRect(myChart As Chart) Dim mySeries As Series Dim dl As DataLabel Dim k& Dim l#, t#, r#, b#, width#, height#
Set mySeries = myChart.SeriesCollection(1) myChart.SetElement msoElementDataLabelCenter '''myChart.SetElement msoElementDataLabelRight
With mySeries.DataLabels .ShowRange = True '「セルの値」を表示 .ShowValue = False 'Y値を非表示 するものとした End With
n_labels = mySeries.Points.Count
ReDim myRect(1 To n_labels) ReDim myRect2(1 To n_labels)
For k = 1 To n_labels Set dl = mySeries.Points(k).DataLabel '位置は相対化するものとした。最後に元に戻す l = dl.Left / insideWidth t = dl.Top / insideHeight width = dl.width / insideWidth height = dl.height / insideHeight
myRect(k) = Array(l, t, l + width, t + height)
'仮にラベルの大きさが2倍だったとしたときのラベルの位置(動かす最大限) myRect2(k) = Array(l - 0.5 * width, t - 0.5 * height, _ l + width * 1.5, t + 1.5 * height) Next
End Sub
' 重なりあう可能性のあるラベルのペアを取得(ラベルの大きさが2倍と仮定) 'これ以外のペアはラベル位置を動かしても重なることはない。(計算負荷の軽減) Function canIntersect() Dim i&, j&, k&
k = 0 For i = 1 To n_labels For j = 1 To i - 1 If rect_intersect(myRect2(i), myRect2(j)) > 0 Then k = k + 1 ReDim Preserve rectidx1(1 To k) ReDim Preserve rectidx2(1 To k) rectidx1(k) = i rectidx2(k) = j End If Next Next End Function
' 焼きなまし法による探索(返り値は、最適な変位組み合わせ) Function simulatedAnnealing() As Variant ReDim displacement(1 To n_labels) As Variant Dim newdisplacement As Variant Dim bestDisplacement As Variant Dim score# Dim newscore# Dim bestscore# Dim temperature# Dim i&, j&, k& Dim l#, t#
'ラベルの変位codeを8に初期設定 For k = 1 To n_labels displacement(k) = 8 Next
score = makeScores(displacement) '「重なり度合い」の計算 bestDisplacement = displacement bestscore = score temperature = 0.001 ' パラメータ
For i = 1 To 50 k = 1 For j = 1 To 50 newdisplacement = displacement 'ランダムなラベルをランダムに変位させる newdisplacement(WorksheetFunction.RandBetween(1, n_labels)) _ = WorksheetFunction.RandBetween(1, 8) newscore = makeScores(newdisplacement)
'高温では緩い条件でも新しい変位に変更 (冒険を許す) '(低温になるとより厳しい条件で変更する)(保守的にする) If (newscore <= score Or Rnd() < Exp((score - newscore) / temperature)) Then k = k + 1 score = newscore displacement = newdisplacement End If If score <= bestscore Then bestscore = score bestDisplacement = displacement End If
If (bestscore = 0 Or k = 10) Then Exit For Next
If bestscore = 0 Then Exit For temperature = 0.9 * temperature '温度を一定率で低下させる Next
simulatedAnnealing = bestDisplacement End Function
'ラベルどうしの「重なり度合い」を評価(ただし、対象は、重なる可能性のあるラベルのペアだけに限定) Function makeScores(displacement) As Double Dim area# Dim k& Dim rect1, rect2
area = 0 For k = 1 To UBound(rectidx1) rect1 = myRect(rectidx1(k)) rect1 = make_rect(rect1, displacement(rectidx1(k)))
rect2 = myRect(rectidx2(k)) rect2 = make_rect(rect2, displacement(rectidx2(k))) area = area + rect_intersect(rect1, rect2) Next makeScores = area End Function
'変位 code(1〜8で指定) に応じて、移動後のラベル位置を返す Function make_rect(rect, code) As Variant Dim l#, t#, r#, b#, width#, height# Dim xdisp#, ydisp#
l = rect(0): t = rect(1): r = rect(2): b = rect(3) width = r - l height = t - b xdisp = Array(0, -1, -1, -1, 0, 0, 1, 1, 1)(code) * width / 2# ydisp = Array(0, -1, 0, 1, -1, 1, -1, 0, 1)(code) * height / 2#
make_rect = rect_displace(rect, xdisp, ydisp)
End Function
'ラベルの位置を指定した分だけずらす Function rect_displace(rect, xdisp, ydisp) As Variant rect_displace = Array(rect(0) + xdisp, rect(1) + ydisp, _ rect(2) + xdisp, rect(3) + ydisp) End Function
'ラベル rect1 と rect2 の重なった部分の面積を返す Function rect_intersect(rect1 As Variant, rect2 As Variant) As Double Dim l1#, t1#, r1#, b1# Dim l2#, t2#, r2#, b2# Dim w#, h#
l1 = rect1(0): t1 = rect1(1): r1 = rect1(2): b1 = rect1(3) l2 = rect2(0): t2 = rect2(1): r2 = rect2(2): b2 = rect2(3)
w = WorksheetFunction.Min(r1, r2) - WorksheetFunction.Max(l1, l2) h = WorksheetFunction.Min(b1, b2) - WorksheetFunction.Max(t1, t2)
If w <= 0 Then rect_intersect = 0 Exit Function End If If h <= 0 Then rect_intersect = 0 Exit Function End If rect_intersect = w * h
End Function
Function setLabelPosition(myChart As Chart, bestDisplacement As Variant) Dim k& Dim l#, t# ReDim myRectReal(1 To n_labels) As Variant
'データラベルの位置を実際に移動 For k = 1 To n_labels myRectReal(k) = make_rect(myRect(k), bestDisplacement(k)) l = myRectReal(k)(0) * insideWidth t = myRectReal(k)(1) * insideHeight myChart.SeriesCollection(1).Points(k).DataLabel.Left = l myChart.SeriesCollection(1).Points(k).DataLabel.Top = t Next End Function
(γ) 2020/10/21(水) 12:14
[ 一覧(最新更新順) ]
YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki.
Modified by kazu.