Skip to content

Latest commit

 

History

History
1071 lines (808 loc) · 30.9 KB

File metadata and controls

1071 lines (808 loc) · 30.9 KB

ShenBi (神笔) Complete Tutorial

matplotlib syntax + pyqtgraph performance = ShenBi

ShenBi is a Python visualization library providing a fully matplotlib-compatible API, powered by pyqtgraph's high-performance rendering engine. Simply replace import matplotlib.pyplot as plt with import shenbi.pyplot as plt — no other code changes needed.

中文教程

Table of Contents


Quick Start

Installation

pip install shenbi
# or development mode
git clone https://github.com/CodeOfMe/ShenBi.git
cd ShenBi
pip install -e .

First Plot

import shenbi.pyplot as plt
import numpy as np

x = np.linspace(0, 4 * np.pi, 10000)
plt.figure(figsize=(10, 5))
plt.plot(x, np.sin(x), 'r-', linewidth=2, label='sin(x)')
plt.plot(x, np.cos(x), 'b--', linewidth=2, label='cos(x)')
plt.title('Sine and Cosine')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.savefig('first_plot.png')
plt.close()

Switching from matplotlib

# Just change one line — everything else stays the same
# import matplotlib.pyplot as plt   ← old
import shenbi.pyplot as plt          # ← new

Core Concepts

Default Styling

  • White background — all plots use white background (matching matplotlib)
  • No grid — grid is OFF by default; call plt.grid(True) to enable
  • Tab10 colours — automatic tab10 colour cycling

Color System

from shenbi.colors import TAB10_COLORS, resolve_color
from shenbi.cm import get_cmap

# Basic colours
plt.plot(x, y, 'r-')                   # single letter
plt.plot(x, y, color='steelblue')       # CSS4 name
plt.plot(x, y, color='#1f77b4')         # hex

# Colormaps
cmap = get_cmap('viridis')
colours = cmap(np.linspace(0, 1, 10))   # returns RGBA array

Supported Colormaps

Category Names
Perceptually Uniform viridis, plasma, inferno, magma, cividis
Diverging coolwarm, bwr, seismic
Sequential Blues, Reds, Greens, Oranges, Purples, Greys
Cyclic twilight, twilight_shifted
Classic jet, cool, hot, spring, summer, autumn, winter

2D Dataset Visualization

This tutorial uses 5 standard datasets. Each example is fully self-contained and runnable.

Loading Datasets

import numpy as np
import shenbi.pyplot as plt
from shenbi.colors import TAB10_COLORS
from shenbi.cm import get_cmap

from sklearn.datasets import load_iris, load_wine, load_breast_cancer, load_digits, load_diabetes

iris = load_iris()
wine = load_wine()
cancer = load_breast_cancer()
digits = load_digits()
diabetes = load_diabetes()

Example 1: Iris Scatter by Species

Sepal length vs sepal width, coloured by species.

X, y = iris.data, iris.target
names = iris.target_names
feat = iris.feature_names

plt.figure(figsize=(8, 6))
for i, name in enumerate(names):
    mask = y == i
    plt.scatter(X[mask, 0], X[mask, 1], s=40, c=TAB10_COLORS[i],
                alpha=0.7, edgecolors='white', linewidths=0.5, label=name)
plt.title('Iris — Sepal Length vs Sepal Width')
plt.xlabel(f'{feat[0]} (cm)')
plt.ylabel(f'{feat[1]} (cm)')
plt.legend()
plt.savefig('ds01_iris_sepal.png')
plt.close()

Iris — Sepal Length vs Sepal Width

Key point: plt.scatter() supports edgecolors and linewidths. alpha controls opacity.


Example 2: Iris Scatter with Colormap

Mapping species labels to the viridis colormap.

plt.figure(figsize=(8, 6))
scatter = plt.scatter(X[:, 2], X[:, 3], s=50, c=y, cmap='viridis',
                       alpha=0.8, edgecolors='white', linewidths=0.5)
plt.title('Iris — Petal Length vs Petal Width')
plt.xlabel(f'{feat[2]} (cm)')
plt.ylabel(f'{feat[3]} (cm)')
plt.colorbar(scatter, label='Species')
plt.savefig('ds02_iris_petal_cmap.png')
plt.close()

Iris — Petal Length vs Petal Width (Colormap)

Key point: Passing c=y (numeric array) auto-applies the colormap. plt.colorbar() adds a colour bar.


Example 3: Iris Boxplots

Feature distributions across the three species.

