Back to MCP Servers

Charted

Zero-dependency chart server that renders bar, line, pie, scatter, and more from JSON or CSV to SVG, HTML, PNG, or data URL. Built-in themes; PNG output renders inline in chat. Install via `uvx --from charted[mcp] charted-mcp`.

data-visualization
By marzukia
6Updated 2 weeks agoPythonMIT

Installation

uvx --from charted[mcp] charted-mcp

Configuration

{
  "mcpServers": {
    "charted": {
      "command": "npx",
      "args": ["-y", "charted"]
    }
  }
}

How to use

  1. Run the installation command above (if needed)
  2. Open your Claude Code settings file (~/.claude/settings.json)
  3. Add the configuration to the mcpServers section
  4. Restart Claude Code to apply changes

charted-logo

Zero-dependency charts for Python and AI agents. SVG and PNG, 15 chart types, a one-line MCP server, and a Claude skill.

codecov charted-ci

<!-- TODO: demo GIF: an agent turning a CSV into a chart inline -->

Quickstart

uv add charted

Or with pip:

pip install charted
from charted import BarChart

chart = BarChart(
    title="Sales by Quarter",
    data=[120, 180, 210, 150],
    labels=["Q1", "Q2", "Q3", "Q4"],
)
chart.save("chart.svg")
chart.save("chart.png")  # PNG export (requires cairosvg)

That is the whole loop: pass a list of numbers, get back an image. No numpy, no pandas, no config files.

Core principle: charted itself has zero runtime dependencies. PNG export and MCP server support are opt-in extras that pull in their own dependencies, and the base library stays pure Python.


Gallery

Every chart type, rendered. Each image below was generated by charted itself from the example script; the Quick Tour shows the code behind each one.

Bar barColumn columnLine line
Scatter scatterPie pieArea area
Radar radarBox Plot boxplotHistogram histogram
Heatmap heatmapGantt ganttBubble bubble
Combo comboPolar Area polar area

Sankey diagrams are also supported; see the Quick Tour for the flow and funnel examples.


Integration

Run the MCP server with no install using uvx:

uvx --from charted[mcp] charted-mcp

This fetches charted with the MCP extra into a throwaway environment and starts the server over stdio, so an agent can generate charts without you adding charted to a project or virtualenv.

Client config

ClientSetup
Claude Codeclaude mcp add charted -- uvx --from charted[mcp] charted-mcp
Cursor, Cline, other clientsAdd the JSON below to the client's MCP server config
{
  "mcpServers": {
    "charted": {
      "command": "uvx",
      "args": ["--from", "charted[mcp]", "charted-mcp"]
    }
  }
}

The server exposes create_chart, list_chart_types, list_themes, and chart_from_csv. See the MCP Server section below for tool details and the pip install path.

Claude Skill

Install the chart Skill in one line:

claude skill install charted

Chart gallery

The Quick Tour renders every chart type with the code that produced it. The same examples live in docs/examples/.


Why Charted?

  • Zero runtime dependencies: pure Python, no numpy/pandas required
  • 15 chart types: Bar, Column, Line, Scatter, Pie, Area, Radar, Box Plot, Histogram, Heatmap, Gantt, Bubble, Combo, Polar Area, Sankey
  • Multi-series support: stacked, side-by-side, grouped layouts
  • Negative values handled: proper zero baseline calculations
  • SVG and PNG output: SVG natively, PNG via optional cairosvg (pip install charted[png])
  • Theme system: 3 built-in presets + custom theme composition
  • Per-series styling: granular control with SeriesStyle builders
  • Data loading: CSV/JSON parsers built-in
  • Markdown export: generate embed-ready markdown snippets
  • CLI included: create charts without writing Python code
  • Jupyter ready: charts render inline automatically
  • Base Chart class: unified API for dynamic chart type selection

Quick Tour

Every chart type shares the same simple interface: pass data, labels, dimensions, and a title:

from charted.charts import BarChart, LineChart, PieChart

# Bar: single series with negatives
BarChart(
    title="Profit/Loss by Region ($M)",
    data=[-12, 34, -8, 52, -5, 28, 41, -19, 15, 60],
    labels=["North", "South", "East", "West", "Central", "Pacific", "Atlantic", "Mountain", "Plains", "Metro"],
    width=700, height=500,
).save("bar.svg")

# Bar: multi-series side-by-side
BarChart(
    title="Revenue vs Expenses by Quarter ($K)",
    data=[[120, -45, 180, -30, 210, -60], [-80, -20, -95, -15, -110, -25]],
    labels=["Q1 Prod", "Q1 Ops", "Q2 Prod", "Q2 Ops", "Q3 Prod", "Q3 Ops"],
    width=700, height=500,
).save("bar_multi.svg")

# Bar: stacked
BarChart(
    title="Budget by Department ($K)",
    data=[[100, -50, 120], [80, 60, -40]],
    labels=["Q1", "Q2", "Q3"],
    series_names=["Revenue", "Expenses"],
    x_stacked=True, width=700, height=400,
).save("bar_stacked.svg")

