fastchart

June 5, 2026 ยท View on GitHub

Tests Version License: BSD-3-Clause Follow @iliaa

Native C PHP extension. 26 chart types behind a modern OO API with fluent setters and final classes. Line, area, bar, scatter, bubble, pie, radar, polar, surface, contour, gauge, gantt, box-plot, treemap, funnel, waterfall, heatmap, linear meter, plus a deep StockChart (seven candle styles, SMA / EMA / WMA overlays, volume + indicator panes).

SVG is the canonical render format. PNG / JPG / WebP outputs flatten text to glyph paths, run plutovg over the resulting SVG, and encode the RGBA buffer with libpng / libjpeg-turbo / libwebp. The same chart object serves a sharp <svg> for dashboards or a PNG for emails without rebuilding state. renderToFile() picks the encoder from the extension; renderPng() / renderJpeg() / renderWebp() / renderSvg() return bytes in-process.

fastchart: 26 chart types in one PHP extension

Live gallery โ†’. Side-by-side SVG / PNG / JPG / WebP renders for every chart family, with the source PHP shown above each row.

Status

v1.1: confidence-band area charts, cone-style funnels, smooth polar curves with overlay vectors, log-scale bubble plots, HTML image-map hot-spots on Bar / Pie / Scatter (setImageMap + getImageMap). v1.0 dropped libgd as a runtime dependency, rebuilt rasterization around vendored plutovg, and replaced draw($canvas) with renderSvg/Png/Jpeg/Webp + renderToFile. 26 chart types, 2-class Symbol family, 138 phpt tests. See CHANGELOG.md for the full breaking-change list.

Install

PIE handles the configure / make / install cycle for you against your active PHP install:

pie install iliaal/fastchart

PIE picks up the latest tagged release from Packagist and respects your platform's pkg-config for the FreeType / libpng / libjpeg-turbo / libwebp probes. After install, enable the extension via your distribution's mechanism (e.g. docker-php-ext-enable fastchart on the official PHP images, or add extension=fastchart to php.ini).

From source

Build manually against the PHP install you want to extend:

phpize
./configure --enable-fastchart
make -j
make test

Strict-warnings dev build (recommended for contributors):

./configure --enable-fastchart --enable-fastchart-dev

Runtime check:

php -d extension=./modules/fastchart.so \
  -r 'echo FastChart\Chart::version(), PHP_EOL;'

Requirements

  • PHP 8.1 or later (NTS or ZTS).
  • FreeType development headers (libfreetype-dev / freetype-devel). Required, since text rendering depends on FreeType.
  • libpng / libjpeg-turbo / libwebp development headers. Each is optional; config.m4 probes them independently via pkg-config and the corresponding renderPng() / renderJpeg() / renderWebp() is wired up only for libs that resolve at build time. A missing lib turns the matching method into a "format not compiled in" Error at call time; SVG output stays available regardless. phpinfo() reports the resolved version of each lib (or (not compiled in)) so you can audit a build.
  • plutovg + plutosvg are vendored under vendor/; no separate install required.

Quick start

The shortest path is the renderToFile() helper, which picks the encoder from the file extension:

(new FastChart\LineChart(640, 320))
    ->setTitle('Daily active users')
    ->setSeries([['data' => [820, 940, 870, 1020, 1180, 1250, 1340]]])
    ->setCategoryLabels(['Mon','Tue','Wed','Thu','Fri','Sat','Sun'])
    ->renderToFile('/tmp/dau.png');

renderPng(), renderJpeg(), and renderWebp() return the encoded bytes if you need them in memory. Raster compression knobs:

$chart->setJpegQuality(75);              // sticks on the chart instance;
$chart->renderJpeg();                    // ...used by every render call
$chart->renderJpeg(95);                  // per-call override (1..100)

$chart->renderWebp();                    // default quality 90
$chart->renderWebp(60);                  // smaller, lossier (1..100)

$chart->renderToFile('/tmp/out.webp');   // setJpegQuality only affects
                                         // .jpg; renderToFile uses the
                                         // built-in WebP default

PNG is always lossless; there's no quality knob. The encoder is libpng with default filter selection.

WebP has four encoder modes selectable via setWebpMode(). The default WEBP_DRAWING is tuned for chart-shaped content (flat fills, sharp edges); see the table in docs/examples/50_webp_modes.php for the speed/size trade-offs each mode picks.

$chart->setWebpMode(FastChart\Chart::WEBP_LOSSLESS);  // archival
$chart->setWebpMode(FastChart\Chart::WEBP_FAST);      // preview pipelines
$chart->setWebpMode(FastChart\Chart::WEBP_PHOTO);     // photo backgrounds
$chart->setWebpMode(FastChart\Chart::WEBP_DRAWING);   // back to default