fig, axes = plt.subplots(2, 2, figsize=(14, 10))
for ax_idx, ax in enumerate(axes.flat):
    data_by_class = [X[y == i, ax_idx] for i in range(3)]
    ax.boxplot(data_by_class, tick_labels=names)
    ax.set_title(f'{feat[ax_idx]}')
    ax.set_ylabel('cm')
plt.suptitle('Iris — Feature Distribution by Species')
plt.tight_layout()
plt.savefig('ds03_iris_boxplot.png')
plt.close()

Iris — Feature Boxplots by Species

Key point: boxplot() accepts a list of groups and auto-computes quartiles & outliers.


Example 4: Iris Histogram Overlay

Overlaid petal-length histograms by species.

plt.figure(figsize=(10, 6))
for i, name in enumerate(names):
    mask = y == i
    plt.hist(X[mask, 2], bins=20, alpha=0.5, color=TAB10_COLORS[i],
             edgecolor='white', linewidth=0.5, label=name)
plt.title('Iris — Petal Length Distribution by Species')
plt.xlabel(f'{feat[2]} (cm)')
plt.ylabel('Count')
plt.legend()
plt.savefig('ds04_iris_hist.png')
plt.close()

Iris — Petal Length Distribution

Key point: alpha=0.5 makes overlaps visible. edgecolor='white' adds bar separation.


Example 5: Iris Grouped Bar Chart

Mean feature values per species.

means = np.array([X[y == i].mean(axis=0) for i in range(3)])
x_pos = np.arange(len(feat))
width = 0.25

plt.figure(figsize=(12, 6))
for i, name in enumerate(names):
    plt.bar(x_pos + i * width, means[i], width, label=name,
            color=TAB10_COLORS[i], edgecolor='white', linewidth=0.5)
plt.xticks(x_pos + width, feat, rotation=45, ha='right')
plt.title('Iris — Mean Feature Values by Species')
plt.ylabel('Mean Value (cm)')
plt.legend()
plt.savefig('ds05_iris_bar.png')
plt.close()

Iris — Mean Feature Values

Key point: Grouped bars via x-position offset. rotation=45 rotates labels.


Example 6: Wine Bar Chart

Mean values of first 6 features across 3 wine classes.

wine_data, wine_y = wine.data, wine.target
wine_names = wine.target_names
wine_feat = wine.feature_names[:6]
wine_means = np.array([wine_data[wine_y == i, :6].mean(axis=0) for i in range(3)])

plt.figure(figsize=(12, 6))
x_pos = np.arange(6)
width = 0.25
for i, name in enumerate(wine_names):
    plt.bar(x_pos + i * width, wine_means[i], width, label=name,
            color=TAB10_COLORS[i], edgecolor='white', linewidth=0.5)
plt.xticks(x_pos + width, wine_feat, rotation=45, ha='right')
plt.title('Wine — Mean Feature Values by Class')
plt.ylabel('Mean Value')
plt.legend()
plt.savefig('ds06_wine_bar.png')
plt.close()

Wine — Mean Feature Values


Example 7: Wine Error Bar Scatter

Mean ± std for two features per wine class.

plt.figure(figsize=(8, 6))
for i, name in enumerate(wine_names):
    mask = wine_y == i
    x_mean, y_mean = wine_data[mask, 0].mean(), wine_data[mask, 1].mean()
    x_std, y_std = wine_data[mask, 0].std(), wine_data[mask, 1].std()
    plt.errorbar([x_mean], [y_mean], xerr=[x_std], yerr=[y_std], fmt='o',
                 color=TAB10_COLORS[i], markersize=10, capsize=5,
                 label=f'{name} (mean±std)')
plt.title('Wine — Feature 0 vs Feature 1 (mean ± std)')
plt.xlabel(wine_feat[0])
plt.ylabel(wine_feat[1])
plt.legend()
plt.savefig('ds07_wine_errorbar.png')
plt.close()

Wine — Error Bar Scatter

Key point: Key point: plt.errorbar() supports xerr and yerr. capsize controls cap width.


Example 8: Breast Cancer Feature Comparison

Malignant vs benign — mean of first 10 features.

cancer_data, cancer_y = cancer.data, cancer.target
cancer_names = cancer.target_names
cancer_feat = cancer.feature_names[:10]
cancer_means = np.array([cancer_data[cancer_y == i, :10].mean(axis=0) for i in range(2)])

plt.figure(figsize=(14, 6))
x_pos = np.arange(10)
width = 0.35
plt.bar(x_pos - width/2, cancer_means[0], width, label=cancer_names[0],
        color='#d62728', edgecolor='white', linewidth=0.5)
