Learn how to create OpenAI-inspired, modern plots in Python using Matplotlib. This guide covers custom color palettes, typography, layout tips, and practical code examples to help you elevate your data visualizations for presentations, reports, and publications.
1import pandas as pd
2import numpy as np
3import matplotlib.pyplot as plt
4import seaborn as sns
1import pandas as pd
2import numpy as np
3import matplotlib.pyplot as plt
4import seaborn as sns
1# Matplotlib rcParams for OpenAI-inspired style
2from pathlib import Path
3from textwrap import dedent
4import numpy as np
5import matplotlib.pyplot as plt
6from matplotlib.ticker import PercentFormatter
7
8
9import matplotlib as mpl
10
11mpl.rcParams.update(
12 {
13 "figure.facecolor": "white",
14 "figure.autolayout": False,
15 "figure.dpi": 160,
16 "savefig.transparent": True,
17 "savefig.bbox": "tight",
18 "savefig.pad_inches": 0.05,
19 "font.family": "DejaVu Sans",
20 "font.size": 12,
21 "text.color": "black",
22 "axes.titlesize": 18,
23 "axes.titleweight": 700,
24 "axes.titlepad": 14,
25 "axes.labelsize": 13,
26 "axes.linewidth": 0.8,
27 "axes.edgecolor": "gainsboro",
28 "axes.facecolor": "white",
29 "axes.spines.top": False,
30 "axes.spines.right": False,
31 "xtick.color": "dimgray",
32 "ytick.color": "dimgray",
33 "xtick.labelsize": 12,
34 "ytick.labelsize": 12,
35 "xtick.major.size": 0,
36 "ytick.major.size": 0,
37 "xtick.major.pad": 6,
38 "ytick.major.pad": 6,
39 "axes.grid": True,
40 "axes.grid.axis": "y",
41 "grid.linestyle": "-",
42 "grid.linewidth": 1.0,
43 "grid.color": "lightgray",
44 "legend.frameon": False,
45 "legend.fontsize": 12,
46 "legend.loc": "upper left",
47 "lines.linewidth": 2,
48 "patch.edgecolor": "none",
49 }
50)
1# Matplotlib rcParams for OpenAI-inspired style
2from pathlib import Path
3from textwrap import dedent
4import numpy as np
5import matplotlib.pyplot as plt
6from matplotlib.ticker import PercentFormatter
7
8
9import matplotlib as mpl
10
11mpl.rcParams.update(
12 {
13 "figure.facecolor": "white",
14 "figure.autolayout": False,
15 "figure.dpi": 160,
16 "savefig.transparent": True,
17 "savefig.bbox": "tight",
18 "savefig.pad_inches": 0.05,
19 "font.family": "DejaVu Sans",
20 "font.size": 12,
21 "text.color": "black",
22 "axes.titlesize": 18,
23 "axes.titleweight": 700,
24 "axes.titlepad": 14,
25 "axes.labelsize": 13,
26 "axes.linewidth": 0.8,
27 "axes.edgecolor": "gainsboro",
28 "axes.facecolor": "white",
29 "axes.spines.top": False,
30 "axes.spines.right": False,
31 "xtick.color": "dimgray",
32 "ytick.color": "dimgray",
33 "xtick.labelsize": 12,
34 "ytick.labelsize": 12,
35 "xtick.major.size": 0,
36 "ytick.major.size": 0,
37 "xtick.major.pad": 6,
38 "ytick.major.pad": 6,
39 "axes.grid": True,
40 "axes.grid.axis": "y",
41 "grid.linestyle": "-",
42 "grid.linewidth": 1.0,
43 "grid.color": "lightgray",
44 "legend.frameon": False,
45 "legend.fontsize": 12,
46 "legend.loc": "upper left",
47 "lines.linewidth": 2,
48 "patch.edgecolor": "none",
49 }
50)
1def label_bars(ax, rects, fmt="{:.1f}%", dy=3, fontsize=12, weight="600"):
2 for r in rects:
3 h = r.get_height()
4 ax.annotate(
5 fmt.format(h),
6 (r.get_x() + r.get_width() / 2, h),
7 xytext=(0, dy),
8 textcoords="offset points",
9 ha="center",
10 va="bottom",
11 fontsize=fontsize,
12 fontweight=weight,
13 )
14
15
16def tweak_axes(ax, top_margin=0.15):
17 ax.set_axisbelow(True)
18 ax.margins(y=top_margin)
19 for s in ("top", "right"):
20 ax.spines[s].set_visible(False)
21
22
23def circular_legend(ax, labels, colors):
24 from matplotlib.lines import Line2D
25
26 handles = [
27 Line2D(
28 [0],
29 [0],
30 marker="o",
31 color="w",
32 markerfacecolor=c,
33 markersize=10,
34 linewidth=0,
35 )
36 for c in colors
37 ]
38 ax.legend(
39 handles,
40 labels,
41 ncol=len(labels),
42 frameon=False,
43 bbox_to_anchor=(0, 1.06),
44 loc="lower left",
45 )
46
47
48# Light/bold paired palette (you can swap these)
49COLORS = ["#C8D7FF", "#4D61D6"] # light indigo, bold indigo
50
51# ---------------------------------------------------------
52# 3) Demo 1 — Incorrect comments (percent bars + error bars)
1def label_bars(ax, rects, fmt="{:.1f}%", dy=3, fontsize=12, weight="600"):
2 for r in rects:
3 h = r.get_height()
4 ax.annotate(
5 fmt.format(h),
6 (r.get_x() + r.get_width() / 2, h),
7 xytext=(0, dy),
8 textcoords="offset points",
9 ha="center",
10 va="bottom",
11 fontsize=fontsize,
12 fontweight=weight,
13 )
14
15
16def tweak_axes(ax, top_margin=0.15):
17 ax.set_axisbelow(True)
18 ax.margins(y=top_margin)
19 for s in ("top", "right"):
20 ax.spines[s].set_visible(False)
21
22
23def circular_legend(ax, labels, colors):
24 from matplotlib.lines import Line2D
25
26 handles = [
27 Line2D(
28 [0],
29 [0],
30 marker="o",
31 color="w",
32 markerfacecolor=c,
33 markersize=10,
34 linewidth=0,
35 )
36 for c in colors
37 ]
38 ax.legend(
39 handles,
40 labels,
41 ncol=len(labels),
42 frameon=False,
43 bbox_to_anchor=(0, 1.06),
44 loc="lower left",
45 )
46
47
48# Light/bold paired palette (you can swap these)
49COLORS = ["#C8D7FF", "#4D61D6"] # light indigo, bold indigo
50
51# ---------------------------------------------------------
52# 3) Demo 1 — Incorrect comments (percent bars + error bars)
1# ---------------------------------------------------------
2labels = ["GPT-5 (high)", "GPT-5-Codex (high)"]
3vals = np.array([13.7, 4.4])
4errs = np.array([1.6, 1.2])
5x = np.arange(len(labels))
6w = 0.55
7
8fig, ax = plt.subplots(figsize=(6.8, 4.2))
9bars = ax.bar(
10 x,
11 vals,
12 yerr=errs,
13 width=w,
14 capsize=3,
15 color=COLORS,
16 zorder=3,
17 edgecolor="dimgray", # solid border color
18 linewidth=1.5, # border thickness
19)
20
21# Round the edges of the bars (simulate rounded corners by overlaying a rectangle with rounded edges)
22for bar in bars:
23 bar.set_linewidth(1.5)
24 bar.set_edgecolor("dimgray")
25 bar.set_zorder(3)
26 # set_capstyle and set_joinstyle are for lines, not patches, so we use path effects for a stylized look
27 import matplotlib.patheffects as pe
28
29 bar.set_path_effects([pe.Stroke(linewidth=1.5, foreground="dimgray"), pe.Normal()])
30 bar.set_antialiased(True)
31 # Simulate rounded corners by overlaying a FancyBboxPatch
32 from matplotlib.patches import FancyBboxPatch
33
34 bbox = bar.get_bbox()
35 fancy = FancyBboxPatch(
36 (bbox.x0, bbox.y0),
37 bbox.width,
38 bbox.height,
39 boxstyle="round,pad=0.02,rounding_size=0.3",
40 linewidth=1.5,
41 edgecolor="dimgray",
42 facecolor=bar.get_facecolor(),
43 zorder=bar.get_zorder() + 1,
44 mutation_aspect=1,
45 )
46 ax.add_patch(fancy)
47 bar.set_visible(False) # Hide the original bar
48
49ax.set_title("Incorrect comments", fontsize=14)
50ax.set_ylabel("% of comments")
51ax.set_xticks(x, labels, rotation=20, ha="right")
52ax.yaxis.set_major_formatter(PercentFormatter())
53label_bars(ax, bars, fmt="{:.1f}%")
54circular_legend(ax, labels, COLORS)
55tweak_axes(ax)
56fig.tight_layout()
57fig.savefig("demo_incorrect_comments.png")
1# ---------------------------------------------------------
2labels = ["GPT-5 (high)", "GPT-5-Codex (high)"]
3vals = np.array([13.7, 4.4])
4errs = np.array([1.6, 1.2])
5x = np.arange(len(labels))
6w = 0.55
7
8fig, ax = plt.subplots(figsize=(6.8, 4.2))
9bars = ax.bar(
10 x,
11 vals,
12 yerr=errs,
13 width=w,
14 capsize=3,
15 color=COLORS,
16 zorder=3,
17 edgecolor="dimgray", # solid border color
18 linewidth=1.5, # border thickness
19)
20
21# Round the edges of the bars (simulate rounded corners by overlaying a rectangle with rounded edges)
22for bar in bars:
23 bar.set_linewidth(1.5)
24 bar.set_edgecolor("dimgray")
25 bar.set_zorder(3)
26 # set_capstyle and set_joinstyle are for lines, not patches, so we use path effects for a stylized look
27 import matplotlib.patheffects as pe
28
29 bar.set_path_effects([pe.Stroke(linewidth=1.5, foreground="dimgray"), pe.Normal()])
30 bar.set_antialiased(True)
31 # Simulate rounded corners by overlaying a FancyBboxPatch
32 from matplotlib.patches import FancyBboxPatch
33
34 bbox = bar.get_bbox()
35 fancy = FancyBboxPatch(
36 (bbox.x0, bbox.y0),
37 bbox.width,
38 bbox.height,
39 boxstyle="round,pad=0.02,rounding_size=0.3",
40 linewidth=1.5,
41 edgecolor="dimgray",
42 facecolor=bar.get_facecolor(),
43 zorder=bar.get_zorder() + 1,
44 mutation_aspect=1,
45 )
46 ax.add_patch(fancy)
47 bar.set_visible(False) # Hide the original bar
48
49ax.set_title("Incorrect comments", fontsize=14)
50ax.set_ylabel("% of comments")
51ax.set_xticks(x, labels, rotation=20, ha="right")
52ax.yaxis.set_major_formatter(PercentFormatter())
53label_bars(ax, bars, fmt="{:.1f}%")
54circular_legend(ax, labels, COLORS)
55tweak_axes(ax)
56fig.tight_layout()
57fig.savefig("demo_incorrect_comments.png")
1# ---------------------------------------------------------
2# 4) Demo 2 — High-impact comments
3# ---------------------------------------------------------
4vals2 = np.array([39.4, 52.4])
5errs2 = np.array([1.7, 2.2])
6fig, ax = plt.subplots(figsize=(6.8, 4.2))
7bars = ax.bar(x, vals2, yerr=errs2, width=w, capsize=3, color=COLORS, zorder=3)
8ax.set_title("High-impact comments")
9ax.set_ylabel("% of comments")
10ax.set_xticks(x, labels, rotation=20, ha="right")
11ax.yaxis.set_major_formatter(PercentFormatter())
12label_bars(ax, bars, fmt="{:.1f}%")
13circular_legend(ax, labels, COLORS)
14tweak_axes(ax)
15fig.tight_layout()
16
17# ---------------------------------------------------------
18# 5) Demo 3 — Comments per PR (avg)
19# ---------------------------------------------------------
20vals3 = np.array([1.32, 0.93])
21fig, ax = plt.subplots(figsize=(6.8, 4.2))
22bars = ax.bar(x, vals3, width=w, color=COLORS, zorder=3)
23ax.set_title("Comments per PR (avg)")
24ax.set_ylabel("# of comments")
25ax.set_xticks(x, labels, rotation=20, ha="right")
26label_bars(ax, bars, fmt="{:.2f}", dy=4)
27circular_legend(ax, labels, COLORS)
28tweak_axes(ax, top_margin=0.22)
29fig.tight_layout()
30
31# ---------------------------------------------------------
32# 6) Demo 4 — Stacked bar, “Economically important tasks”
33# ---------------------------------------------------------
34models = ["GPT-5", "ChatGPT agent", "OpenAI o3"]
35wins = np.array([41.0, 36.5, 27.5])
36ties = np.array([6.1, 7.0, 6.0])
37
38fig, ax = plt.subplots(figsize=(7.0, 4.6))
39x = np.arange(len(models))
40w = 0.55
41p1 = ax.bar(x, wins, width=w, color="#B07CFF", label="Wins", zorder=3)
42p2 = ax.bar(x, ties, width=w, bottom=wins, color="#E7D2FF", label="Ties", zorder=3)
43
44ax.set_title("Economically important tasks")
45ax.set_ylabel("%")
46ax.set_xticks(x, models, rotation=20, ha="right")
47ax.set_ylim(0, 60)
48ax.axhline(50, color="gray", linestyle="--", linewidth=1)
49ax.text(ax.get_xlim()[0], 50.5, "Industry expert baseline", fontsize=11)
50
51for a, b in zip(p1, p2):
52 total = a.get_height() + b.get_height()
53 ax.annotate(
54 f"{total:.1f}",
55 (a.get_x() + a.get_width() / 2, total),
56 ha="center",
57 va="bottom",
58 fontsize=11,
59 fontweight="600",
60 xytext=(0, 3),
61 textcoords="offset points",
62 )
63
64ax.legend(ncol=2, bbox_to_anchor=(0, 1.06), loc="lower left")
65tweak_axes(ax)
66fig.tight_layout()
1# ---------------------------------------------------------
2# 4) Demo 2 — High-impact comments
3# ---------------------------------------------------------
4vals2 = np.array([39.4, 52.4])
5errs2 = np.array([1.7, 2.2])
6fig, ax = plt.subplots(figsize=(6.8, 4.2))
7bars = ax.bar(x, vals2, yerr=errs2, width=w, capsize=3, color=COLORS, zorder=3)
8ax.set_title("High-impact comments")
9ax.set_ylabel("% of comments")
10ax.set_xticks(x, labels, rotation=20, ha="right")
11ax.yaxis.set_major_formatter(PercentFormatter())
12label_bars(ax, bars, fmt="{:.1f}%")
13circular_legend(ax, labels, COLORS)
14tweak_axes(ax)
15fig.tight_layout()
16
17# ---------------------------------------------------------
18# 5) Demo 3 — Comments per PR (avg)
19# ---------------------------------------------------------
20vals3 = np.array([1.32, 0.93])
21fig, ax = plt.subplots(figsize=(6.8, 4.2))
22bars = ax.bar(x, vals3, width=w, color=COLORS, zorder=3)
23ax.set_title("Comments per PR (avg)")
24ax.set_ylabel("# of comments")
25ax.set_xticks(x, labels, rotation=20, ha="right")
26label_bars(ax, bars, fmt="{:.2f}", dy=4)
27circular_legend(ax, labels, COLORS)
28tweak_axes(ax, top_margin=0.22)
29fig.tight_layout()
30
31# ---------------------------------------------------------
32# 6) Demo 4 — Stacked bar, “Economically important tasks”
33# ---------------------------------------------------------
34models = ["GPT-5", "ChatGPT agent", "OpenAI o3"]
35wins = np.array([41.0, 36.5, 27.5])
36ties = np.array([6.1, 7.0, 6.0])
37
38fig, ax = plt.subplots(figsize=(7.0, 4.6))
39x = np.arange(len(models))
40w = 0.55
41p1 = ax.bar(x, wins, width=w, color="#B07CFF", label="Wins", zorder=3)
42p2 = ax.bar(x, ties, width=w, bottom=wins, color="#E7D2FF", label="Ties", zorder=3)
43
44ax.set_title("Economically important tasks")
45ax.set_ylabel("%")
46ax.set_xticks(x, models, rotation=20, ha="right")
47ax.set_ylim(0, 60)
48ax.axhline(50, color="gray", linestyle="--", linewidth=1)
49ax.text(ax.get_xlim()[0], 50.5, "Industry expert baseline", fontsize=11)
50
51for a, b in zip(p1, p2):
52 total = a.get_height() + b.get_height()
53 ax.annotate(
54 f"{total:.1f}",
55 (a.get_x() + a.get_width() / 2, total),
56 ha="center",
57 va="bottom",
58 fontsize=11,
59 fontweight="600",
60 xytext=(0, 3),
61 textcoords="offset points",
62 )
63
64ax.legend(ncol=2, bbox_to_anchor=(0, 1.06), loc="lower left")
65tweak_axes(ax)
66fig.tight_layout()