Call renderSvg() on the same chart object for vector output: dashboards, print, anywhere infinite-zoom matters.

$chart = (new FastChart\LineChart(640, 320))
    ->setTitle('Daily active users')
    ->setSeries([['data' => [820, 940, 870, 1020, 1180, 1250, 1340]]])
    ->setCategoryLabels(['Mon','Tue','Wed','Thu','Fri','Sat','Sun']);

$svg = $chart->renderSvg();              // full <?xml ?><svg>...</svg>
$chart->renderToFile('/tmp/dau.svg');    // same, written to disk

// Stitch several charts into one outer SVG document:
$fragment = $chart->drawSvgFragment();   // <g class="fastchart">...</g>

Construction is identical for every output format; only the final render call differs. By default, SVG text is flattened to glyph outline paths (SVG_TEXT_PATHS mode). The resulting SVG is self- contained and renders identically in any viewer or rasterizer. For smaller files with selectable text, switch to native <text> mode:

$chart->setSvgTextMode(FastChart\Chart::SVG_TEXT_NATIVE);
$svg = $chart->renderSvg();  // ~30% smaller; needs consumer text support

Raster outputs (PNG/JPG/WebP) always use the PATHS mode internally; plutovg has no text support of its own, so glyph flattening is what makes labels appear in the rasterized output.

Three static methods on FastChart\Chart rasterize caller-supplied SVG bytes through the same plutovg + libpng / libjpeg-turbo / libwebp pipeline. Useful for round-tripping renderSvg() output, or for stitching multiple drawSvgFragment() calls into one outer SVG and rasterizing the result, all in-process:

$png  = FastChart\Chart::svgToPng($svg);
$jpg  = FastChart\Chart::svgToJpeg($svg, 88, 0xFFFFFF);  // bg + quality
$webp = FastChart\Chart::svgToWebp($svg, 90, FastChart\Chart::WEBP_LOSSLESS);

Output dimensions come from the SVG's width / height / viewBox. SVG <text> elements render blank because plutovg has no text engine, so text must be path-flattened first (fastchart's own SVG builder does this automatically). See docs/examples/51_svg_to_raster.php for a runnable demo and docs/specs/svg-to-raster.md for the full contract.

๐Ÿ“Š Performance

Median in-memory render time at 1920ร—1080 on a single core (Intel i9-13950HX, PHP 8.4 release build NTS -O2, default font + DPI). SVG is the canonical output; PNG / WebP / JPG go through the same SVG build, then plutosvg + plutovg rasterize, then the format encoder (libpng / libwebp / libjpeg-turbo). The raster columns therefore add the rasterize cost on top of the SVG-only number.

ChartSVG msPNG msWebP msJPG ms
AreaChart0.1245.1934.0411.45
BarChart0.2243.5435.1812.35
BoxPlot0.0940.2630.749.95
BubbleChart0.0652.8444.1814.84
ContourChart0.1148.4641.1412.97
Funnel0.0840.1531.599.53
GanttChart0.1240.0830.9810.22
GaugeChart0.0244.7533.7810.19
Heatmap0.0543.2835.6211.85
LineChart0.1145.4537.2311.81
LinearMeter0.0238.6429.108.98
PieChart0.0644.4233.8411.75
PolarChart0.0247.6037.1011.48
RadarChart0.0647.8936.4813.39
ScatterChart0.1143.0532.4810.38
StockChart0.2046.0040.8614.19
SurfaceChart0.0640.0934.5010.35
Treemap0.1139.7631.749.74
Waterfall0.0939.2132.0510.08

SVG stays well under a quarter-millisecond across the board (0.02 to 0.22 ms) because there's no rasterization; the backend appends strings into a smart_str via an allocation-free integer/fraction number emitter. The raster encoders split into three bands: JPG fastest (9-15 ms, libjpeg-turbo with 4:2:0 subsampling + SSSE3/NEON RGBA-pack), WebP middle (29-44 ms, libwebp with WEBP_PRESET_DRAWING + method=2 + multi-thread), PNG slowest (38-53 ms, libpng's deflate dominates). All four formats stay under 55 ms at 1080p on one thread.

These numbers reflect the optimization series in v1.1.x (allocation-free SVG number formatting, glyph outline cache, opaque-detect un-premultiply with SSSE3/NEON shuffle, deferred text overlays, larger FT raster pool); see optimization.md for the per-finding breakdown.

Repro the numbers locally:

php -d extension=./modules/fastchart.so docs/bench/bench.php