plt.bar(x_pos + width/2, cancer_means[1], width, label=cancer_names[1],
        color='#2ca02c', edgecolor='white', linewidth=0.5)
plt.xticks(x_pos, cancer_feat, rotation=45, ha='right')
plt.title('Breast Cancer — Mean Feature Values')
plt.ylabel('Mean Value')
plt.legend()
plt.savefig('ds08_cancer_bar.png')
plt.close()

Breast Cancer — Malignant vs Benign


Example 9: Breast Cancer Boxplots

5 key features across malignant/benign.

fig, axes = plt.subplots(1, 5, figsize=(16, 5))
top_feats = [0, 1, 2, 3, 4]
for idx, ax in enumerate(axes):
    feat_idx = top_feats[idx]
    data_by_class = [cancer_data[cancer_y == i, feat_idx] for i in range(2)]
    ax.boxplot(data_by_class, tick_labels=cancer_names)
    ax.set_title(cancer_feat[feat_idx])
plt.suptitle('Breast Cancer — Feature Distribution by Diagnosis')
plt.tight_layout()
plt.savefig('ds09_cancer_boxplot.png')
plt.close()

Breast Cancer — Feature Boxplots


Example 10: Digits Image Display

15 sample digit images (8×8 pixels).

digits_images = digits.images
digits_y = digits.target

fig, axes = plt.subplots(3, 5, figsize=(12, 8))
axes = axes.flatten()
for i, ax in enumerate(axes):
    ax.imshow(digits_images[i], cmap='Greys', vmin=0, vmax=16)
    ax.set_title(f'Digit: {{digits_y[i]}}')
    ax.axis('off')
plt.suptitle('Digits — Sample Images (8×8 pixels)')
plt.tight_layout()
plt.savefig('ds10_digits_images.png')
plt.close()

Digits — Sample Images

Key point: Key point: plt.imshow() with cmap. ax.axis('off') hides axes.


Example 11: Digits Feature Scatter

First two features coloured by digit class.

digits_data = digits.data
plt.figure(figsize=(8, 6))
for digit in range(10):
    mask = digits_y == digit
    plt.scatter(digits_data[mask, 0], digits_data[mask, 1],
                s=20, c=TAB10_COLORS[digit], alpha=0.6,
                edgecolors='none', label=str(digit))
plt.title('Digits — Feature 0 vs Feature 1')
plt.xlabel('Feature 0')
plt.ylabel('Feature 1')
plt.legend(title='Digit', fontsize=8)
plt.savefig('ds11_digits_scatter.png')
plt.close()

Digits — Feature Scatter


Example 12: Digits Pixel Histogram

Pixel intensity distributions for every other digit.

plt.figure(figsize=(10, 6))
for digit in range(0, 10, 2):
    mask = digits_y == digit
    pixels = digits_data[mask].flatten()
    plt.hist(pixels, bins=32, alpha=0.4, color=TAB10_COLORS[digit],
             edgecolor='white', linewidth=0.3, label=f'Digit {{digit}}')
plt.title('Digits — Pixel Intensity Distribution')
plt.xlabel('Pixel Value (0–16)')
plt.ylabel('Count')
plt.legend(fontsize=8)
plt.savefig('ds12_digits_hist.png')
plt.close()

Digits — Pixel Histogram


Example 13: Diabetes Regression Scatter

BMI vs disease progression with linear fit.

dia_data, dia_target = diabetes.data, diabetes.target
dia_feat = diabetes.feature_names
bmi_idx = 2
x_bmi, y_target = dia_data[:, bmi_idx], dia_target

coeffs = np.polyfit(x_bmi, y_target, 1)
x_fit = np.linspace(min(x_bmi), max(x_bmi), 100)
y_fit = np.polyval(coeffs, x_fit)

plt.figure(figsize=(10, 6))
plt.scatter(x_bmi, y_target, s=15, c='#1f77b4', alpha=0.4, edgecolors='none')
plt.plot(x_fit, y_fit, 'r-', linewidth=2,
         label=f'y = {{coeffs[0]:.1f}}x + {{coeffs[1]:.0f}}')
plt.title('Diabetes — BMI vs Disease Progression')
plt.xlabel(dia_feat[bmi_idx])
plt.ylabel('Disease Progression')
plt.legend()
plt.savefig('ds13_diabetes_scatter.png')
plt.close()

Diabetes — BMI vs Disease Progression

Key point: Key point: np.polyfit() + np.polyval() for simple linear regression.


Example 14: Diabetes Feature Correlations

Correlation of each feature with the target.

