You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

关于Pandas/Matplotlib可平移缩放时序图及实时更新等功能的技术问询

Great question! You’re absolutely right that modifying Matplotlib’s source code is overkill—there are built-in tools and straightforward workarounds to meet all your requirements without reinventing the wheel. Let’s walk through a complete solution that checks every box, plus a better way to generate your initial data.

First: A Simpler Way to Generate Your Time Series

Your original loop works, but we can make this much more efficient with Pandas and NumPy, no manual iteration needed:

import pandas as pd
import datetime
import math
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
from matplotlib.animation import FuncAnimation
import numpy as np
import threading
import time

# Generate initial 1000-day data (cleaner, faster approach)
N = 1000
start_date = datetime.datetime.now() - datetime.timedelta(days=N)
dates = pd.date_range(start=start_date, periods=N, freq='D')
y_values = np.sin(0.1 * np.arange(N))
df = pd.DataFrame({'y': y_values}, index=dates)

Full Implementation of Your Required Features

Here’s code that covers all your requested functionality:

# Set up the plot
fig, ax = plt.subplots(figsize=(10, 6))
line, = ax.plot(df.index, df['y'], linewidth=2)

# --- UI Optimization ---
fig.patch.set_facecolor('#f0f0f0')  # Gray background for the figure
ax.set_facecolor('#e0e0e0')         # Lighter gray for the plot area
plt.rcParams['toolbar'] = 'None'    # Remove default bottom toolbar

# --- 1. Default Click-Drag Panning ---
dragging = False
last_x_position = 0

def on_mouse_press(event):
    global dragging, last_x_position
    if event.inaxes == ax:
        dragging = True
        last_x_position = event.xdata

def on_mouse_release(event):
    global dragging
    dragging = False

def on_mouse_move(event):
    global last_x_position
    if dragging and event.inaxes == ax:
        # Calculate pan distance
        x_shift = event.xdata - last_x_position
        current_x_min, current_x_max = ax.get_xlim()
        
        # --- 2. Prevent Panning Beyond Last Date ---
        new_x_max = current_x_max - x_shift
        if new_x_max <= df.index[-1]:
            ax.set_xlim(current_x_min - x_shift, new_x_max)
        else:
            # Lock right boundary to last date, adjust left boundary accordingly
            ax.set_xlim(df.index[-1] - (current_x_max - current_x_min), df.index[-1])
        
        last_x_position = event.xdata
        fig.canvas.draw_idle()

# Bind mouse events to enable dragging
fig.canvas.mpl_connect('button_press_event', on_mouse_press)
fig.canvas.mpl_connect('button_release_event', on_mouse_release)
fig.canvas.mpl_connect('motion_notify_event', on_mouse_move)

# --- 3. X-Axis Zoom +/- Buttons ---
# Create button axes
ax_zoom_in = plt.axes([0.82, 0.01, 0.07, 0.04])
ax_zoom_out = plt.axes([0.73, 0.01, 0.07, 0.04])

# Style and initialize buttons
btn_zoom_in = Button(ax_zoom_in, 'Zoom In', color='#cccccc', hovercolor='#999999')
btn_zoom_out = Button(ax_zoom_out, 'Zoom Out', color='#cccccc', hovercolor='#999999')

def zoom_in(event):
    x_min, x_max = ax.get_xlim()
    zoom_factor = 0.8  # Shrink view by 20%
    new_width = (x_max - x_min) * zoom_factor
    center = (x_min + x_max) / 2
    ax.set_xlim(center - new_width/2, center + new_width/2)
    fig.canvas.draw_idle()

def zoom_out(event):
    x_min, x_max = ax.get_xlim()
    zoom_factor = 1.2  # Expand view by 20%
    new_width = (x_max - x_min) * zoom_factor
    center = (x_min + x_max) / 2
    ax.set_xlim(center - new_width/2, center + new_width/2)
    fig.canvas.draw_idle()

# Link buttons to zoom functions
btn_zoom_in.on_clicked(zoom_in)
btn_zoom_out.on_clicked(zoom_out)

# --- 4. Real-Time Data Updates with Auto-Scroll ---
# Simulate background thread adding new data (replace with your actual data source)
def add_new_data_continuously():
    global df
    while True:
        time.sleep(1)  # Add a new point every second
        new_date = df.index[-1] + datetime.timedelta(days=1)
        new_y = math.sin(0.1 * len(df))
        # Thread-safe update (use a lock in production if multiple threads write)
        df.loc[new_date] = new_y

# Start the background thread
data_thread = threading.Thread(target=add_new_data_continuously, daemon=True)
data_thread.start()

# Update plot to reflect new data
def update_plot(frame):
    # Refresh the line with latest data
    line.set_data(df.index, df['y'])
    
    # Auto-scroll X-axis to show new data (only if we're near the right edge)
    current_x_max = ax.get_xlim()[1]
    if current_x_max >= df.index[-2]:
        # Keep the same view width, shift to show the new date
        ax.set_xlim(df.index[-1] - (current_x_max - ax.get_xlim()[0]), df.index[-1])
    
    fig.canvas.draw_idle()
    return line,

# Run the animation to update the plot every 500ms
ani = FuncAnimation(fig, update_plot, interval=500, blit=True)

# Adjust layout to fit zoom buttons
plt.tight_layout(rect=[0, 0.06, 1, 1])
plt.show()

Breakdown of How This Meets Your Needs

  • Default drag-panning: We bind mouse press/release/move events directly to the plot, so you can drag to pan without clicking any toolbar icons.
  • No panning beyond last date: The on_mouse_move function checks if the new right boundary would exceed the latest date, and locks it in place if so.
  • Zoom buttons: Uses Matplotlib's built-in Button widget to add custom zoom controls at the bottom of the plot.
  • Real-time updates: A background thread adds new data, and FuncAnimation refreshes the plot periodically. The auto-scroll logic ensures new data stays visible if you're already viewing the end of the series.
  • UI polish: Hides the default toolbar, sets gray backgrounds, and adjusts layout to avoid overlapping elements.

Bonus: Third-Party Tools to Simplify Even More

If you want to avoid writing custom mouse event handlers, libraries like mpl_interactions (built on Matplotlib) provide out-of-the-box drag-panning and zooming with minimal code. For even more interactive features, you could also explore Plotly (though it's not strictly Matplotlib/Pandas, it integrates well with Pandas DataFrames).

内容的提问来源于stack exchange,提问作者Basj

火山引擎 最新活动