Iteration count via FC_BENCH_ITERS (default 50). Bench source at docs/bench/bench.php.

What you can render

26 chart classes plus a 2-class symbology family, all under the FastChart\ namespace. Each name links to its rendered example image:

Cross-cutting features available on most chart types:

  • TrueType / OpenType labels via setFontPath() (and per-role setTitleFont(), setAxisFont(), setLabelFont()).
  • Light and dark themes (THEME_LIGHT, THEME_DARK); per-series colors via setSeriesColors(); full custom palettes via setPalette().
  • Legend positioning (LEGEND_TOP_RIGHT, _TOP_LEFT, _BOTTOM_RIGHT, _BOTTOM_LEFT, _NONE).
  • Annotations: plot bands, vertical bands, horizontal / vertical lines, text labels, icon plots, error bars, zones.
  • Strict-mode input validation (setStrict(true) rejects malformed series with a TypeError instead of silently coercing to NaN).
  • Background images, drop shadows, anti-aliased lines and markers.
  • Image map output (getImageMap() returns category-aligned rectangles for HTML overlay).

Examples

A gallery of code + rendered chart pairs lives in docs/README.md. Forty-two runnable scripts in docs/examples/ regenerate the images and exercise every public method on the API surface.

Public classes

All under the FastChart\ namespace:

  • Chart: abstract base. Carries shared geometry / theme / font / legend / annotation setters, the version() static, and the chart- family enums (themes, candle styles, legend positions, line styles, marker styles, MA kinds).
  • LineChart, AreaChart, BarChart, ScatterChart, BubbleChart: series-based plots.
  • PieChart: slice-based, with optional donut hole.
  • StockChart: OHLC(V) candlesticks, moving-average overlays, volume + indicator panes.
  • RadarChart, PolarChart, SurfaceChart, ContourChart: non-Cartesian plots.
  • GaugeChart, LinearMeter: single-value readouts with zoned ranges.
  • GanttChart: time-axis task bars with dependency links and milestones.
  • BoxPlot: five-number summaries with per-category outliers.
  • Treemap, Funnel, Waterfall: value-encoded layouts (rectangle packing, stage drop-off, signed-delta running totals). Funnel supports a triangle-with-bands layout via setStyle(STYLE_PYRAMID) for callers who want the classic pyramid shape.
  • Heatmap: 2D grid with linear color-ramp interpolation.

Every setter returns static, so a single fluent expression configures and emits a chart.

The Symbol family lives parallel to Chart (no shared base, since axes / palettes / plot rect have no meaning for a barcode):

  • Symbol: abstract base for all 1D + 2D codes. Carries shared setters: setSize(), setData(), setQuietZone(), setForeground(), setBackground(), setTransparentBackground(), setDpi(), setSvgTextMode(), setJpegQuality(), plus the same renderPng() / renderJpeg() / renderWebp() / renderSvg() / drawSvgFragment() / renderToFile() helpers as Chart. Reload via imagecreatefromstring() to composite onto an existing canvas.
  • Barcode: abstract 1D linear-barcode base.
  • Code128 (extends Barcode): ISO/IEC 15417, alphanumeric, three subsets (A: control + uppercase, B: full ASCII printable, C: digit pairs). Auto-switches between subsets to minimise encoded length; mod-103 checksum appended automatically. setShowText(true) renders the human-readable payload below the bars using the auto-detected default font.
  • QrCode (extends Symbol): ISO/IEC 18004, four error-correction levels (ECC_L ~7%, ECC_M ~15%, ECC_Q ~25%, ECC_H ~30%), versions 1..40. Encoder is the vendored nayuki/QR-Code-generator C library. setMinVersion() / setMaxVersion() pin the symbol size; the encoder picks the smallest version that fits within the range. Input must be valid UTF-8.

๐Ÿ”— PHP Performance Toolkit

Companion native PHP extensions for high-throughput PHP workloads:

  • php_excel: native Excel I/O. 7-10ร— faster than PhpSpreadsheet, full XLS/XLSX with formulas, formatting, and styling. Powered by LibXL.
  • mdparser: native CommonMark + GFM parser. 15-30ร— faster than pure-PHP alternatives, full CommonMark 0.31 compliance.
  • php_clickhouse: native ClickHouse client speaking the wire protocol directly. Picks up where SeasClick left off.

License

BSD 3-Clause for the extension itself; see LICENSE. Vendored third-party code (all MIT):

SPDX: (BSD-3-Clause AND MIT).


Follow @iliaa on X โ€ข Blog โ€ข If this saved you a chart-rendering microservice, โญ star it!