correlations = np.array([np.corrcoef(dia_data[:, i], dia_target)[0, 1]
                         for i in range(dia_data.shape[1])])
colors_corr = ['#d62728' if c < 0 else '#2ca02c' for c in correlations]

plt.figure(figsize=(12, 6))
plt.bar(range(len(dia_feat)), correlations, color=colors_corr,
        edgecolor='white', linewidth=0.5)
plt.xticks(range(len(dia_feat)), dia_feat, rotation=45, ha='right')
plt.axhline(y=0, color='gray', linestyle='-', linewidth=0.5)
plt.title('Diabetes — Feature Correlation with Target')
plt.ylabel('Correlation Coefficient')
plt.savefig('ds14_diabetes_corr.png')
plt.close()

Diabetes — Feature Correlations

Key point: Key point: Green = positive correlation, red = negative. axhline for the zero line.


Example 15: Iris Scatter Matrix

4×4 pairwise feature scatter matrix, histograms on diagonal.

fig, axes = plt.subplots(4, 4, figsize=(14, 14))
for i in range(4):
    for j in range(4):
        ax = axes[i, j]
        if i == j:
            for k, name in enumerate(names):
                mask = y == k
                ax.hist(X[mask, i], bins=15, alpha=0.5, color=TAB10_COLORS[k],
                        edgecolor='white', linewidth=0.3)
            ax.set_title(feat[i])
        else:
            for k, name in enumerate(names):
                mask = y == k
                ax.scatter(X[mask, j], X[mask, i], s=15, c=TAB10_COLORS[k],
                           alpha=0.5, edgecolors='none')
        if i == 3: ax.set_xlabel(feat[j])
        if j == 0: ax.set_ylabel(feat[i])
plt.suptitle('Iris — Pairwise Feature Scatter Matrix', y=1.02)
plt.tight_layout()
plt.savefig('ds15_iris_pairwise.png')
plt.close()

Iris — Pairwise Scatter Matrix


Example 16: Iris Cumulative Distribution

CDF of petal length by species.

plt.figure(figsize=(10, 6))
for i, name in enumerate(names):
    mask = y == i
    vals = np.sort(X[mask, 2])
    cumsum = np.arange(1, len(vals) + 1) / len(vals)
    plt.plot(vals, cumsum, color=TAB10_COLORS[i], linewidth=2, label=name)
plt.title('Iris — Cumulative Distribution of Petal Length')
plt.xlabel(f'{{feat[2]}} (cm)')
plt.ylabel('Cumulative Proportion')
plt.legend()
plt.savefig('ds16_iris_cdf.png')
plt.close()

Iris — Cumulative Distribution


Example 17: Wine Stacked Bar

Normalised feature contributions per class.

wine_norm = (wine_data[:, :5] - wine_data[:, :5].min(axis=0)) / \
            (wine_data[:, :5].max(axis=0) - wine_data[:, :5].min(axis=0) + 1e-10)
wine_class_means = np.array([wine_norm[wine_y == i, :5].mean(axis=0) for i in range(3)])

plt.figure(figsize=(10, 6))
bottom = np.zeros(3)
for j in range(5):
    plt.bar(range(3), wine_class_means[:, j], bottom=bottom,
            label=wine_feat[j], color=TAB10_COLORS[j], edgecolor='white', linewidth=0.5)
    bottom += wine_class_means[:, j]
plt.xticks(range(3), wine_names)
plt.title('Wine — Stacked Feature Contributions')
plt.ylabel('Normalised Mean')
plt.legend(fontsize=8)
plt.savefig('ds17_wine_stacked.png')
plt.close()

Wine — Stacked Bar

Key point: Key point: The bottom parameter creates stacking.


Example 18: Multi-dataset Overview

Comparing samples, features, and classes across 5 datasets.

datasets = ['Iris', 'Wine', 'Breast\nCancer', 'Digits', 'Diabetes']
sizes = [len(iris.data), len(wine.data), len(cancer.data), len(digits.data), len(diabetes.data)]
features = [iris.data.shape[1], wine.data.shape[1], cancer.data.shape[1], digits.data.shape[1], diabetes.data.shape[1]]
classes = [len(iris.target_names), len(wine.target_names), len(cancer.target_names), 10, 1]

