如何用Python自定义Matplotlib坐标轴缩放以清晰展示环形测量数据?
Absolutely! You can achieve this custom axis scaling in Python with Matplotlib by creating a custom scale transformation—it’ll let you compress the inner -145 to 145mm range while zooming in on the critical 145–150mm (and -145–-150mm) qualified interval. Here’s a tailored, step-by-step solution:
Step 1: Define the Custom Scale Transformation
Matplotlib lets you build custom scales by subclassing matplotlib.scale.ScaleBase. We’ll create a scale that squashes the inner range and stretches the outer (qualified) ranges to make your measurements easier to inspect:
import matplotlib.pyplot as plt import matplotlib.scale as mscale import matplotlib.transforms as mtransforms import pandas as pd import numpy as np class CustomRadialScale(mscale.ScaleBase): name = 'custom_radial' def __init__(self, axis, **kwargs): super().__init__(axis) # Critical thresholds from your requirements self.inner_threshold = 145 self.upper_limit = 150 # Adjust this to control inner range compression (smaller = more compression) self.compression_factor = 0.2 def get_transform(self): return self.CustomRadialTransform(self.inner_threshold, self.upper_limit, self.compression_factor) def set_default_locators_and_formatters(self, axis): # Place ticks at key thresholds for clarity axis.set_major_locator(plt.FixedLocator([-150, -145, 0, 145, 150])) axis.set_major_formatter(plt.FormatStrFormatter('%d')) class CustomRadialTransform(mtransforms.Transform): input_dims = 1 output_dims = 1 is_separable = True def __init__(self, inner_threshold, upper_limit, compression_factor): super().__init__() self.inner_thresh = inner_threshold self.upper_limit = upper_limit self.compression = compression_factor self.stretch_range = upper_limit - inner_threshold def transform_non_affine(self, x): # Handle positive values: compress inner, stretch outer pos_mask = x >= 0 pos_x = x[pos_mask] pos_transformed = np.where( pos_x <= self.inner_thresh, pos_x * self.compression, self.inner_thresh * self.compression + (pos_x - self.inner_thresh) * (1 - self.inner_thresh * self.compression) / self.stretch_range ) # Handle negative values (mirror logic) neg_mask = x < 0 neg_x = x[neg_mask] neg_transformed = np.where( neg_x >= -self.inner_thresh, neg_x * self.compression, -self.inner_thresh * self.compression + (neg_x + self.inner_thresh) * (1 - self.inner_thresh * self.compression) / self.stretch_range ) # Combine results result = np.empty_like(x) result[pos_mask] = pos_transformed result[neg_mask] = neg_transformed return result def inverted(self): return CustomRadialScale.InvertedCustomRadialTransform(self.inner_thresh, self.upper_limit, self.compression) class InvertedCustomRadialTransform(mtransforms.Transform): input_dims = 1 output_dims = 1 is_separable = True def __init__(self, inner_threshold, upper_limit, compression_factor): super().__init__() self.inner_thresh = inner_threshold self.upper_limit = upper_limit self.compression = compression_factor self.stretch_range = upper_limit - inner_threshold def transform_non_affine(self, y): # Reverse the transformation for axis ticks pos_mask = y >= 0 pos_y = y[pos_mask] pos_inverted = np.where( pos_y <= self.inner_thresh * self.compression, pos_y / self.compression, self.inner_thresh + (pos_y - self.inner_thresh * self.compression) * self.stretch_range / (1 - self.inner_thresh * self.compression) ) neg_mask = y < 0 neg_y = y[neg_mask] neg_inverted = np.where( neg_y >= -self.inner_thresh * self.compression, neg_y / self.compression, -self.inner_thresh + (neg_y + self.inner_thresh * self.compression) * self.stretch_range / (1 - self.inner_thresh * self.compression) ) result = np.empty_like(y) result[pos_mask] = pos_inverted result[neg_mask] = neg_inverted return result def inverted(self): return CustomRadialScale.CustomRadialTransform(self.inner_thresh, self.upper_limit, self.compression) # Register the custom scale with Matplotlib mscale.register_scale(CustomRadialScale)
Step 2: Integrate with Your Data and Plot
Since you’re working with a circular plot, switching to polar coordinates will make the visualization more intuitive (matches your "CirclPlot" description). Here’s how to adapt your existing code:
# Read your CSV data EBRData = pd.read_csv('C://Users/vanderey/Documents/MATLAB/EBRTest2.csv', header=0) # Extract data columns Rx = EBRData['xCoat'] Ry = EBRData['yCoat'] RLSLx = EBRData['xCoat_LSL'] RLSLy = EBRData['yCoat_LSL'] RUSLx = EBRData['xCoat_USL'] RUSLy = EBRData['yCoat_USL'] # Convert Cartesian coordinates to polar (r, theta) for natural circular plotting def cart_to_polar(x, y): r = np.sqrt(x**2 + y**2) theta = np.arctan2(y, x) return r, theta r_meas, theta_meas = cart_to_polar(Rx, Ry) r_lsl, theta_lsl = cart_to_polar(RLSLx, RLSLy) r_usl, theta_usl = cart_to_polar(RUSLx, RUSLy) # Create the plot with custom scaling my_dpi = 96 plt.figure(figsize=(480/my_dpi, 480/my_dpi), dpi=my_dpi) ax = plt.subplot(projection='polar') # Plot your measurements and limits ax.plot(theta_meas, r_meas, color='blue', marker='.', linewidth=1, alpha=0.4, label='Measurements') ax.plot(theta_lsl, r_lsl, color='red', marker='.', linewidth=1, alpha=0.4, label='LSL (146mm)') ax.plot(theta_usl, r_usl, color='red', marker='.', linewidth=1, alpha=0.4, label='USL (150mm)') # Apply the custom radial scale ax.set_rscale('custom_radial') # Adjust plot settings for clarity ax.set_title('EBR - Custom Scaled Circular Plot', y=1.1) ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1)) plt.show()
Key Customization Tips
- Tweak Compression: Modify the
compression_factorinCustomRadialScaleto control how much the inner range is squashed (smaller values = more zoom on the qualified range). - Cartesian Alternative: If you prefer to stick with your original Cartesian plot, replace the polar subplot setup with standard axes and apply the scale to both x and y:
fig, ax = plt.subplots(figsize=(480/my_dpi, 480/my_dpi), dpi=my_dpi) ax.plot(Rx, Ry, color='blue', marker='.', linewidth=1, alpha=0.4) ax.set_xscale('custom_radial') ax.set_yscale('custom_radial') - Tick Adjustments: Update the
FixedLocatorvalues if you need to show additional tick marks for reference.
内容的提问来源于stack exchange,提问作者Jelle

