# Chart Image Generator API: JSON to PNG, SVG, PDF Charts (`actose/chart-image-renderer`) Actor

\[$2 / 1,000 charts] Turn JSON into PNG, SVG or PDF chart images: bar, line, area, pie, doughnut, gauge, radar, scatter, sparkline, with your theme, size, colors and logo. Chart.js configs accepted, bulk batches. No API key, MCP-ready for AI agents, errors are free. $2 per 1,000 charts.

- **URL**: https://apify.com/actose/chart-image-renderer.md
- **Developed by:** [ABM](https://apify.com/actose) (community)
- **Categories:** Developer tools, Automation, AI
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 chart rendereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Chart Image Generator API – JSON to PNG, SVG & PDF charts

**Price: $2 per 1,000 charts ($0.002 per rendered chart).** You pay only for charts that are actually rendered and saved. Invalid configurations are reported in the dataset and **never billed**. There is no start fee and no proxy cost.

Send chart data as JSON and get back a ready-to-use **PNG, SVG or PDF chart image** with a public link. It handles bar, horizontal bar, stacked bar, line, area, pie, doughnut, gauge, radar, scatter and sparkline charts. You can set the theme, size, colors, title, footer and logo. One run can render **one chart or a batch of hundreds**.

Rendering runs **inside the Actor** (no headless browser, no third-party chart website, no scraping). Results are fast and the same input always gives the same image.

### How much does it cost?

You pay **$0.002 per rendered chart**, which is **$2 per 1,000 charts**. You don't pay a monthly rental, a start fee or proxy costs.

| Run | Charts rendered | Price |
|---|---|---|
| One KPI chart for a daily e-mail | 1 | $0.002 |
| A weekly report with 25 charts | 25 | $0.05 |
| A monthly batch for 1,000 clients | 1,000 | $2.00 |

Charts that fail validation and exact duplicates in the same run are **free**. If you set a maximum cost per run, the Actor stops cleanly when it reaches that limit. It never adds unpaid extras.

### What it does

- **11 chart types**: `bar`, `horizontalBar`, `stackedBar`, `line`, `area`, `pie`, `doughnut`, `gauge`, `radar`, `scatter`, `sparkline`.
- **3 output formats**: PNG (e-mail, Slack, Teams, WhatsApp), SVG (web, sharp at any zoom), PDF (printable reports).
- **Size control**: 100–4000 px in each direction, with a pixel ratio up to 4× for retina PNGs.
- **Branding**: 4 themes (light, dark, minimal, corporate), your own color palette, a transparent background, a footer or watermark text, a logo from a URL, and value prefixes/suffixes (`$`, `%`, ` kg`).
- **Readable defaults**: color-blind-checked palette, thin grid lines and automatic label rotation. Long pie charts are grouped into "Other". The doughnut shows the total in its center.
- **Batch mode**: the `charts` array renders dozens or hundreds of charts in one run. Each chart gets its own file and dataset row.
- **Chart.js-compatible input**: paste an existing `{ "type": "bar", "data": { "labels": [...], "datasets": [...] } }` config.
- **Automation-friendly output**: each row has a direct `fileUrl`. Enable `includeDataUri` to also get a base64 `data:` URI you can embed straight into an e-mail or HTML in n8n, Make or Zapier.

### Input example – single chart

```json
{
  "type": "bar",
  "title": "Monthly sales",
  "labels": ["Jan", "Feb", "Mar"],
  "data": [12, 19, 7],
  "width": 800,
  "height": 400,
  "format": "png",
  "showValues": true,
  "valuePrefix": "$"
}
```

### Input example – batch with styling defaults

Top-level styling fields apply to every chart, and each chart can override them.

```json
{
  "format": "png",
  "theme": "dark",
  "width": 1000,
  "height": 500,
  "scale": 2,
  "colors": ["#2a78d6", "#eb6834", "#1baf7a"],
  "brandText": "Source: ACME analytics",
  "logoUrl": "https://example.com/logo.png",
  "charts": [
    {
      "chartId": "revenue",
      "type": "line",
      "title": "Revenue by quarter",
      "labels": ["Q1", "Q2", "Q3", "Q4"],
      "datasets": [
        { "label": "2025", "data": [120, 150, 170, 210] },
        { "label": "2026", "data": [140, 160, 205, 260] }
      ],
      "valuePrefix": "$"
    },
    {
      "chartId": "traffic-share",
      "type": "doughnut",
      "title": "Traffic sources",
      "labels": ["Organic", "Paid", "Social", "Direct"],
      "data": [48, 22, 18, 12],
      "valueSuffix": "%"
    },
    {
      "chartId": "nps",
      "type": "gauge",
      "title": "NPS",
      "value": 62,
      "gaugeMin": -100,
      "gaugeMax": 100,
      "gaugeThresholds": [
        { "value": -100, "color": "critical" },
        { "value": 0, "color": "warning" },
        { "value": 50, "color": "good" }
      ]
    },
    {
      "chartId": "chartjs-example",
      "type": "bar",
      "data": {
        "labels": ["Red", "Blue"],
        "datasets": [{ "label": "Votes", "data": [12, 19] }]
      },
      "options": { "plugins": { "title": { "display": true, "text": "Chart.js config" } } }
    }
  ]
}
```

#### Data shapes

| Chart type | Data fields |
|---|---|
| bar, horizontalBar, line, area, pie, doughnut, sparkline | `labels` + `data: [numbers]`, or `datasets: [{label, data, color}]` (up to 8 series) |
| stackedBar, radar | `labels` + `datasets` (radar needs 3+ labels) |
| gauge | `value` (+ `gaugeMin`, `gaugeMax`, `gaugeThresholds`) |
| scatter | `data: [{"x": 1, "y": 2}, ...]` or `[[1, 2], ...]`, or `datasets` of points |

Empty values (`null`) are allowed. Lines show a gap and bars show nothing.

### Output example

Each chart becomes one dataset row, and the image itself is stored in the run's key-value store.

```json
{
  "index": 1,
  "chartId": "revenue",
  "status": "success",
  "type": "line",
  "title": "Revenue by quarter",
  "format": "png",
  "fileKey": "revenue.png",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/AbC123/records/revenue.png",
  "contentType": "image/png",
  "sizeBytes": 48213,
  "width": 1000,
  "height": 500,
  "pixelWidth": 2000,
  "pixelHeight": 1000,
  "seriesCount": 2,
  "dataPoints": 8,
  "renderMs": 182,
  "markdown": "![Revenue by quarter](https://api.apify.com/v2/key-value-stores/AbC123/records/revenue.png)",
  "warnings": [],
  "error": null
}
```

If a chart is identical to an earlier one in the same run, you get `"status": "duplicate"` with the first chart's `fileUrl`, and it is not billed.

A failed chart (not billed):

```json
{
  "index": 2,
  "chartId": "chart-0002",
  "status": "error",
  "error": "Unknown chart type \"funnel\". Use one of: bar, horizontalBar, stackedBar, line, area, pie, doughnut, gauge, radar, scatter, sparkline",
  "fileKey": null,
  "fileUrl": null
}
```

### Use cases

#### 1. Daily sales e-mail for an e-commerce manager (n8n)

Every morning at 7:00, an n8n workflow reads yesterday's orders from Shopify. It sends the last 7 days to this Actor and puts the chart into the team e-mail. That is 1 chart a day, about $0.06 a month.

Input:

```json
{
  "type": "bar",
  "title": "Orders, last 7 days",
  "labels": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
  "data": [132, 118, 141, 155, 170, 210, 188],
  "showValues": true,
  "width": 600,
  "height": 300,
  "includeDataUri": true
}
```

Output (excerpt):

```json
[{ "chartId": "chart-0001", "status": "success", "fileUrl": "https://api.apify.com/v2/key-value-stores/AbC123/records/chart-0001.png", "dataUri": "data:image/png;base64,iVBORw0KG..." }]
```

Next step: the n8n "Send Email" node embeds `dataUri` as `<img src="...">`, so the image shows even when the mail client blocks remote images.

#### 2. Monthly client reports for a marketing agency (Make)

An agency sends a PDF report to each of 40 clients. Make loops over the Google Sheets rows and builds one `charts` array, with a traffic line chart and a channel doughnut per client. The run renders 80 charts for $0.16.

Input:

```json
{
  "format": "svg",
  "theme": "corporate",
  "brandText": "Prepared by Northwind Agency",
  "logoUrl": "https://example.com/agency-logo.png",
  "charts": [
    { "chartId": "acme-traffic", "type": "line", "title": "ACME – sessions", "labels": ["Jul", "Aug", "Sep"], "datasets": [{ "label": "2025", "data": [8200, 9100, 9900] }, { "label": "2026", "data": [10400, 11800, 12650] }] },
    { "chartId": "acme-channels", "type": "doughnut", "title": "ACME – channels", "labels": ["Organic", "Paid", "Social", "Email"], "data": [52, 21, 17, 10], "valueSuffix": "%" }
  ]
}
```

Output (excerpt):

```json
[
  { "chartId": "acme-traffic", "status": "success", "format": "svg", "fileUrl": ".../records/acme-traffic.svg" },
  { "chartId": "acme-channels", "status": "success", "format": "svg", "fileUrl": ".../records/acme-channels.svg" }
]
```

Next step: Make puts each `fileUrl` into a Google Docs template, exports it to PDF and e-mails it to the client.

#### 3. SLA gauge for a support lead in Slack (Zapier)

When the weekly helpdesk export lands, Zapier sends the first-response SLA to this Actor. It then posts the gauge to the #support channel. That is 52 charts a year, about $0.10.

Input:

```json
{
  "type": "gauge",
  "title": "First-response SLA",
  "value": 87,
  "valueSuffix": "%",
  "gaugeThresholds": [{ "value": 0, "color": "critical" }, { "value": 80, "color": "warning" }, { "value": 90, "color": "good" }],
  "width": 500,
  "height": 300
}
```

Output (excerpt):

```json
[{ "type": "gauge", "status": "success", "fileUrl": ".../records/chart-0001.png", "markdown": "![First-response SLA](.../records/chart-0001.png)" }]
```

Next step: the Slack step posts `fileUrl` as an image, which Slack unfurls inline.

#### 4. AI agent answering "show me the trend"

A data assistant built on Claude or ChatGPT queries a database and gets numbers. It calls this Actor as an MCP tool and pastes the returned `markdown` field into its answer, so the user sees a real chart instead of a table.

### Use with AI agents (MCP)

This Actor works as a tool in the Apify MCP server, so Claude Desktop, Claude Code, Cursor, ChatGPT and other MCP clients can call it. Add `https://mcp.apify.com/?tools=<username>/chart-image-renderer` to your MCP client. The agent sends the chart JSON and gets back `fileUrl` and a ready-to-paste `markdown` image tag. The input fields have plain names (`type`, `labels`, `data`, `title`), so LLMs fill them in correctly without examples.

### Call it from the API

The synchronous endpoint `run-sync-get-dataset-items` returns the rows in a single HTTP call.

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("<username>/chart-image-renderer").call(run_input={
    "type": "line", "title": "Signups", "labels": ["W1", "W2", "W3", "W4"], "data": [40, 52, 61, 75],
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["status"], item["fileUrl"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<APIFY_TOKEN>' });
const run = await client.actor('<username>/chart-image-renderer').call({
    type: 'pie', title: 'Budget', labels: ['Ads', 'Content', 'Tools'], data: [50, 30, 20], format: 'svg',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((i) => i.fileUrl));
```

cURL (one call, rows returned directly):

```bash
curl -X POST "https://api.apify.com/v2/acts/<username>~chart-image-renderer/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"type":"bar","labels":["Jan","Feb","Mar"],"data":[12,19,7]}'
```

### Integrations: n8n, Make, Zapier, Google Sheets

- **n8n**: add the Apify node, choose "Run Actor and get dataset", pick this Actor and map your data to `labels` and `data`. Then use `{{$json.fileUrl}}` or `{{$json.dataUri}}` in the Email, Slack or Telegram node.
- **Make**: use the Apify module "Run an Actor" (synchronous) followed by "Get Dataset Items". Map `fileUrl` into Gmail, Slack, Google Docs or Google Slides.
- **Zapier**: use the Apify action "Run Actor", then "Find Last Dataset Items", and send `fileUrl` to any app.
- **Google Sheets**: use Apify's Google Sheets integration, or run Make or n8n on a schedule. Read a range, build `labels` and `data` from the columns, and write `fileUrl` back with `=IMAGE(url)` to show the chart in a cell.

### FAQ

**Do I pay for charts that fail?**
No. You are billed through the `chart-rendered` event only for charts that were rendered, saved and delivered. Errors are listed in the dataset with a clear reason, and you pay nothing for them or for duplicates.

**Do I need an API key or an account on a chart service?**
No. You only need your Apify account. No other API key or login is used.

**How long are the image links valid?**
They last as long as the run's storage is kept, which depends on your Apify plan's data retention. Copy the files elsewhere if you need them permanently.

**Is my data sent to a third-party service?**
No. Charts are drawn inside the Actor's container. The only outbound request is the optional download of your `logoUrl`.

**Can I use my Chart.js configs?**
Yes, for the common parts: `type`, `data.labels`, `data.datasets[].label/data/backgroundColor/borderColor`, and `options.plugins.title.text`. Plugins and custom JavaScript callbacks are not executed.

**Can I control the spending?**
Yes. Set a maximum charge for the run, and the Actor stops cleanly once the limit is reached. `maxCharts` adds a cap on the batch size too.

**What if the logo URL is broken?**
The chart is still rendered without the logo, and the row contains a warning.

### Limits

- Up to 1,000 charts per run and 8 series per chart. Each image can be up to 4000 px on a side and 16 megapixels in total, counting the PNG pixel ratio (for example 4000 × 4000, or 2000 × 2000 at scale 2). With the default 256 MB run memory, PNGs are capped at 8 megapixels (for example 4000 × 2000, or 1600 × 1200 at scale 2); run with 512 MB for up to 16 megapixels. SVG and PDF are vector files and are not affected.
- Static images only (no interactive HTML or animations).
- No dual-axis charts, by design (two scales on one chart mislead readers). Use two charts instead.
- Pie and doughnut charts with more slices than palette colors group the smallest slices into "Other".
- Font: DejaVu Sans (Latin, Greek and Cyrillic). CJK and emoji characters may not render.
- Logo: public PNG or JPEG, 5 MB max.

# Actor input Schema

## `type` (type: `string`):

Kind of chart. Chart.js names also work (e.g. "doughnut", "horizontalBar").

## `title` (type: `string`):

Chart title shown at the top left.

## `labels` (type: `array`):

One label per data point, e.g. months or product names.

## `data` (type: `array`):

Numbers for a single series, e.g. \[12, 19, 7]. For scatter use \[{"x":1,"y":2}]. You may also paste a full Chart.js "data" object here via the batch list.

## `datasets` (type: `array`):

Several series: \[{"label": "2025", "data": \[1,2,3], "color": "#2a78d6"}]. Overrides "Data" when filled. Max 8 series per chart.

## `value` (type: `number`):

Only for gauge charts: the value to display.

## `charts` (type: `array`):

Render many charts in one run. Each item is a chart object with the same fields as this form (type, title, labels, data, datasets, value, chartId, plus any styling field to override). Chart.js-style configs ({"type":"bar","data":{"labels":\[],"datasets":\[]}}) are accepted. When this list is filled, the single-chart data fields above are ignored.

## `format` (type: `string`):

PNG for e-mails and chat, SVG for the web (sharp at any size), PDF for printable reports.

## `width` (type: `integer`):

Chart width in pixels (100–4000).

## `height` (type: `integer`):

Chart height in pixels (100–4000).

## `scale` (type: `number`):

Multiply PNG resolution (2 = retina-sharp). Ignored for SVG/PDF.

## `theme` (type: `string`):

Colors of background, text, grid and default series palette (color-blind-checked).

## `colors` (type: `array`):

Your brand palette, in order (hex or CSS names). Leave empty for the theme palette.

## `backgroundColor` (type: `string`):

Hex color, CSS name, or "transparent". Empty = theme background.

## `subtitle` (type: `string`):

Smaller text under the title.

## `xAxisLabel` (type: `string`):

Text under the horizontal axis.

## `yAxisLabel` (type: `string`):

Text next to the vertical axis.

## `showValues` (type: `boolean`):

Print each value on bars / points (percentages on pie charts, on by default there).

## `showLegend` (type: `boolean`):

Legend is shown when there are 2+ named series, and on pie/doughnut charts.

## `showGrid` (type: `boolean`):

Light horizontal grid lines behind the data.

## `yMin` (type: `number`):

Force the value axis start (empty = automatic).

## `yMax` (type: `number`):

Force the value axis end (empty = automatic).

## `valuePrefix` (type: `string`):

Added before numbers, e.g. "$".

## `valueSuffix` (type: `string`):

Added after numbers, e.g. "%" or " kg".

## `decimals` (type: `integer`):

Fixed number of decimals for value labels (empty = automatic).

## `fontScale` (type: `number`):

1 = default; 1.5 for large dashboards or small thumbnails.

## `brandText` (type: `string`):

Small text at the bottom right, e.g. "Source: ACME analytics".

## `logoUrl` (type: `string`):

Public PNG or JPEG URL (max 5 MB), downloaded once per run. If it fails, the chart is still rendered without logo.

## `logoHeight` (type: `integer`):

Logo height in pixels.

## `logoPosition` (type: `string`):

Corner where the logo is placed.

## `gaugeMin` (type: `number`):

Gauge start value (default 0).

## `gaugeMax` (type: `number`):

Gauge end value (default 100).

## `gaugeThresholds` (type: `array`):

Arc color by value: \[{"value": 0, "color": "critical"}, {"value": 50, "color": "warning"}, {"value": 80, "color": "good"}]. Colors: good / warning / critical or any hex.

## `centerLabel` (type: `string`):

Text under the total in the doughnut hole (default "Total").

## `includeDataUri` (type: `boolean`):

Adds the image as a data: URI (files up to 1.5 MB) so n8n / Make / Zapier can embed it without a second download.

## `maxCharts` (type: `integer`):

Safety cap on the batch size (each rendered chart is billed).

## Actor input object example

```json
{
  "type": "bar",
  "title": "Monthly sales",
  "labels": [
    "Jan",
    "Feb",
    "Mar"
  ],
  "data": [
    12,
    19,
    7
  ],
  "charts": [
    {
      "chartId": "revenue",
      "type": "line",
      "title": "Revenue",
      "labels": [
        "Q1",
        "Q2",
        "Q3",
        "Q4"
      ],
      "datasets": [
        {
          "label": "2025",
          "data": [
            120,
            150,
            170,
            210
          ]
        },
        {
          "label": "2026",
          "data": [
            140,
            160,
            205,
            260
          ]
        }
      ]
    },
    {
      "chartId": "nps",
      "type": "gauge",
      "title": "NPS",
      "value": 62,
      "gaugeMin": -100,
      "gaugeMax": 100
    }
  ],
  "format": "png",
  "width": 800,
  "height": 400,
  "scale": 1,
  "theme": "light",
  "showValues": false,
  "showLegend": true,
  "showGrid": true,
  "fontScale": 1,
  "logoHeight": 40,
  "logoPosition": "top-right",
  "includeDataUri": false,
  "maxCharts": 1000
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `files` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "type": "bar",
    "title": "Monthly sales",
    "labels": [
        "Jan",
        "Feb",
        "Mar"
    ],
    "data": [
        12,
        19,
        7
    ],
    "format": "png",
    "width": 800,
    "height": 400
};

// Run the Actor and wait for it to finish
const run = await client.actor("actose/chart-image-renderer").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "type": "bar",
    "title": "Monthly sales",
    "labels": [
        "Jan",
        "Feb",
        "Mar",
    ],
    "data": [
        12,
        19,
        7,
    ],
    "format": "png",
    "width": 800,
    "height": 400,
}

# Run the Actor and wait for it to finish
run = client.actor("actose/chart-image-renderer").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "type": "bar",
  "title": "Monthly sales",
  "labels": [
    "Jan",
    "Feb",
    "Mar"
  ],
  "data": [
    12,
    19,
    7
  ],
  "format": "png",
  "width": 800,
  "height": 400
}' |
apify call actose/chart-image-renderer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,actose/chart-image-renderer"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/eeQxtcRj6owSarhdM/builds/aZ5ebtXJUhuhtlkXk/openapi.json