fig, axes = plt.subplots(1, 3, figsize=(16, 5))
x_pos = range(len(datasets))
axes[0].bar(x_pos, sizes, color='#1f77b4', edgecolor='white', linewidth=0.5)
axes[0].set_xticks(x_pos); axes[0].set_xticklabels(datasets)
axes[0].set_title('Sample Size')
axes[1].bar(x_pos, features, color='#2ca02c', edgecolor='white', linewidth=0.5)
axes[1].set_xticks(x_pos); axes[1].set_xticklabels(datasets)
axes[1].set_title('Features')
axes[2].bar(x_pos, classes, color='#d62728', edgecolor='white', linewidth=0.5)
axes[2].set_xticks(x_pos); axes[2].set_xticklabels(datasets)
axes[2].set_title('Classes')
plt.suptitle('Dataset Overview Comparison')
plt.tight_layout()
plt.savefig('ds18_dataset_overview.png')
plt.close()

Multi-dataset Overview


Example 19: Iris Mean Profiles

Plotting mean values across the 4 features for each species.

means = np.array([iris.data[iris.target == i].mean(axis=0) for i in range(3)])
plt.figure(figsize=(10, 6))
x_feat = np.arange(4)
for i, name in enumerate(names):
    plt.plot(x_feat, means[i], 'o-', color=TAB10_COLORS[i], linewidth=2,
             markersize=8, label=name)
plt.xticks(x_feat, iris.feature_names, rotation=45, ha='right')
plt.title('Iris — Mean Feature Profiles by Species')
plt.ylabel('Mean Value (cm)')
plt.legend()
plt.savefig('ds19_iris_profiles.png')
plt.close()

Iris — Mean Profiles


Example 20: Iris Confidence Bands

Petal length vs width with ±1σ bands.

plt.figure(figsize=(10, 6))
for i, name in enumerate(names):
    mask = y == i
    petal_l, petal_w = X[mask, 2], X[mask, 3]
    sort_idx = np.argsort(petal_l)
    pl, pw = petal_l[sort_idx], petal_w[sort_idx]
    std_w = np.std(pw)
    plt.plot(pl, pw, color=TAB10_COLORS[i], linewidth=1.5, label=name)
    plt.fill_between(pl, pw - std_w, pw + std_w, alpha=0.15, color=TAB10_COLORS[i])
plt.title('Iris — Petal Length vs Width with Confidence Bands')
plt.xlabel(f'{{feat[2]}} (cm)')
plt.ylabel(f'{{feat[3]}} (cm)')
plt.legend()
plt.savefig('ds20_iris_confidence.png')
plt.close()

Iris — Confidence Bands

Key point: Key point: plt.fill_between() fills the region between two curves.


3D Data Projection Visualization

Because OpenGL rendering has limitations on Apple Silicon macOS, the 3D demos use 2D projection to simulate 3D effects — works on all platforms.

3D Projection Math

def project_3d(X, Y, Z, azim=-60, elev=30):
    """Project 3D coordinates to 2D with perspective."""
    a, e = np.deg2rad(azim), np.deg2rad(elev)
    x1 = X * np.cos(a) - Y * np.sin(a)
    y1 = X * np.sin(a) + Y * np.cos(a)
    x2 = x1
    y2 = y1 * np.cos(e) - Z * np.sin(e)
    z2 = y1 * np.sin(e) + Z * np.cos(e)
    d = 10.0
    p = d / (d - z2 * 0.1)  # perspective factor
    return x2 * p, y2 * p, z2

Example 3D-1: Iris 3D Scatter

Sepal length × width × petal length projected to 2D.

X, Y, Z = iris.data[:, 0], iris.data[:, 1], iris.data[:, 2]
px, py, pz = project_3d(X, Y, Z, azim=-50, elev=25)

plt.figure(figsize=(8, 6))
for i, name in enumerate(iris.target_names):
    mask = iris.target == i
    plt.scatter(px[mask], py[mask], s=40, c=TAB10_COLORS[i],
                alpha=0.7, edgecolors='white', linewidths=0.5, label=name)
plt.title('Iris 3D — Sepal Length × Width × Petal Length')
plt.xlabel('Sepal Length'); plt.ylabel('Sepal Width')
plt.legend()
plt.savefig('ds3d_01_iris_3d.png')
plt.close()

Iris 3D — Sepal Scatter


Example 3D-2: Iris 3D Colormap

Petal measurements projected with viridis colormap.

X, Y, Z = iris.data[:, 2], iris.data[:, 3], iris.data[:, 0]
px, py, pz = project_3d(X, Y, Z, azim=-45, elev=30)

plt.figure(figsize=(8, 6))
scatter = plt.scatter(px, py, s=50, c=iris.target, cmap='viridis',
                       alpha=0.8, edgecolors='white', linewidths=0.5)
