軸座標(matplotlib transformations tutorialで定義されている)で関連するアーティスト(この場合はティックとチックラベルのみ)を含むプロットの範囲を見つける必要があります。軸座標でのマットプロット図(ティックラベルを含む)の範囲の検索
バックグラウンドは、元のプロットのデータが不明瞭にならないようにサムネイルを配置できる場合にのみ、多数のチャートのサムネイルプロット(this SO questionなど)を自動的に作成することです。
これは私の現在のアプローチです:
- は、元のプロットの右上に始まり、元のプロットや移動の左、そして右下の作業、テストするために候補矩形の数を作成します。左。各候補矩形の
- :this SO questionからコードを使用
- 矩形がカバーするX-データのどのスライスを見つけるために、データの座標に(軸座標で)四角形の左側と右側に変換します。
- 矩形がカバーするデータスライスの最小/最大y値を検索します。
- 矩形の上と下をデータ座標で検索します。
- 上記を使用して、矩形がどのデータとも重なっているかどうかを判断します。そうでない場合は、現在の矩形にサムネイルプロットを描画し、それ以外の場合は続行します。
このアプローチの問題点は、軸座標が(1,1)
に(0,0)
(左下軸)からあなたの軸の程度を与えることである(右上)およびマダニとticklabelsを(含まれていませんサムネイルプロットには、タイトル、軸ラベル、凡例または他のアーティストはありません)。
すべてのグラフは同じフォントサイズを使用しますが、チャートの長さはさまざまです(例:1.5
または1.2345 * 10^6
)。フォントサイズ/ポイントを軸座標に変換する方法はありますか?あるいは、おそらく、上記のものより優れたアプローチがあります(バウンディングボックス?)。
次のコードは、上記のアルゴリズムを実装しています
import math
from matplotlib import pyplot, rcParams
rcParams['xtick.direction'] = 'out'
rcParams['ytick.direction'] = 'out'
INSET_DEFAULT_WIDTH = 0.35
INSET_DEFAULT_HEIGHT = 0.25
INSET_PADDING = 0.05
INSET_TICK_FONTSIZE = 8
def axis_data_transform(axis, xin, yin, inverse=False):
"""Translate between axis and data coordinates.
If 'inverse' is True, data coordinates are translated to axis coordinates,
otherwise the transformation is reversed.
Code by Covich, from: https://stackoverflow.com/questions/29107800/
"""
xlim, ylim = axis.get_xlim(), axis.get_ylim()
xdelta, ydelta = xlim[1] - xlim[0], ylim[1] - ylim[0]
if not inverse:
xout, yout = xlim[0] + xin * xdelta, ylim[0] + yin * ydelta
else:
xdelta2, ydelta2 = xin - xlim[0], yin - ylim[0]
xout, yout = xdelta2/xdelta, ydelta2/ydelta
return xout, yout
def add_inset_to_axis(fig, axis, rect):
left, bottom, width, height = rect
def transform(coord):
return fig.transFigure.inverted().transform(
axis.transAxes.transform(coord))
fig_left, fig_bottom = transform((left, bottom))
fig_width, fig_height = transform([width, height]) - transform([0, 0])
return fig.add_axes([fig_left, fig_bottom, fig_width, fig_height])
def collide_rect((left, bottom, width, height), fig, axis, data):
# Find the values on the x-axis of left and right edges of the rect.
x_left_float, _ = axis_data_transform(axis, left, 0, inverse=False)
x_right_float, _ = axis_data_transform(axis, left + width, 0, inverse=False)
x_left = int(math.floor(x_left_float))
x_right = int(math.ceil(x_right_float))
# Find the highest and lowest y-value in that segment of data.
minimum_y = min(data[int(x_left):int(x_right)])
maximum_y = max(data[int(x_left):int(x_right)])
# Convert the bottom and top of the rect to data coordinates.
_, inset_top = axis_data_transform(axis, 0, bottom + height, inverse=False)
_, inset_bottom = axis_data_transform(axis, 0, bottom, inverse=False)
# Detect collision.
if ((bottom > 0.5 and maximum_y > inset_bottom) or # inset at top of chart
(bottom < 0.5 and minimum_y < inset_top)): # inset at bottom
return True
return False
if __name__ == '__main__':
x_data, y_data = range(0, 100), [-1.0] * 50 + [1.0] * 50 # Square wave.
y_min, y_max = min(y_data), max(y_data)
fig = pyplot.figure()
axis = fig.add_subplot(111)
axis.set_ylim(y_min - 0.1, y_max + 0.1)
axis.plot(x_data, y_data)
# Find a rectangle that does not collide with data. Start top-right
# and work left, then try bottom-right and work left.
inset_collides = False
left_offsets = [x/10.0 for x in xrange(6)] * 2
bottom_values = (([1.0 - INSET_DEFAULT_HEIGHT - INSET_PADDING] * (len(left_offsets)/2))
+ ([INSET_PADDING * 2] * (len(left_offsets)/2)))
for left_offset, bottom in zip(left_offsets, bottom_values):
# rect: (left, bottom, width, height)
rect = (1.0 - INSET_DEFAULT_WIDTH - left_offset - INSET_PADDING,
bottom, INSET_DEFAULT_WIDTH, INSET_DEFAULT_HEIGHT)
inset_collides = collide_rect(rect, fig, axis, y_data)
print 'TRYING:', rect, 'RESULT:', inset_collides
if not inset_collides:
break
if not inset_collides:
inset = add_inset_to_axis(fig, axis, rect)
inset.set_ylim(axis.get_ylim())
inset.set_yticks([y_min, y_min + ((y_max - y_min)/2.0), y_max])
inset.xaxis.set_tick_params(labelsize=INSET_TICK_FONTSIZE)
inset.yaxis.set_tick_params(labelsize=INSET_TICK_FONTSIZE)
inset_xlimit = (0, int(len(y_data)/100.0 * 2.5)) # First 2.5% of data.
inset.set_xlim(inset_xlimit[0], inset_xlimit[1], auto=False)
inset.plot(x_data[inset_xlimit[0]:inset_xlimit[1] + 1],
y_data[inset_xlimit[0]:inset_xlimit[1] + 1])
fig.savefig('so_example.png')
そして、これの出力は次のとおりです。
TRYING: (0.6, 0.7, 0.35, 0.25) RESULT: True
TRYING: (0.5, 0.7, 0.35, 0.25) RESULT: True
TRYING: (0.4, 0.7, 0.35, 0.25) RESULT: True
TRYING: (0.30000000000000004, 0.7, 0.35, 0.25) RESULT: True
TRYING: (0.2, 0.7, 0.35, 0.25) RESULT: True
TRYING: (0.10000000000000002, 0.7, 0.35, 0.25) RESULT: False
感謝:
最後に、ここに私の変更や追加を使用して完全なコードです。私が元のコード(ここの例がベースになっている)に移植したとき、 'show()'は正常に動作していたようでした。私はPDFレンダラを使用していると思われます。ちなみに、 'inset_bbox = inset.bbox.inverse_transformed(axis.transAxes)'の行は、 'inset_bbox'が決して読み込まれないので、冗長かもしれません。 – snim2
あなたはそうです、その行は以前の反復から残されました。そして、わかりやすくそれを削除しました。 'renderer'のものを検索すると、バックエンドに応じてヒットしたりミスしたりするようです。特に問題があるのは、mac osです。 'tight_layout'や[この質問]への回答をインポートするのではなく、' renderer = fig.canvas.get_renderer() 'を使ってみることもできます(http://stackoverflow.com/questions/22667224/matplotlib-get-text-bounding -box-independent-back-endend)を指定します。とにかく、すでに動作していれば、それは素晴らしいです。お役に立てて嬉しいです! –