"""
Pure DNL/INL plotting functionality.
This module provides plotting functions for DNL and INL that can be used
with pre-computed values from compute_inl_from_sine.
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpecFromSubplotSpec
[文档]
def plot_dnl_inl(
code,
dnl,
inl,
num_bits=None,
show_title=True,
col_title=None,
axes=None,
ax=None,
color_dnl='b',
color_inl='b',
inl_code=None,
):
"""
Plot DNL and INL curves in a 2-row subplot layout.
Parameters
----------
code : array_like
Code values (x-axis)
dnl : array_like
DNL values in LSB (y-axis)
inl : array_like
INL values in LSB (y-axis)
inl_code : array_like, optional
Code or transition-code values for the INL x-axis. If omitted, ``code``
is used for both DNL and INL for backward compatibility.
num_bits : int, optional
Number of bits for x-axis limits. If None, uses code range.
show_title : bool, default=True
Show subplot titles with DNL/INL min/max ranges
col_title : str, optional
Column title to display above DNL plot (e.g., "N = 2^10")
axes : tuple of matplotlib.axes.Axes, optional
Pre-made axes tuple (dnl_ax, inl_ax). If provided, uses these directly.
ax : matplotlib.axes.Axes, optional
Single axis to split into 2 rows (auto-nested GridSpec). If None and axes=None, uses current axis.
color_dnl : str, default='r'
Color for DNL plot
color_inl : str, default='b'
Color for INL plot
Returns
-------
axes : tuple of matplotlib.axes.Axes
The axes objects [dnl_ax, inl_ax]
"""
# Use provided axes tuple, or split a single axis
if axes is not None:
# Use provided axes tuple directly
ax_dnl, ax_inl = axes
else:
# Single axis (or None), get current axis and split it
if ax is None:
ax = plt.gca()
fig = ax.get_figure()
# Split the axis into 2 rows using GridSpec
if hasattr(ax, 'get_subplotspec') and ax.get_subplotspec() is not None:
# Axis is part of a grid, use nested GridSpec
subplotspec = ax.get_subplotspec()
nested_gs = GridSpecFromSubplotSpec(2, 1, subplot_spec=subplotspec, hspace=0.4)
ax.remove()
ax_dnl = fig.add_subplot(nested_gs[0])
ax_inl = fig.add_subplot(nested_gs[1])
axes = (ax_dnl, ax_inl)
else:
# Axis not in a grid, replace it with 2-row layout
pos = ax.get_position()
ax.remove()
ax_dnl = fig.add_axes([pos.x0, pos.y0 + pos.height/2, pos.width, pos.height/2])
ax_inl = fig.add_axes([pos.x0, pos.y0, pos.width, pos.height/2])
axes = (ax_dnl, ax_inl)
# Plot DNL (top)
_plot_single_curve(axes[0], code, dnl, num_bits, 'DNL (LSB)', color_dnl)
# Plot INL (bottom)
inl_code = code if inl_code is None else inl_code
_plot_single_curve(axes[1], inl_code, inl, num_bits, 'INL (LSB)', color_inl)
# Set titles on subplots
if show_title:
dnl_min, dnl_max = np.min(dnl), np.max(dnl)
inl_min, inl_max = np.min(inl), np.max(inl)
# Column title on DNL axis if provided
if col_title is not None:
axes[0].set_title(f'{col_title}\nDNL = [{dnl_min:.2f}, {dnl_max:.2f}] LSB', fontweight='bold', fontsize=10)
else:
axes[0].set_title(f'DNL = [{dnl_min:.2f}, {dnl_max:.2f}] LSB', fontweight='bold', fontsize=10)
axes[1].set_title(f'INL = [{inl_min:.2f}, {inl_max:.2f}] LSB', fontweight='bold', fontsize=10)
return axes
def _plot_single_curve(ax, code, data, num_bits=None, ylabel='Data (LSB)', color='r'):
"""
Helper function to plot a single DNL or INL curve.
Parameters
----------
ax : matplotlib.axes.Axes
Axes object to plot on
code : array_like
Code values (x-axis)
data : array_like
Data values in LSB (y-axis)
num_bits : int, optional
Number of bits for x-axis limits. If None, uses code range.
ylabel : str, default='Data (LSB)'
Y-axis label
color : str, default='r'
Line color
"""
code = np.asarray(code)
if num_bits is not None:
full_scale = 2**num_bits
x_values = code / full_scale
else:
x_values = code
# Plot curve
ax.plot(x_values, data, f'{color}-', linewidth=0.5)
# Add reference lines
ax.axhline(1.0, color='gray', linestyle='--', linewidth=1.2, alpha=0.8)
ax.axhline(-1.0, color='gray', linestyle='--', linewidth=1.2, alpha=0.8)
# Always show grid and labels
ax.grid(True, alpha=0.3)
ax.set_ylabel(ylabel)
# Set x-axis limits
if num_bits is not None:
ax.set_xlim([0, 1])
ax.set_xlabel(f'Code (2^{num_bits} LSB)')
ax.set_xticks([0, 0.25, 0.5, 0.75, 1.0])
ax.set_xticklabels(['0', '0.25', '0.5', '0.75', '1'])
else:
ax.set_xlim([np.min(code), np.max(code)])
ax.set_xlabel('Code (LSB)')
# Set y-axis limits: minimum ±1, or 1.2x data range if larger
data_min, data_max = np.min(data), np.max(data)
data_range = max(abs(data_min), abs(data_max))
if data_range <= 1.0:
ax.set_ylim([-1, 1])
else:
ax.set_ylim([data_min * 1.2, data_max * 1.2])