# Bar: side-by-side with negatives
BarChart(
    title="Revenue vs Expenses by Quarter ($K)",
    data=[[120, 180, 210], [-80, -95, -110]],
    labels=["Q1", "Q2", "Q3"],
    series_names=["Revenue", "Expenses"],
    width=700, height=400,
).save("bar_sidebyside.svg")


# Column: multi-series with negatives
from charted.charts import ColumnChart

ColumnChart(
    title="Year-over-Year Growth Rate (%) by Segment",
    data=[[12, -8, 22, 18, -5, 30], [-3, -15, 5, -2, -20, 8], [9, -23, 17, 16, -25, 38]],
    labels=["Q1", "Q2", "Q3", "Q4", "Q5", "Q6"],
    width=700, height=500,
    theme={"v_padding": 0.12, "h_padding": 0.10},
).save("column.svg")

# Column: stacked (default for multi-series)
ColumnChart(
    title="Year-over-Year Growth by Segment",
    data=[[12, 22, 30], [-8, -15, -20], [4, 7, 10]],
    labels=["Q1", "Q2", "Q3"],
    series_names=["Revenue", "Costs", "Net"],
    width=700, height=400,
).save("column_stacked.svg")

# Column: side-by-side
ColumnChart(
    title="Sales Performance by Region",
    data=[[45, 52, 38, 61], [38, 46, 52, 49], [52, 39, 46, 51]],
    labels=["Q1", "Q2", "Q3", "Q4"],
    series_names=["North", "South", "East"],
    width=700, height=400, y_stacked=False,
).save("column_sidebyside.svg")


# Line: multi-series signal data
import math
from charted.charts import LineChart

n = 20
LineChart(
    title="Signal Analysis: Raw vs Filtered vs Baseline",
    data=[
        [math.sin(i * 0.5) * 30 + (i % 7 - 3) * 5 for i in range(n)],
        [math.sin(i * 0.5) * 25 for i in range(n)],
        [math.sin(i * 0.5) * 10 - 5 for i in range(n)],
    ],
    labels=[str(i) for i in range(n)],
    width=700, height=400,
).save("line.svg")

# Line: XY mode with temperature anomaly data
years = list(range(1990, 2010))
anomalies = [-15, -5, 10, 20, 5, 25, 15, 30, 10, 20, 40, 25, 45, 30, 50, 35, 60, 55, 45, 70]
baseline = [round(5 + 2 * math.sin(i * 0.4) + i * 0.5, 1) for i in range(len(years))]

LineChart(
    title="Temperature Anomaly vs 5-Year Rolling Baseline (1990-2009)",
    data=[anomalies, baseline],
    x_data=years,
    labels=[str(y) for y in years],
    width=700, height=400,
).save("xy_line.svg")

# Line: single series
LineChart(
    title="Monthly Active Users (K)",
    data=[[42, 48, 55, 61, 58, 70, 80, 78, 85, 92, 88, 100]],
    labels=["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
    series_names=["MAU"], width=700, height=400,
).save("line_single.svg")


# Line: log scale (opt-in) for data spanning orders of magnitude.
# Pass y_scale="log" (or x_scale="log"); a LogScale instance also works.
LineChart(
    title="Requests/sec (log scale)",
    data=[[1, 8, 60, 450, 3200, 25000]],
    labels=["t0", "t1", "t2", "t3", "t4", "t5"],
    series_names=["rps"],
    y_scale="log",
    width=700, height=400,
).save("line_log.svg")


# Scatter: multi-series cluster analysis
import random
from charted.charts import ScatterChart

random.seed(42)
ca_x = [30 + random.gauss(0, 8) for _ in range(20)]
ca_y = [40 + random.gauss(0, 8) for _ in range(20)]
cb_x = [70 + random.gauss(0, 10) for _ in range(20)]
cb_y = [20 + random.gauss(0, 10) for _ in range(20)]

ScatterChart(
    title="Cluster Analysis: Two Distinct Populations",
    x_data=[ca_x, cb_x], y_data=[ca_y, cb_y],
    series_names=["Cluster A", "Cluster B"],
    width=700, height=400,
).save("scatter.svg")

# Scatter: single series with quadratic curve
random.seed(1)
x_vals = [i for i in range(5, 95, 5)]
y_vals = [round(10 + (v - 50) ** 2 / 50 + random.gauss(0, 4), 1) for v in x_vals]

ScatterChart(
    title="U-Shaped Response Curve: Signal vs Input",
    x_data=x_vals, y_data=y_vals,
    series_names=["Observations"],
    width=700, height=400,
).save("scatter_single.svg")


# Pie: basic
from charted.charts import PieChart

PieChart(
    title="Market Share by Product Line",
    data=[35, 28, 18, 12, 7],
    labels=["Product A", "Product B", "Product C", "Product D", "Other"],
    width=600, height=500,
).save("pie.svg")

# Pie: doughnut mode
PieChart(
    title="Operating System Market Share",
    data=[72, 15, 8, 5],
    labels=["Windows", "macOS", "Linux", "Other"],
    inner_radius=0.5, width=600, height=500,
).save("pie_doughnut.svg")


# Radar: multi-series
from charted.charts import RadarChart

RadarChart(
    title="Player Skill Comparison",
 

…
View source on GitHub