A Python library to generate beautiful, clean charts in premium visual styles
Project description
Clean Charts
Clean Charts is a lightweight Python library for creating beautiful, publication-quality data visualizations inspired by modern editorial styles like The Economist. Go from raw data to presentation-ready charts in a single function call — no styling boilerplate required.
✨ Highlights
| Feature | Details |
|---|---|
| Publication-ready aesthetics | Cream-gray backgrounds, minimal gridlines, elegant sans-serif typography, and consistent padding — out of the box. |
| Jupyter-first workflow | Set output_path=None (the default) and charts render inline instantly. |
| Six chart types | Time-series lines, horizontal bars, grouped bars, stacked bars, donut charts, and multi-chart dashboards. |
| Smart defaults | Auto date parsing, PCHIP spline smoothing, overlap-free label placement, and adaptive scaling for any canvas size. |
| One-line theming | Override a handful of config variables to restyle every chart in your notebook at once. |
📦 Installation
pip install clean-charts
Dependencies (installed automatically): matplotlib ≥ 3.5, pandas ≥ 1.3, numpy ≥ 1.20, Pillow ≥ 8.0, scipy ≥ 1.7.
🚀 Quick Start
1. Grouped Horizontal Bar Chart
Display multiple numeric series side-by-side for each category, with an automatic color gradient and legend.
import pandas as pd
from clean_charts import plot_grouped_barh_chart
df = pd.DataFrame({
'Fruit': ['Apples', 'Bananas', 'Cherries', 'Dates', 'Elderberries', 'Figs', 'Grapes'],
'2024': [380, 410, 150, 420, 85, 280, 490],
'2025': [510, 180, 830, 450, 190, 240, 560],
'2026': [415, 450, 590, 310, 60, 310, 400]
})
plot_grouped_barh_chart(
data = df,
title="Regional Supermarket Inventory and Fruit Category Performance Analysis",
subtitle="Comparative overview of total stock volume across key produce items to optimize supply chain distribution",
bar_padding=0,
group_padding=0.3,
value_suffix = ' Kg'
)
Output:
2. Horizontal Bar Chart
Draw a single-series horizontal bar chart with category labels and value annotations.
import pandas as pd
from clean_charts import plot_barh_chart
df = pd.DataFrame({
'Category': ['Apples', 'Bananas', 'Cherries', 'Dates', 'Elderberries', 'Figs', 'Grapes', 'Honeydew'],
'Sales': [400, 350, 300, 450, 120, 210, 520, 180],
})
plot_barh_chart(
data=df,
title="Regional Supermarket Inventory and Fruit Category Performance Analysis",
subtitle="Comparative overview of total stock volume across key produce items to optimize supply chain distribution",
value_suffix=' Kg',
)
Output:
3. Time-Series Line Chart
Generate a time-series line chart with smooth PCHIP spline curves, right-aligned Y-axis, and smart inline labels.
import pandas as pd
from clean_charts import plot_time_series
df = pd.DataFrame({
'Dates': pd.date_range("2026-01-01", periods=12, freq="MS"),
'Apples': [500, 596, 590, 523, 582, 515, 501, 551, 494, 467, 548, 490],
'Bananas': [350, 339, 349, 382, 328, 359, 403, 390, 459, 390, 373, 437],
'Elderberries': [160, 118, 118, 124, 179, 126, 117, 115, 157, 114, 120, 127],
})
plot_time_series(
data=df,
aspect_ratio='1:1',
title="Regional Supermarket Inventory and Fruit Category Performance Analysis",
subtitle="Comparative overview of total stock volume across key produce items to optimize supply chain distribution",
label_frequency="month",
line_labels='name',
value_suffix=' Kg',
)
Output:
4. Donut Chart
Draw a stylized donut chart with automatic label placement that avoids overlaps.
import pandas as pd
from clean_charts import plot_donut_chart
df = pd.DataFrame({
'Category': ['Apples', 'Bananas', 'Cherries', 'Dates', 'Elderberries', 'Figs', 'Grapes', 'Honeydew'],
'Sales': [400, 350, 300, 450, 120, 210, 520, 180]
})
plot_donut_chart(
data=df,
title="Regional Supermarket Inventory and Fruit Category Performance Analysis",
subtitle="Comparative overview of total stock volume across key produce items to optimize supply chain distribution",
value_suffix=' kg',
start_angle=60,
)
Output:
5. Stacked Horizontal Bar Chart
Visualize part-to-whole relationships across categories. Supports raw values or 100 % stacked mode.
import pandas as pd
from clean_charts import plot_stacked_bar_chart
df = pd.DataFrame({
'Year': ['2021', '2022', '2023', '2024', '2025', '2026'],
'Apples': [320, 340, 360, 380, 510, 415],
'Bananas': [390, 400, 405, 410, 180, 450],
'Cherries': [120, 135, 140, 150, 830, 590]
})
plot_stacked_bar_chart(
data = df,
title="Regional Supermarket Inventory and Fruit Category Performance Analysis",
subtitle="Comparative overview of total stock volume across key produce items to optimize supply chain distribution",
bar_padding=0.5,
value_suffix=' Kg',
show_percentages=True,
bar_labels='value',
aspect_ratio='1:1',
scale_text=False
)
Output:
6. Dashboard (Multi-Chart Layout)
Combine any mix of chart types into a single, cohesive dashboard image using plot_dashboard. Each sub-chart is rendered independently and composited onto a unified mosaic — no manual subplot wrangling needed.
import pandas as pd
from clean_charts import (
plot_time_series,
plot_barh_chart,
plot_donut_chart,
plot_stacked_bar_chart,
plot_dashboard,
)
# Prepare individual datasets
df_ts = pd.DataFrame({
'Dates': pd.date_range("2026-01-01", periods=12, freq="MS"),
'Apples': [500, 596, 590, 523, 582, 515, 501, 551, 494, 467, 548, 490],
'Bananas': [350, 339, 349, 382, 328, 359, 403, 390, 459, 390, 373, 437],
})
df_bar = pd.DataFrame({
'Category': ['Apples', 'Bananas', 'Cherries', 'Dates', 'Elderberries'],
'Sales': [400, 350, 300, 450, 120],
})
df_donut = pd.DataFrame({
'Category': ['Apples', 'Bananas', 'Cherries', 'Dates'],
'Sales': [400, 350, 300, 450],
})
df_stacked = pd.DataFrame({
'Year': ['2023', '2024', '2025', '2026'],
'Apples': [360, 380, 510, 415],
'Bananas': [405, 410, 180, 450],
'Cherries': [140, 150, 830, 590],
})
# Build the dashboard
plot_dashboard(
charts=[
(plot_time_series, {"data": df_ts, "title": "Monthly Trend"}),
(plot_barh_chart, {"data": df_bar, "title": "Top Items"}),
(plot_donut_chart, {"data": df_donut, "title": "Market Share"}),
(plot_stacked_bar_chart, {"data": df_stacked, "title": "Yearly Breakdown"}),
],
layout="AB\nCD", # 2×2 grid
title="Fruit Sales Overview — Q1 2026",
subtitle="A consolidated view of inventory, sales trends, and category distribution",
width=1400,
)
How it works
-
charts— A list of(plot_function, kwargs_dict)tuples. You can use any Clean Charts plot function (plot_time_series,plot_barh_chart,plot_barv_chart,plot_grouped_barh_chart,plot_donut_chart,plot_stacked_bar_chart). Do not includeoutput_pathin the kwargs — it is managed automatically. -
layout— An ASCII mosaic string where each unique letter maps to one chart in order. Use repeated letters to span cells across rows or columns:"AB\nCD" → 2×2 grid (default for 4 charts) "AA\nBC" → chart A spans the entire top row (2 columns) "AB\nAC" → chart A spans the entire left column (2 rows) "AB\nCC" → chart C spans the entire bottom row "AAB\nCDD" → 3-column layout with mixed spans "ABC" → single row, three equal columns "AA\nAA" → one chart fills the entire canvasIf omitted, charts are auto-arranged in a roughly square grid.
-
Consistent styling — All sub-charts share a unified scale factor and pixel margins, so titles, subtitles, labels, and margins align perfectly across all charts — even when charts span multiple columns or rows.
Multi-span layout example
Charts spanning 2+ columns or rows have the same title size and positioning as 1×1 charts:
# Wide chart on top, two standard charts below
plot_dashboard(
charts=[
(plot_time_series, {"data": df_ts, "title": "Revenue Trend", "subtitle": "24-month overview"}),
(plot_barh_chart, {"data": df_bar, "title": "Top Items", "subtitle": "By sales volume"}),
(plot_donut_chart, {"data": df_donut, "title": "Market Share", "subtitle": "By category"}),
],
layout="AA\nBC",
title="Executive Summary",
width=1400,
)
Output:
🎨 Customizing Data & Advanced Usage
Custom Time-Series Data
Supply your own pandas.DataFrame containing a date/time column and one or more value columns. The library automatically parses the datetime column and maps all other numeric columns as lines.
import pandas as pd
from clean_charts import plot_time_series
data = pd.DataFrame({
"Day": pd.date_range("2026-05-01", periods=10, freq="D"),
"Active Users": [120, 150, 190, 240, 220, 250, 270, 310, 340, 320],
"Signups": [15, 22, 35, 40, 28, 30, 32, 45, 52, 48],
})
plot_time_series(
data=data,
output_path="daily_stats.png",
title="Daily Server Growth",
subtitle="Active users and registrations in May 2026",
label_frequency="day", # "year" | "quarter" | "month" | "week" | "day" | "hour" | "minute" | "second"
start_color="#006400", # Dark green gradient start
end_color="#ffd700", # Gold gradient end
smooth=True, # Smooth PCHIP spline curves (default True)
markers=True, # Show circle markers on data points
line_labels="both", # Show "Series: value" inline labels
value_suffix="%", # Append "%" to Y-axis ticks and inline labels
)
Custom Horizontal Bar Chart Data
Pass a pandas.DataFrame where the first column contains string labels and the second column contains numeric values.
import pandas as pd
from clean_charts import plot_barh_chart
df = pd.DataFrame({
"Category": ["Apples", "Bananas", "Cherries", "Dates", "Elderberries", "Figs", "Grapes", "Honeydew"],
"Sales (tons)":[400, 350, 300, 450, 120, 210, 520, 180],
})
plot_barh_chart(
data=df,
output_path="fruit_sales.png",
title="Fruit Performance Analysis",
subtitle="Total sales volume by item",
value_suffix=" t",
color="#1f77b4",
)
Custom Grouped Bar Chart Data
Pass a DataFrame whose first column contains category labels and each subsequent column represents one series.
import pandas as pd
from clean_charts import plot_grouped_barh_chart
df = pd.DataFrame({
"Country": ["Germany", "France", "Italy", "Spain", "Poland"],
"BEV": [18, 14, 8, 5, 3],
"PHEV": [9, 7, 5, 4, 2],
"Hybrid": [22, 19, 12, 9, 6],
})
plot_grouped_barh_chart(
data=df,
output_path="ev_by_country.png",
title="EV Adoption by Country",
subtitle="Share of new car sales by powertrain, %",
value_suffix="%",
bar_labels="value", # "none" | "value" | "name" | "both"
start_color="#005f73",
end_color="#94d2bd",
)
📖 API Reference
plot_time_series
| Parameter | Type | Default | Description |
|---|---|---|---|
data |
pd.DataFrame |
None |
DataFrame with a datetime column and value series. Uses built-in sample data when None. |
output_path |
str |
None |
File path to save the image. Displays inline in Jupyter when None. |
width |
int |
1000 |
Target image width in pixels. |
height |
int |
562 |
Target image height in pixels. |
aspect_ratio |
str |
None |
"square" / "1:1", "landscape" / "2:1", "vertical" / "1:2". Overrides width/height. |
title |
str |
None |
Bold title text, left-aligned. Auto-wraps to 2 lines. |
subtitle |
str |
None |
Subtitle below the title. Auto-wraps to 2 lines. |
start_color |
str |
None |
Hex color for the first series in a gradient. |
end_color |
str |
None |
Hex color for the last series in a gradient. |
label_frequency |
str |
"year" |
X-axis tick frequency: "year", "quarter", "month", "week", "day", "hour", "minute", "second". |
markers |
bool / str |
None |
False/None = none, True = circles, or any matplotlib marker string (e.g. "s", "D"). |
line_labels |
str |
"name" |
Inline endpoint labels: "name", "value", "both", or "none". |
value_suffix |
str |
"" |
Appended to Y-axis ticks and inline value labels (e.g. "%"). |
smooth |
bool |
True |
Draw smooth PCHIP spline curves. Falls back to straight lines if scipy is missing. |
scale_text |
bool |
False |
Scale fonts and line weights proportionally to image size. |
plot_barh_chart
| Parameter | Type | Default | Description |
|---|---|---|---|
data |
pd.DataFrame |
None |
Column 0 = category strings, column 1 = numeric values. Uses built-in survey data when None. |
output_path |
str |
None |
File path to save the image. Displays inline when None. |
width |
int |
600 |
Target image width in pixels. |
height |
int |
None |
Auto-sized by number of categories when None. |
aspect_ratio |
str |
None |
"square" / "1:1", "landscape" / "2:1", "vertical" / "1:2". |
title |
str |
None |
Bold title text. |
subtitle |
str |
None |
Subtitle below the title. |
color |
str |
"#000000" |
Hex color for the bars. |
bar_padding |
float |
0.35 |
Fraction of bar slot left as gap (0.0–1.0). |
value_suffix |
str |
"" |
Appended to value labels and axis ticks. |
scale_text |
bool |
True |
Scale fonts proportionally to image size. |
plot_grouped_barh_chart
| Parameter | Type | Default | Description |
|---|---|---|---|
data |
pd.DataFrame |
None |
Column 0 = category labels, remaining columns = numeric series. |
output_path |
str |
None |
File path to save. Displays inline when None. |
width |
int |
600 |
Target image width in pixels. |
height |
int |
None |
Auto-sized when None. |
aspect_ratio |
str |
None |
"square" / "1:1", "landscape" / "2:1", "vertical" / "1:2". |
title |
str |
None |
Bold title text. Auto-wraps to 2 lines. |
subtitle |
str |
None |
Subtitle. Auto-wraps to 3 lines. |
start_color |
str |
"#000000" |
Gradient start color. |
end_color |
str |
"#2323FF" |
Gradient end color. |
bar_padding |
float |
0 |
Whitespace fraction within a single bar slot (0–1). |
group_padding |
float |
0.45 |
Spacing fraction between groups (0–1). |
value_suffix |
str |
"" |
Appended to axis tick labels. |
bar_labels |
str |
"none" |
"none", "value", "name", or "both". |
scale_text |
bool |
True |
Scale fonts proportionally to image size. |
plot_donut_chart
| Parameter | Type | Default | Description |
|---|---|---|---|
data |
pd.DataFrame |
None |
Two columns: category labels and values. |
output_path |
str |
None |
File path to save. Renders inline when None. |
width |
int |
600 |
Target image width in pixels. |
height |
int |
600 |
Target image height. Defaults to width. |
aspect_ratio |
str |
None |
"square" / "1:1", "landscape" / "2:1", "vertical" / "1:2". |
title |
str |
None |
Bold title text. Auto-wraps to 2 lines. |
subtitle |
str |
None |
Subtitle. Auto-wraps to 3 lines. |
colors |
list |
DEFAULT_COLORS |
List of hex colors for slices. |
start_color |
str |
None |
Gradient start (overrides colors when paired with end_color). |
end_color |
str |
None |
Gradient end. |
donut_radius |
float |
0.4 |
Outer radius relative to figure height. |
donut_thickness |
float |
0.15 |
Ring thickness relative to figure height. |
value_suffix |
str |
"" |
Appended to value labels. |
scale_text |
bool |
True |
Scale fonts proportionally to image size. |
show_percentages |
bool |
False |
Show percentage of total instead of raw value. |
start_angle |
int |
90 |
Starting angle for the first slice (degrees). |
plot_stacked_bar_chart
| Parameter | Type | Default | Description |
|---|---|---|---|
data |
pd.DataFrame |
None |
Column 0 = category strings, columns 1–N = numeric series. |
output_path |
str |
None |
File path to save. Renders inline when None. |
width |
int |
600 |
Target image width in pixels. |
height |
int |
None |
Auto-sized from categories when None. |
aspect_ratio |
str |
None |
"square" / "1:1", "landscape" / "2:1", "vertical" / "1:2". |
title |
str |
None |
Bold title text. Auto-wraps to 2 lines. |
subtitle |
str |
None |
Subtitle. Auto-wraps to 3 lines. |
colors |
list |
DEFAULT_COLORS |
List of hex colors for series. |
start_color |
str |
None |
Gradient start (overrides colors when paired with end_color). |
end_color |
str |
None |
Gradient end. |
bar_padding |
float |
0.30 |
Whitespace fraction within a bar slot (0–1). |
value_suffix |
str |
"" |
Appended to axis tick labels. |
bar_labels |
str |
"none" |
"none", "value", "name", or "both". |
scale_text |
bool |
True |
Scale fonts proportionally to image size. |
show_percentages |
bool |
False |
Convert to 100 % stacked bar chart with percentage labels. |
plot_dashboard
Combine multiple charts into a single composite image using a mosaic layout. Charts that span multiple columns or rows are rendered with the same title size and margin alignment as single-cell charts.
| Parameter | Type | Default | Description |
|---|---|---|---|
charts |
list[tuple] |
(required) | List of (plot_function, kwargs_dict) pairs. Accepts any Clean Charts function (plot_time_series, plot_barh_chart, plot_barv_chart, plot_grouped_barh_chart, plot_donut_chart, plot_stacked_bar_chart). output_path is managed internally — do not include it. |
layout |
str |
None |
ASCII mosaic string (e.g. "AB\nCD"). Each unique letter maps to one chart in order of first appearance. Repeat letters to span columns/rows. Auto-generates a grid when None. |
title |
str |
None |
Dashboard title rendered above the mosaic. |
subtitle |
str |
None |
Dashboard subtitle rendered below the title. |
output_path |
str |
None |
File path to save. Displays inline when None. |
width |
int |
1400 |
Final image width in pixels. |
height |
int |
None |
Auto-derived from layout proportions and width when None. |
padding |
float |
0.02 |
Fractional space between sub-charts (0–0.5). |
Layout examples:
"AB\nCD" → 2×2 grid (default for 4 charts)
"AA\nBC" → chart A spans full top row (2 columns)
"AB\nAC" → chart A spans full left column (2 rows)
"AB\nCC" → chart C spans full bottom row
"AAB\nCDD" → 3-column layout with mixed spans
"ABC" → single row, three equal columns
"AA\nAA" → one chart fills the entire canvas
Alignment note: All sub-charts automatically share a unified scale factor and pixel margins, ensuring titles, subtitles, labels, and axes are perfectly aligned across charts — even when they span different numbers of grid cells.
🎛️ Global Customization
Import and modify global configuration variables to apply a consistent theme across all charts:
import clean_charts.config as config
from clean_charts import plot_time_series
# Override styling tokens before plotting
config.BACKGROUND_COLOR = "#ffffff" # Pure white background
config.GRID_COLOR = "#eaeaea" # Light gridlines
config.AXIS_COLOR = "#333333" # Dark charcoal axes
plot_time_series(
title="Custom White Theme",
output_path="white_theme_chart.png"
)
Available Config Variables (clean_charts.config)
| Variable | Default | Description |
|---|---|---|
BACKGROUND_COLOR |
"#f4f3f0" |
Chart background (cream-gray). |
GRID_COLOR |
"#dcdbd7" |
Horizontal/vertical gridline color. |
AXIS_COLOR |
"#000000" |
Axis spine and tick color. |
TITLE_COLOR |
"#111111" |
Title text color. |
SUBTITLE_COLOR |
"#444444" |
Subtitle text color. |
LINE_COLOR |
"#000000" |
Default line/bar color. |
DEFAULT_COLOR |
"#000000" |
Fallback single-bar color. |
DEFAULT_START_COLOR |
"#000000" |
Gradient start for grouped bars. |
DEFAULT_END_COLOR |
"#2323FF" |
Gradient end for grouped bars. |
DEFAULT_COLORS_LIST |
['#000000', '#2323FF', ...] |
Default multi-series color palette. |
🧪 Examples & Development
Generate Sample Charts
python generate_sample.py
This produces a set of sample images showcasing different aspect ratios, gradient themes, label frequencies, and title-wrapping behavior.
Running Tests
python -m unittest tests/test_plot.py
📄 License
MIT © Raghuram Sirigiri
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file clean_charts-0.6.0.tar.gz.
File metadata
- Download URL: clean_charts-0.6.0.tar.gz
- Upload date:
- Size: 47.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
638aaf21857fbd8fc2c168f2ea45622dd3738f177ad71ffe08354364e2939b51
|
|
| MD5 |
190d8d1cdafbab5bfac20f512c5270e6
|
|
| BLAKE2b-256 |
c8e8c18a74f005182fc23d01a816ec65f3d0ca324f8bd436158b88b68b282bc0
|
File details
Details for the file clean_charts-0.6.0-py3-none-any.whl.
File metadata
- Download URL: clean_charts-0.6.0-py3-none-any.whl
- Upload date:
- Size: 42.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9cda255179ab8813fc55bea3695a1316541350e2593a86930bc11857ad3222b3
|
|
| MD5 |
2c3c220cea3b309b8ccf61deec189b6f
|
|
| BLAKE2b-256 |
01e4b9342c98bbb1d6174242d2443a974a3872b161bbd7dbaa48e5e806243e59
|