Charts are drawn by JavaScript, but most places charts need to go can't run JavaScript: emails, Slack and Teams messages, PDFs, slide decks, SMS and printed reports. Render the chart in a browser, wait until it's drawn, and send a PNG instead.
Chart.js to PNG
Load the library from a CDN, draw the chart, and capture its container. Turn off the chart's animation so the capture never catches it half drawn:
curl -X POST "https://shotkit.net/api/take" \
-H "X-Access-Key: YOUR_ACCESS_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.6/dist/chart.umd.min.js\"></script><div id=\"chart\" style=\"width:800px;height:400px;padding:16px;background:#fff\"><canvas id=\"c\"></canvas></div><script>new Chart(document.getElementById('\''c'\''),{type:'\''bar'\'',data:{labels:['\''Jul'\'','\''Aug'\'','\''Sep'\'','\''Oct'\''],datasets:[{label:'\''Signups'\'',data:[120,190,240,310],backgroundColor:'\''#2563eb'\''}]},options:{animation:false,maintainAspectRatio:false}});document.body.dataset.ready='\''1'\''</script>","selector":"#chart","wait_for_selector":"body[data-ready]","device_scale_factor":2,"format":"png"}' \
--fail-with-body -o shot.pngThe script sets data-ready once the chart is drawn, and wait_for_selector waits for it. It's a reliable signal for any library, including ones that render after fetching data.
Any library, your styles
Because it's a real browser, there's nothing to port: ECharts, Highcharts, D3, Vega-Lite, Recharts in a small React page, Mermaid diagrams or your own canvas code all render as they do in your app. Keep the chart in a page of your own and pass its URL:
curl -X POST "https://shotkit.net/api/take" \
-H "X-Access-Key: YOUR_ACCESS_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://app.example.com/internal/charts/weekly-revenue?team=emea","selector":"#chart","wait_for_selector":"#chart canvas","authorization":"Bearer YOUR_INTERNAL_TOKEN","device_scale_factor":2,"format":"png"}' \
--fail-with-body -o shot.pngauthorization sends a header to your page, so an internal chart route doesn't need to be public.
Getting a clean capture
- Animations: disable them in the chart's options (
animation: falsein Chart.js and ECharts), or addreduce_motion: true. - Sharpness:
device_scale_factor2 or 3 renders canvas charts at higher resolution, so they stay sharp in retina emails and PDFs. - Transparent background:
omit_background: truewithformat: "png"drops the white background, as long as the chart's container doesn't set one. - Several charts: render one page with all of them and capture it whole, or one request per chart with different
selectorvalues.
Frequently asked questions
How is this different from QuickChart?
QuickChart renders Chart.js configurations from a URL, which is convenient for that one library. Here you render any library and any HTML around it, such as titles, legends, tables and your brand fonts, at the cost of writing the page yourself.
Why is my chart blank or half drawn?
The capture happened before the chart finished. Turn off animations and wait for a signal: wait_for_selector on the canvas, or on an attribute your script sets once the chart is drawn.
Can I get the chart as SVG?
No. Output formats are raster images, PDF and video. Inside a PDF, SVG charts stay vector graphics, so format: "pdf" is the choice when you need them to scale.