plt.title('Iris 3D — Petal Length × Width × Sepal Length')
plt.xlabel('Petal Length'); plt.ylabel('Petal Width')
plt.colorbar(scatter, label='Species')
plt.savefig('ds3d_02_iris_3d_cmap.png')
plt.close()

Iris 3D — Petal Colormap


Example 3D-3: Wine 3D Scatter

X, Y, Z = wine.data[:, 0], wine.data[:, 1], wine.data[:, 2]
px, py, pz = project_3d(X, Y, Z, azim=-55, elev=20)

plt.figure(figsize=(8, 6))
for i, name in enumerate(wine.target_names):
    mask = wine.target == i
    plt.scatter(px[mask], py[mask], s=30, c=TAB10_COLORS[i],
                alpha=0.6, edgecolors='white', linewidths=0.5, label=name)
plt.title('Wine 3D — Features 0, 1, 2')
plt.xlabel('Feature 0'); plt.ylabel('Feature 1')
plt.legend()
plt.savefig('ds3d_03_wine_3d.png')
plt.close()

Wine 3D Scatter


Example 3D-4: Breast Cancer 3D Scatter

X, Y, Z = cancer.data[:, 0], cancer.data[:, 1], cancer.data[:, 2]
px, py, pz = project_3d(X, Y, Z, azim=-50, elev=25)

plt.figure(figsize=(8, 6))
for i, name in enumerate(cancer.target_names):
    mask = cancer.target == i
    plt.scatter(px[mask], py[mask], s=10, c=TAB10_COLORS[i],
                alpha=0.5, edgecolors='none', label=name)
plt.title('Breast Cancer 3D — Features 0, 1, 2')
plt.xlabel('Feature 0'); plt.ylabel('Feature 1')
plt.legend()
plt.savefig('ds3d_04_cancer_3d.png')
plt.close()

Breast Cancer 3D Scatter


Example 3D-5: Iris KDE Surface

Gaussian KDE density surface.

from scipy.stats import gaussian_kde
xy = iris.data[:, :2].T
kde = gaussian_kde(xy)
x_grid = np.linspace(iris.data[:, 0].min()-0.5, iris.data[:, 0].max()+0.5, 40)
y_grid = np.linspace(iris.data[:, 1].min()-0.5, iris.data[:, 1].max()+0.5, 40)
Xg, Yg = np.meshgrid(x_grid, y_grid)
Zg = kde(np.vstack([Xg.ravel(), Yg.ravel()])).reshape(Xg.shape)

plt.figure(figsize=(10, 8))
plt.contourf(Xg, Yg, Zg, levels=20, cmap='viridis', alpha=0.8)
plt.contour(Xg, Yg, Zg, levels=8, colors='white', linewidths=0.3, alpha=0.5)
for i, name in enumerate(iris.target_names):
    mask = iris.target == i
    plt.scatter(iris.data[mask, 0], iris.data[mask, 1], s=20,
                c='white', alpha=0.8, edgecolors=TAB10_COLORS[i], linewidths=1)
plt.title('Iris — KDE Surface (Sepal Length × Width)')
plt.xlabel('Sepal Length'); plt.ylabel('Sepal Width')
plt.savefig('ds3d_05_iris_kde.png')
plt.close()

Iris — KDE Surface

Key point: Key point: plt.contourf() for filled contours, plt.contour() for contour lines.


Example 3D-6: Iris Wireframe + Data

Wireframe mesh with scattered data points.

x = np.linspace(iris.data[:, 2].min(), iris.data[:, 2].max(), 30)
y = np.linspace(iris.data[:, 3].min(), iris.data[:, 3].max(), 30)
Xg, Yg = np.meshgrid(x, y)
Zg = np.sin(np.sqrt((Xg-3)**2 + (Yg-1)**2)) * 2 + 3

plt.figure(figsize=(10, 8))
for i in range(0, len(x), 2):
    plt.plot(Xg[i, :], Yg[i, :], Zg[i, :], 'navy', linewidth=0.4, alpha=0.5)
for j in range(0, len(y), 2):
    plt.plot(Xg[:, j], Yg[:, j], Zg[:, j], 'navy', linewidth=0.4, alpha=0.5)
plt.scatter(iris.data[:, 2], iris.data[:, 3], s=30, c=iris.target,
            cmap='viridis', alpha=0.8, edgecolors='white', linewidths=0.5)
plt.title('Iris — Wireframe + Data Points')
plt.xlabel('Petal Length'); plt.ylabel('Petal Width')
plt.savefig('ds3d_06_iris_wireframe.png')
plt.close()

Iris — Wireframe + Data


Example 3D-7: Multi-dataset 3D Comparison

