diff --git a/main.py b/main.py index e1fe8cd..0276c28 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ import matplotlib.pyplot as plt import pandas as pd from matplotlib.patches import FancyArrowPatch -from matplotlib.widgets import Slider +from matplotlib.widgets import Button, Slider from mpl_toolkits.mplot3d import proj3d connection = [ @@ -28,11 +28,17 @@ # Configure vectors to draw (list of (start_index, end_index) using 0-based indices) # Edit this list to add/remove vectors to display -VECTOR_PAIRS = [(4, 5), (12, 13)] +VECTOR_PAIRS = [(4, 5), (9, 8)] # Optional colors (will cycle if fewer colors than VECTOR_PAIRS) -VECTOR_COLORS = ["r", "b"] +VECTOR_COLORS = ["r", "g"] # Line width for vector arrows VECTOR_LW = 4 +# Optional labels for vectors (defaults to v1, v2, ... if None) +VECTOR_LABELS = None +# Label offset factors: fraction of vector (x/y) and fraction of z-range to offset label position +# Increase these to place labels further from the vector +LABEL_OFFSET_XY = 0.12 +LABEL_OFFSET_Z_FACTOR = 0.05 # 3D arrow helper to draw arrows between 3D points @@ -44,6 +50,9 @@ def draw(self, renderer): xs3d, ys3d, zs3d = self._verts3d xs, ys, _ = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.get_proj()) + # If projected endpoints are effectively identical, skip drawing to avoid path errors + if abs(xs[0] - xs[1]) < 1e-6 and abs(ys[0] - ys[1]) < 1e-6: + return self.set_positions((xs[0], ys[0]), (xs[1], ys[1])) super().draw(renderer) @@ -158,6 +167,32 @@ ax.add_artist(a) arrows.append(a) + # Create labels for each configured vector (v1, v2, ... by default) + arrow_labels = [] + labels = ( + VECTOR_LABELS + if VECTOR_LABELS is not None + else [f"v{i + 1}" for i in range(len(VECTOR_PAIRS))] + ) + z_range = z_max - z_min if z_max > z_min else 1.0 + for i_pair, (idx1, idx2) in enumerate(VECTOR_PAIRS): + x1, y1, z1 = points[idx1] + x2, y2, z2 = points[idx2] + mid_x = 0.5 * (x1 + x2) + LABEL_OFFSET_XY * (x2 - x1) + mid_y = 0.5 * (y1 + y2) + LABEL_OFFSET_XY * (y2 - y1) + mid_z = 0.5 * (z1 + z2) + LABEL_OFFSET_Z_FACTOR * z_range + label = labels[i_pair] + lbl = ax.text( + mid_x, + mid_y, + mid_z, + label, + color=VECTOR_COLORS[i_pair % len(VECTOR_COLORS)] if VECTOR_COLORS else "k", + fontsize=10, + weight="bold", + ) + arrow_labels.append(lbl) + ax.set_xlabel("X") ax.set_ylabel("Y") ax.set_zlabel("Z") @@ -199,11 +234,30 @@ [points[idx1][2], points[idx2][2]], ) + # Update arrow labels positions + for i_pair, (idx1, idx2) in enumerate(VECTOR_PAIRS): + x1, y1, z1 = points[idx1] + x2, y2, z2 = points[idx2] + mid_x = 0.5 * (x1 + x2) + LABEL_OFFSET_XY * (x2 - x1) + mid_y = 0.5 * (y1 + y2) + LABEL_OFFSET_XY * (y2 - y1) + mid_z = 0.5 * (z1 + z2) + LABEL_OFFSET_Z_FACTOR * (z_max - z_min) + arrow_labels[i_pair].set_position((mid_x, mid_y, mid_z)) + ax.set_title(f"Frame {data[frame_idx].frame_number}") fig.canvas.draw_idle() slider.on_changed(update) + # Button: switch to top-down (Z軸方向=真上) の視点に切り替える + ax_button = plt.axes([0.02, 0.02, 0.1, 0.04]) + btn_top = Button(ax_button, "X-Y plane") + + def set_top_view(event): + ax.view_init(elev=90, azim=0) + fig.canvas.draw_idle() + + btn_top.on_clicked(set_top_view) + plt.show()