How to Generate Comparison Graph Images with an API
Create comparison graph PNGs from JSON with Chart.js APIs, choose GET or POST, size outputs for email, and troubleshoot production issues.
Direct answer: represent your comparison as a Chart.js configuration, send it to a chart-image API such as QuickChart, and save the returned PNG, JPEG, WebP, SVG, PDF, or base64 result. Use GET for short configurations and POST for large or complex charts. Set dimensions, pixel ratio, background, format, and Chart.js version for the surface where the image will appear.
1. The request-to-image workflow
- Convert your source data into shared category labels and one dataset per item or series.
- Build a Chart.js configuration with a chart type such as
bar,line,pie, orradar. - Send the configuration to a chart-image endpoint. QuickChart supports a URL form at
/chart?c=...and a JSON POST form. - Choose output controls for the destination: width, height, device pixel ratio, background color, Chart.js version, and format.
- Save the response bytes or embed a generated URL in email, a report, a chatbot, or another static surface.
Grouped bars work well when readers compare values side by side for the same categories. Use lines when labels represent an ordered sequence such as time or rank.
2. Minimal comparison graph with a GET request
This JavaScript example creates a grouped bar chart comparing two products across three features. The configuration is JSON-encoded before it is placed in the URL.
const chart = {
type: 'bar',
data: {
labels: ['Feature A', 'Feature B', 'Feature C'],
datasets: [
{ label: 'Product 1', data: [12, 19, 7] },
{ label: 'Product 2', data: [15, 14, 10] }
]
}
};
const url = 'https://quickchart.io/chart?width=900&height=500&format=png&version=4&c=' +
encodeURIComponent(JSON.stringify(chart));
const response = await fetch(url);
if (!response.ok) throw new Error(`Chart API returned ${response.status}`);
const image = Buffer.from(await response.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('comparison.png', image));
Always URL-encode the configuration. Unescaped braces, quotes, spaces, or array characters can produce malformed requests or silently changed charts.
3. Use POST for production-sized configurations
POST avoids URL-length limits and is the safer default when you have many datasets, labels, plugins, annotations, or formatting options.
curl -X POST https://quickchart.io/chart \
-H 'Content-Type: application/json' \
--data @- > comparison.png <<'JSON'
{
"version": "4",
"format": "png",
"width": 900,
"height": 500,
"devicePixelRatio": 2,
"chart": {
"type": "bar",
"data": {
"labels": ["Feature A", "Feature B", "Feature C"],
"datasets": [
{"label": "Product 1", "data": [12, 19, 7]},
{"label": "Product 2", "data": [15, 14, 10]}
]
}
}
}
JSON
The POST response is image data. Write it as binary; treating it as text can corrupt the file.
Python
import requests
payload = {
"version": "4",
"format": "png",
"width": 900,
"height": 500,
"devicePixelRatio": 2,
"chart": {
"type": "bar",
"data": {
"labels": ["Feature A", "Feature B", "Feature C"],
"datasets": [
{"label": "Product 1", "data": [12, 19, 7]},
{"label": "Product 2", "data": [15, 14, 10]},
],
},
},
}
r = requests.post("https://quickchart.io/chart", json=payload, timeout=30)
r.raise_for_status()
with open("comparison.png", "wb") as f:
f.write(r.content)
Node.js
import { writeFile } from 'node:fs/promises';
const payload = {
version: '4',
format: 'png',
width: 900,
height: 500,
devicePixelRatio: 2,
chart: {
type: 'bar',
data: {
labels: ['Feature A', 'Feature B', 'Feature C'],
datasets: [
{ label: 'Product 1', data: [12, 19, 7] },
{ label: 'Product 2', data: [15, 14, 10] }
]
}
}
};
const res = await fetch('https://quickchart.io/chart', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error(`Chart API returned ${res.status}`);
await writeFile('comparison.png', Buffer.from(await res.arrayBuffer()));
4. Dimensions, formats, and readability
| Control | What it changes | Practical choice |
|---|---|---|
width and height |
Canvas dimensions in pixels | Choose for the final email, report, or document slot instead of accepting defaults. |
devicePixelRatio |
Extra raster resolution | Use 2 for sharper retina output, while checking file size. |
format |
PNG, JPG/JPEG, WebP, SVG, PDF, or base64 output | PNG for general charts; SVG for scalable documents when supported by your pipeline. |
backgroundColor |
Canvas background | Set an explicit color when transparent or default backgrounds would clash with the destination. |
version |
Chart.js major version | Set it to match the syntax in your configuration. QuickChart documents versions 2, 3, and 4; its older v2 line is the default. |
Keep labels short or rotate them when categories are numerous. Make units and comparison direction clear in dataset labels. If a chart will be read on a phone, test the rendered dimensions at the actual display width.
5. Data modeling and chart choices
Grouped bars
Use one dataset for each product or series and keep every dataset aligned with the same labels array. Missing values should be represented deliberately rather than shifting array positions.
Lines
Use lines for ordered observations such as monthly price or rank. A line connecting unrelated categories implies a sequence that may not exist.
Other chart types
Pie and radar charts are useful for part-to-whole or multivariate comparisons when the number of categories is small. Image-Charts also documents bar, line, pie, and radar image output as an alternative API.
6. Limits, authentication, and security
- Check current provider limits before rollout. QuickChart documents free-request strings up to 200,000 characters and label arrays up to 250 entries, with authenticated limits up to 1,500 labels.
- Keep API keys on your server. Do not place private keys in browser JavaScript, public source, or email links.
- For untrusted clients, use a server endpoint or a provider’s signed-URL option rather than allowing arbitrary chart configuration to reach your key.
- Validate labels and numeric values before sending. Reject unexpectedly large arrays and strip secrets from tooltips or labels.
- Review current licensing terms when embedding a chart renderer in a product. QuickChart states that its implementation is dual licensed under GNU AGPLv3 and a commercial license, while generated images may be used for any purpose.
7. Reliability, performance, and cost
- Cache identical requests using a stable hash of the normalized configuration, dimensions, format, and version.
- Use POST for large payloads and set a client timeout. Retry transient network failures with bounded exponential backoff; do not retry malformed 4xx requests unchanged.
- Keep chart dimensions and pixel ratio as small as the destination allows. Larger canvases and higher ratios increase response bytes and processing work.
- Record status codes, response time, provider request identifiers when available, and the chart hash so a failed image can be reproduced.
- For email, store the returned image at a location your mail clients can reach, or use a provider URL that remains valid for your required retention period.
- Confirm current quotas and pricing with the provider before setting a high-volume budget. Do not assume free limits apply after authentication or across providers.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| HTTP 414 or truncated chart | GET URL is too long | Switch to the JSON POST endpoint. |
| Chart is blank | Malformed configuration, unsupported option, or missing data | Validate JSON, check the selected Chart.js version, and confirm every dataset aligns with labels. |
| Unexpected styling | Configuration uses a different Chart.js major version | Set version explicitly and update syntax for that version. |
| Downloaded file will not open | Binary response was decoded as text | Write raw response bytes and inspect the response content type. |
| Labels overlap | Canvas is too small for category names | Increase width, shorten labels, rotate ticks, or split the comparison. |
| 401 or 403 | Missing, invalid, or exposed authentication | Send credentials server-side and verify the account’s current limits. |
| Intermittent timeout | Network or provider transient failure | Set a finite timeout, retry only transient failures, and cache successful results. |
| Wrong totals | Values were converted to strings or shifted between arrays | Validate numeric types and build all datasets from the same ordered category list. |
9. Or skip the browser setup
If your chart is already published as a web page, you can capture the rendered page with ScreenshotNeo instead of maintaining browser automation. Its API accepts one GET request and can return PNG, JPEG, WebP, or PDF. The request below targets a page containing your comparison graph; replace the URL with your page.
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed, and the response identifies the page verdict and billing status. ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf for AI agents.
See the ScreenshotNeo API documentation for the complete option list.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/comparison-chart -o chart.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/comparison-chart"}, timeout=90)
open("chart.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/comparison-chart' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
There are 1,000 free screenshots each month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.
10. FAQ
Should I use GET or POST?
Use GET for a short, cacheable configuration. Use POST when the chart includes many labels, datasets, or options.
Can I embed the result directly in an email?
Yes. Save the returned bytes and host the image where the email can fetch it, or use a stable generated URL. Test the result in the mail clients you support.
How do I make a high-resolution chart?
Set dimensions for the destination and increase devicePixelRatio, commonly to 2, while watching response size.
Which API syntax should a team standardize on?
Chart.js configurations are a practical standard when your team already uses Chart.js. Compare providers on grammar, GET and POST support, output formats, payload limits, version compatibility, authentication, signed URLs, latency, uptime, licensing, and data handling.