Side-by-side 3D projection of Iris, Wine, Cancer.

fig, axes = plt.subplots(1, 3, figsize=(18, 6))

px, py, _ = project_3d(iris.data[:, 0], iris.data[:, 1], iris.data[:, 2])
for i, name in enumerate(iris.target_names):
    mask = iris.target == i
    axes[0].scatter(px[mask], py[mask], s=20, c=TAB10_COLORS[i], alpha=0.6, edgecolors='none')
axes[0].set_title('Iris (150 samples)')

px, py, _ = project_3d(wine.data[:, 0], wine.data[:, 1], wine.data[:, 2])
for i, name in enumerate(wine.target_names):
    mask = wine.target == i
    axes[1].scatter(px[mask], py[mask], s=15, c=TAB10_COLORS[i], alpha=0.6, edgecolors='none')
axes[1].set_title('Wine (178 samples)')

px, py, _ = project_3d(cancer.data[:, 0], cancer.data[:, 1], cancer.data[:, 2])
for i, name in enumerate(cancer.target_names):
    mask = cancer.target == i
    axes[2].scatter(px[mask], py[mask], s=5, c=TAB10_COLORS[i], alpha=0.5, edgecolors='none')
axes[2].set_title('Breast Cancer (569 samples)')

plt.suptitle('3D Feature Projection Comparison')
plt.tight_layout()
plt.savefig('ds3d_07_multi_3d.png')
plt.close()

Multi-dataset 3D Comparison


Example 3D-8: Iris 3D Bar Chart

Pseudo-3D bars built from filled polygons.

means = np.array([iris.data[iris.target == i].mean(axis=0) for i in range(3)])
plt.figure(figsize=(12, 8))
x_pos = np.arange(4)
width = 0.25
for i, name in enumerate(iris.target_names):
    for j in range(4):
        h = means[i, j]
        cx = x_pos[j] + i * width
        top_x = [cx, cx+width*0.8, cx+width*0.8+0.1, cx+0.1]
        top_y = [i*0.5, i*0.5, i*0.5+0.1, i*0.5+0.1]
        plt.fill(top_x, top_y, color=TAB10_COLORS[i], alpha=0.9,
                 edgecolor='white', linewidth=0.5)
        front_x = [cx, cx+width*0.8, cx+width*0.8, cx]
        front_y = [i*0.5-h*0.15, i*0.5-h*0.15, i*0.5, i*0.5]
        plt.fill(front_x, front_y, color=TAB10_COLORS[i], alpha=0.6,
                 edgecolor='white', linewidth=0.3)
plt.xlim(-0.5, 4.5); plt.ylim(-2, 1.5)
plt.xticks(x_pos + width*1.5, iris.feature_names, rotation=45, ha='right')
plt.title('Iris — 3D Bar Chart (Mean Feature Values)')
plt.ylabel('Mean Value')
plt.savefig('ds3d_08_iris_3d_bar.png')
plt.close()

Iris — 3D Bar Chart


Example 3D-9: Iris 3D Trajectory

Smooth quadratic interpolation through species means.

from scipy.interpolate import interp1d
species_means = np.array([iris.data[iris.target == i].mean(axis=0) for i in range(3)])
f_interp = interp1d([0, 1, 2], species_means, axis=0, kind='quadratic')
traj = f_interp(np.linspace(0, 2, 100))
px, py, pz = project_3d(traj[:, 0], traj[:, 1], traj[:, 2], azim=-50, elev=25)

plt.figure(figsize=(8, 6))
for i in range(len(px)-1):
    color_val = i / len(px)
    c = get_cmap('plasma')(color_val, bytes=True)
    plt.plot(px[i:i+2], py[i:i+2], color=(c[0]/255, c[1]/255, c[2]/255),
             linewidth=2, alpha=0.8)
for i, name in enumerate(iris.target_names):
    pmx, pmy, _ = project_3d(species_means[i:i+1, 0], species_means[i:i+1, 1],
                              species_means[i:i+1, 2])
    plt.scatter(pmx, pmy, s=100, c=TAB10_COLORS[i], edgecolors='white',
                linewidths=2, label=name, zorder=5)
plt.title('Iris — 3D Species Trajectory')
plt.xlabel('Sepal Length'); plt.ylabel('Sepal Width')
plt.legend()
plt.savefig('ds3d_09_iris_trajectory.png')
plt.close()

Iris — 3D Trajectory


Example 3D-10: Iris Density Slices

3D density sliced at different petal lengths.

from scipy.stats import gaussian_kde
xy = iris.data[:, :3].T
kde = gaussian_kde(xy)

