asciichartpy plots line charts in the terminal using pure ASCII characters. pip install asciichartpy. Basic: import asciichartpy; print(asciichartpy.plot([1,4,9,16,25])). Series with height: asciichartpy.plot(data, {"height": 10}). Min/max: {"min": 0, "max": 100}. Format: {"format": "{:8.2f}"} — controls y-axis label format. Multiple series: asciichartpy.plot([series_a, series_b], {"height": 10, "colors": [asciichartpy.blue, asciichartpy.red]}). Colors: asciichartpy.red | .green | .yellow | .blue | .magenta | .cyan | .lightred | .lightgreen | .lightyellow | .lightblue | .lightmagenta | .lightcyan. Single series no config: plot(series) uses auto-scaled height. Config dict keys: height (rows), min/max (y range), offset (left indent), padding (axis label padding), format (axis label pattern), colors (list of color codes). Scrolling: from collections import deque; buf = deque([0]*80, maxlen=80) then buf.append(new_value); print(asciichartpy.plot(list(buf))). Sparkline: small height (3–5) with no axis labels. CPU: psutil.cpu_percent() → append to deque → plot. Memory: psutil.virtual_memory().percent. Histogram: bin data then plot bin counts. Normalize: divide by max. Overlay: pass list of equal-length lists. os.system("clear") for live refresh. Claude Code generates asciichartpy dashboards, sparklines, and real-time metric plotters.
CLAUDE.md for asciichartpy
## asciichartpy Stack
- Version: asciichartpy >= 1.5 | pip install asciichartpy
- Single: asciichartpy.plot(series) | plot(series, {"height": 10})
- Multi: plot([series_a, series_b], {"colors": [asciichartpy.blue, asciichartpy.red]})
- Axes: {"min": 0, "max": 100, "format": "{:8.1f}"}
- Scrolling: deque(maxlen=80) → append → plot(list(buf)) with os.system("clear")
- Colors: .red .green .yellow .blue .magenta .cyan + light variants
- Sparkline: {"height": 3} — small chart for inline dashboards
asciichartpy Terminal Chart Pipeline
# app/charts.py — asciichartpy line charts, sparklines, and real-time monitors
from __future__ import annotations
import math
import os
import time
from collections import deque
from typing import Any
import asciichartpy
# ─────────────────────────────────────────────────────────────────────────────
# 1. Basic chart helpers
# ─────────────────────────────────────────────────────────────────────────────
def plot(
series: list[float],
height: int = 10,
min_val: float | None = None,
max_val: float | None = None,
fmt: str = "{:8.2f}",
offset: int = 3,
padding: str = " ",
title: str = "",
) -> str:
"""
Render a single series as a terminal line chart.
title: optional header line above the chart.
"""
config: dict[str, Any] = {
"height": height,
"format": fmt,
"offset": offset,
"padding": padding,
}
if min_val is not None:
config["min"] = min_val
if max_val is not None:
config["max"] = max_val
chart = asciichartpy.plot(series, config)
if title:
width = max(len(line) for line in chart.split("\n"))
header = title.center(width)
return f"{header}\n{chart}"
return chart
def plot_multi(
series_list: list[list[float]],
colors: list[str] | None = None,
height: int = 10,
min_val: float | None = None,
max_val: float | None = None,
fmt: str = "{:8.2f}",
title: str = "",
) -> str:
"""
Render multiple series as an overlaid terminal line chart.
colors: list of asciichartpy color constants (e.g. [asciichartpy.blue, asciichartpy.red]).
"""
default_colors = [
asciichartpy.blue,
asciichartpy.red,
asciichartpy.green,
asciichartpy.yellow,
asciichartpy.magenta,
asciichartpy.cyan,
]
used_colors = (colors or default_colors)[:len(series_list)]
config: dict[str, Any] = {
"height": height,
"format": fmt,
"colors": used_colors,
}
if min_val is not None:
config["min"] = min_val
if max_val is not None:
config["max"] = max_val
chart = asciichartpy.plot(series_list, config)
if title:
width = max(len(line) for line in chart.split("\n"))
return f"{title.center(width)}\n{chart}"
return chart
def sparkline(series: list[float], width: int = 60) -> str:
"""
Render a compact 3-row sparkline — good for inline dashboards.
Truncates or pads series to `width` data points.
"""
data = series[-width:] if len(series) > width else series
return asciichartpy.plot(data, {"height": 3})
# ─────────────────────────────────────────────────────────────────────────────
# 2. Data normalisation helpers
# ─────────────────────────────────────────────────────────────────────────────
def normalize(series: list[float], low: float = 0.0, high: float = 1.0) -> list[float]:
"""Scale series to [low, high]."""
mn, mx = min(series), max(series)
if mx == mn:
return [low] * len(series)
return [low + (v - mn) / (mx - mn) * (high - low) for v in series]
def smooth(series: list[float], window: int = 3) -> list[float]:
"""Simple moving-average smoothing."""
result = []
half = window // 2
for i, _ in enumerate(series):
lo = max(0, i - half)
hi = min(len(series), i + half + 1)
result.append(sum(series[lo:hi]) / (hi - lo))
return result
def histogram_series(
data: list[float],
bins: int = 20,
) -> tuple[list[float], list[float]]:
"""
Bin data into a histogram.
Returns (bin_centers, counts) — plot counts as a bar approximation.
"""
if not data:
return [], []
mn, mx = min(data), max(data)
width = (mx - mn) / bins if mx != mn else 1.0
counts = [0.0] * bins
for v in data:
idx = min(int((v - mn) / width), bins - 1)
counts[idx] += 1
centers = [mn + (i + 0.5) * width for i in range(bins)]
return centers, counts
# ─────────────────────────────────────────────────────────────────────────────
# 3. Scrolling buffer for real-time charts
# ─────────────────────────────────────────────────────────────────────────────
class ScrollingChart:
"""
Fixed-width scrolling chart backed by deque.
Usage:
chart = ScrollingChart(width=80, height=8, title="CPU %")
while True:
chart.push(psutil.cpu_percent())
print(chart.render())
time.sleep(1)
"""
def __init__(
self,
width: int = 80,
height: int = 8,
title: str = "",
min_val: float | None = 0.0,
max_val: float | None = None,
color: str | None = None,
fmt: str = "{:6.1f}",
):
self._buf: deque[float] = deque([0.0] * width, maxlen=width)
self._width = width
self._height = height
self._title = title
self._min = min_val
self._max = max_val
self._color = color
self._fmt = fmt
def push(self, value: float) -> None:
self._buf.append(value)
def render(self) -> str:
series = list(self._buf)
if self._color:
return plot_multi(
[series],
colors=[self._color],
height=self._height,
min_val=self._min,
max_val=self._max,
fmt=self._fmt,
title=self._title,
)
return plot(
series,
height=self._height,
min_val=self._min,
max_val=self._max,
fmt=self._fmt,
title=self._title,
)
def clear_screen_and_render(self) -> None:
os.system("clear")
print(self.render())
# ─────────────────────────────────────────────────────────────────────────────
# 4. Multi-metric dashboard
# ─────────────────────────────────────────────────────────────────────────────
class MetricDashboard:
"""
Display multiple scrolling charts stacked vertically.
Usage:
dash = MetricDashboard(width=60, height=5)
dash.add("CPU %", color=asciichartpy.green, min_val=0, max_val=100)
dash.add("Memory %", color=asciichartpy.blue, min_val=0, max_val=100)
while True:
dash.push("CPU %", psutil.cpu_percent())
dash.push("Memory %", psutil.virtual_memory().percent)
dash.render()
time.sleep(1)
"""
def __init__(self, width: int = 60, height: int = 5):
self._width = width
self._height = height
self._charts: dict[str, ScrollingChart] = {}
self._order: list[str] = []
def add(
self,
name: str,
color: str | None = None,
min_val: float | None = 0.0,
max_val: float | None = None,
fmt: str = "{:6.1f}",
) -> None:
self._charts[name] = ScrollingChart(
width=self._width,
height=self._height,
title=name,
min_val=min_val,
max_val=max_val,
color=color,
fmt=fmt,
)
self._order.append(name)
def push(self, name: str, value: float) -> None:
if name in self._charts:
self._charts[name].push(value)
def render(self) -> None:
os.system("clear")
for name in self._order:
print(self._charts[name].render())
print()
# ─────────────────────────────────────────────────────────────────────────────
# 5. psutil helpers
# ─────────────────────────────────────────────────────────────────────────────
def cpu_chart(samples: int = 60, interval: float = 0.5, height: int = 8) -> None:
"""
Collect CPU% samples and plot a static chart.
Requires: pip install psutil
"""
try:
import psutil
except ImportError:
raise ImportError("pip install psutil")
data: list[float] = []
for _ in range(samples):
data.append(psutil.cpu_percent(interval=interval))
print(plot(data, height=height, min_val=0, max_val=100, title="CPU Usage (%)"))
def live_cpu_chart(
width: int = 60,
height: int = 8,
interval: float = 0.5,
duration: float = 30.0,
) -> None:
"""
Live-updating CPU chart — clears screen and redraws each tick.
Requires: pip install psutil
"""
try:
import psutil
except ImportError:
raise ImportError("pip install psutil")
chart = ScrollingChart(
width=width,
height=height,
title="CPU % (live)",
min_val=0,
max_val=100,
color=asciichartpy.green,
)
end = time.time() + duration
while time.time() < end:
chart.push(psutil.cpu_percent())
chart.clear_screen_and_render()
time.sleep(interval)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import random
print("=== Single series (sine wave) ===")
sine = [math.sin(x * 0.3) * 10 for x in range(60)]
print(plot(sine, height=8, title="Sine Wave"))
print("\n=== Multi-series (sine + cosine) ===")
cosine = [math.cos(x * 0.3) * 10 for x in range(60)]
print(plot_multi(
[sine, cosine],
colors=[asciichartpy.blue, asciichartpy.red],
height=8,
title="Sine (blue) vs Cosine (red)",
))
print("\n=== Sparkline ===")
noise = [random.gauss(50, 10) for _ in range(60)]
print(sparkline(noise))
print("\n=== Histogram approximation ===")
dist = [random.gauss(50, 10) for _ in range(500)]
centers, counts = histogram_series(dist, bins=30)
print(plot(counts, height=6, min_val=0, title="Normal Distribution (~500 samples)"))
print("\n=== Smoothed noisy series ===")
noisy = [math.sin(x * 0.3) * 10 + random.gauss(0, 2) for x in range(80)]
smoothed = smooth(noisy, window=7)
print(plot_multi(
[noisy, smoothed],
colors=[asciichartpy.red, asciichartpy.green],
height=8,
title="Raw (red) vs Smoothed (green)",
))
print("\n=== Scrolling chart simulation ===")
schart = ScrollingChart(width=60, height=6, title="Live Metric", min_val=0, max_val=100)
base = 50.0
for i in range(60):
base += random.gauss(0, 3)
base = max(0, min(100, base))
schart.push(base)
print(schart.render())
For the plotext alternative — plotext supports scatter plots, bar charts, histograms, date axes, and image rendering in the terminal, making it more feature-complete than asciichartpy; asciichartpy is a single-file library that does one thing extremely well — rendering line series with no dependencies beyond Python — making it ideal when you only need line charts and want the smallest possible dependency. For the uniplot alternative — uniplot renders Unicode braille charts with higher visual resolution than ASCII, offers logarithmic axes and color via ANSI, and is well-suited for scientific data; asciichartpy works in any terminal including SSH sessions and CI logs that strip Unicode, because it sticks to plain ASCII characters. The Claude Skills 360 bundle includes asciichartpy skill sets covering asciichartpy.plot() with height/min/max/format config, multi-series overlay with colors list, plot() and plot_multi() helpers, sparkline() compact chart, normalize() and smooth() data helpers, histogram_series() binning, ScrollingChart deque-backed live chart, MetricDashboard multi-chart vertical stacking, cpu_chart() static CPU sample chart, and live_cpu_chart() real-time updating display. Start with the free tier to try terminal chart code generation.