About the author: Michael Brenndoerfer
All opinions expressed here are my own and do not reflect the views of my employer.
Michael currently works as an Associate Director of Data Science at EQT Partners in Singapore, where he drives AI and data initiatives across private capital investments.
With over a decade of experience spanning private equity, management consulting, and software engineering, he specializes in building and scaling analytics capabilities from the ground up. He has published research in leading AI conferences and holds expertise in machine learning, natural language processing, and value creation through data.
Related Content

Ordinary Least Squares (OLS): Complete Mathematical Guide with Formulas, Examples & Python Implementation
A comprehensive guide to Ordinary Least Squares (OLS) regression, including mathematical derivations, matrix formulations, step-by-step examples, and Python implementation. Learn the theory behind OLS, understand the normal equations, and implement OLS from scratch using NumPy and scikit-learn.

Simple Linear Regression: Complete Guide with Formulas, Examples & Python Implementation
A complete hands-on guide to simple linear regression, including formulas, intuitive explanations, worked examples, and Python code. Learn how to fit, interpret, and evaluate a simple linear regression model from scratch.

R-squared (Coefficient of Determination): Formula, Intuition & Model Fit in Regression
A comprehensive guide to R-squared, the coefficient of determination. Learn what R-squared means, how to calculate it, interpret its value, and use it to evaluate regression models. Includes formulas, intuitive explanations, practical guidelines, and visualizations.
Stay updated
Get notified when I publish new articles on data and AI, private equity, technology, and more.