fig, axes = plt.subplots(1, 3, figsize=(15, 5))
z_levels = [1.5, 3.0, 4.5]
for idx, z_val in enumerate(z_levels):
    x_grid = np.linspace(4, 8, 40)
    y_grid = np.linspace(2, 4.5, 40)
    Xg, Yg = np.meshgrid(x_grid, y_grid)
    Zg = kde(np.vstack([Xg.ravel(), Yg.ravel(),
          np.full_like(Xg.ravel(), z_val)])).reshape(Xg.shape)
    axes[idx].contourf(Xg, Yg, Zg, levels=15, cmap='viridis', alpha=0.8)
    axes[idx].set_title(f'Petal Length = {{z_val:.1f}} cm')
    axes[idx].set_xlabel('Sepal Length')
    axes[idx].set_ylabel('Sepal Width')
plt.suptitle('Iris — 3D Density Slices at Different Petal Lengths')
plt.tight_layout()
plt.savefig('ds3d_10_iris_volume.png')
plt.close()

Iris — Density Slices


API Reference

Pyplot Functions

Function Description
plt.figure(figsize) Create a new figure
plt.subplots(nrows, ncols) Create a subplot grid
plt.plot(x, y, fmt, **kw) Line plot
plt.scatter(x, y, s, c, cmap, **kw) Scatter plot
plt.bar(x, h, **kw) Bar chart
plt.barh(y, w, **kw) Horizontal bar chart
plt.hist(x, bins, **kw) Histogram
plt.errorbar(x, y, xerr, yerr, **kw) Error bar plot
plt.fill_between(x, y1, y2, **kw) Fill between curves
plt.contour(X, Y, Z, **kw) Contour lines
plt.contourf(X, Y, Z, **kw) Filled contours
plt.imshow(X, cmap, **kw) Image display
plt.boxplot(data, **kw) Box and whisker plot
plt.pie(sizes, **kw) Pie chart
plt.stem(x, y, **kw) Stem plot
plt.step(x, y, **kw) Step plot
plt.axhline(y, **kw) Horizontal reference line
plt.axvline(x, **kw) Vertical reference line
plt.text(x, y, s, **kw) Add text
plt.annotate(text, xy, **kw) Add annotation
plt.title(label) Set title
plt.xlabel(label) X-axis label
plt.ylabel(label) Y-axis label
plt.legend() Add legend
plt.grid(visible) Toggle grid
plt.xlim(a, b), plt.ylim(a, b) Set axis limits
plt.xscale(s), plt.yscale(s) Set axis scale
plt.savefig(fname) Save figure
plt.fill(x, y, **kw) Fill polygon
plt.colorbar(mappable) Add colour bar

Axes Methods

Method Description
ax.plot(...) etc. All plotting methods available on axes
ax.set_title(), ax.set_xlabel(), etc. Labels
ax.set_xlim(), ax.set_ylim() Limits
ax.set_xticks(), ax.set_yticks() Tick positions
ax.set_xticklabels(), ax.set_yticklabels() Tick labels
ax.twinx(), ax.twiny() Create twin axes
ax.axis('off') Hide axes
ax.add_patch(polygon) Add a patch

Colour System

from shenbi.colors import TAB10_COLORS, resolve_color
from shenbi.cm import get_cmap, Colormap

rgba = resolve_color('steelblue')      # → (70, 130, 180, 255)
rgba = resolve_color('#FF0000')        # → (255, 0, 0, 255)
rgba = resolve_color((1.0, 0.5, 0.0))  # → (255, 127, 0, 255)
rgba = resolve_color('r', alpha=0.5)   # → (214, 39, 40, 127)

cmap = get_cmap('viridis')
colours = cmap(np.linspace(0, 1, 10))  # → (N, 4) RGBA array

plt.cm.viridis          # module attribute
plt.cm.get_cmap('jet')  # function call

Performance Comparison

Data Size matplotlib Render Time ShenBi (pyqtgraph) Render Time
1,000 pts ~0.05s ~0.003s
10,000 pts ~0.15s ~0.005s
100,000 pts ~1.2s ~0.04s
1,000,000 pts ~12s ~0.3s

ShenBi is 20–40× faster than matplotlib on large datasets, thanks to pyqtgraph's OpenGL acceleration and automatic downsampling.


Running the Demos

cd demo
python demo_pyqtgraph_2d.py   # 20 dataset 2D demos
python demo_pyqtgraph_3d.py   # 10 3D projection demos

All outputs are saved to demo/output/ in both PNG and SVG formats.