smronju / nativephp-charts
Native line, area, bar, scatter, candlestick, radar, pie, and doughnut charts for NativePHP Mobile — drawn on-device with SwiftUI Canvas / Jetpack Compose Canvas, no web view or JS charting library.
Package info
github.com/smronju/nativephp-charts
Type:nativephp-plugin
pkg:composer/smronju/nativephp-charts
Requires
- php: ^8.2
- nativephp/mobile: ^3.0|^4.0
Requires (Dev)
- illuminate/view: ^13.32
- pestphp/pest: ^3.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Native line, area, bar, scatter, candlestick, radar, pie, and doughnut charts for NativePHP
Mobile — drawn on-device with SwiftUI Canvas (iOS) and Jetpack Compose Canvas
(Android), the same drawing primitives core's own native:canvas/native:rect use. No web view, no bundled JS
charting library, no third-party charting dependency on either platform.
Android-first: this plugin has been verified on a real Android device. iOS renders through the same code path but has not yet been verified on a real device or simulator — see iOS status before shipping an iOS build that uses it.
Documentation
Installation
composer require smronju/nativephp-charts php artisan vendor:publish --tag=nativephp-plugins-provider php artisan native:plugin:register smronju/nativephp-charts php artisan native:plugin:list
Then rebuild with php artisan native:run.
Quick start
<native:chart> is a single self-closing element for every chart type — type picks the shape, series
carries the data:
<native:chart type="line" :series="[ ['values' => [120, 340, 200, 410], 'color' => theme('primary')], ['values' => [80, 150, 90, 60], 'color' => theme('secondary')], ]" class="w-full h-48" />
This element only draws shapes — no axis labels, no legend, no value formatting. Category labels and a legend
are ordinary native:text alongside it, since the screen already has the same labels/series data in PHP to
render those from:
<native:chart type="line" :series="$series" class="w-full h-48" /> <native:row class="w-full justify-between px-1"> @foreach ($labels as $label) <native:text class="text-xs text-theme-on-surface-variant">{{ $label }}</native:text> @endforeach </native:row>
Available chart types
| Chart | type |
Series data | Reads |
|---|---|---|---|
| Line | line |
values (one per shared category) |
Every series |
| Area | area |
values (one per shared category) |
Every series — overlay fill only, not stacked |
| Bar | bar |
values (one per shared category) |
Every series, as grouped bars |
| Scatter | scatter |
points (a list of {x, y} pairs) |
Every series |
| Candlestick | candlestick |
candles (a list of {open, high, low, close}) |
series[0] only |
| Radar | radar |
values (one per axis, 3+ required) |
Every series |
| Pie | pie |
values (one per slice) |
series[0] only |
| Doughnut | doughnut |
values (one per slice) |
series[0] only |
Every listed chart renders natively on Android via Jetpack Compose Canvas, and through the same code path on
iOS via SwiftUI Canvas (see iOS status).
Chart examples
Line, area, and bar
Line, area, and bar all read values off every series, plotted index-for-index against a scale shared across
every series (0 to the largest value anywhere), so multiple series stay visually comparable. Every series needs
the same number of values.
{{-- Line --}} <native:chart type="line" :series="[ ['values' => [120, 340, 200, 410], 'color' => theme('primary')], ]" class="w-full h-48" /> {{-- Area — overlay only; series fill independently and semi-transparently, not stacked --}} <native:chart type="area" :series="[ ['values' => [42, 51, 62, 58], 'color' => '#2563EB'], ['values' => [28, 34, 40, 37], 'color' => '#F59E0B'], ]" class="w-full h-48" /> {{-- Bar — multiple series render as grouped bars per category --}} <native:chart type="bar" :series="[ ['values' => [42000, 51800], 'color' => '#7C3AED'], ['values' => [31000, 38600], 'color' => '#0F766E'], ]" class="w-full h-48" />
Scatter
Scatter reads points instead of values — real {x, y} coordinates, not evenly spaced categories. Both axes
scale to the actual min/max across every series' points.
<native:chart type="scatter" :series="[[ 'points' => [ ['x' => 1.2, 'y' => 0.34], ['x' => 2.8, 'y' => 0.47], ], 'color' => '#2563EB', ]]" class="w-full h-48" />
Candlestick
Candlestick reads candles off series[0] only — a candlestick chart has exactly one instrument. Each candle
needs open, high, low, and close; the body colors green when it closed at or above where it opened, red
otherwise, unless overridden with rising-color/falling-color.
<native:chart type="candlestick" :series="[[ 'candles' => [ ['open' => 36.72, 'high' => 36.91, 'low' => 36.68, 'close' => 36.84], ['open' => 36.84, 'high' => 36.88, 'low' => 36.70, 'close' => 36.76], ], ]]" rising-color="#15803D" falling-color="#B91C1C" class="w-full h-48" />
Radar
Radar reads values off every series too, but as one value per axis rather than per x position — every series
needs at least 3 of them to form a polygon. It draws grid rings, axis spokes, and each series' filled polygon;
it does not draw axis labels (see Scope).
<native:chart type="radar" :series="[[ 'values' => [88, 92, 74], 'color' => '#6366F1', ]]" class="w-full h-48" />
Pie and doughnut
Both read only series[0]['values']; each value becomes one slice sized by its share of the total.
series[0]['colors'] gives each slice its own color (falls back to the built-in palette, cycling by index,
when omitted). Doughnut draws the same slices as a ring instead of a filled wedge.
<native:chart type="pie" :series="[[ 'values' => [58, 27, 15], 'colors' => ['#7C3AED', '#2563EB', '#F59E0B'], ]]" class="w-full h-48" /> <native:chart type="doughnut" :series="[[ 'values' => [48200, 31750, 9650], 'colors' => ['#2563EB', '#7C3AED', '#F59E0B'], ]]" class="w-full h-48" />
Colors
color (line/area/bar/scatter/radar — the whole series draws in one color) and colors (pie/doughnut — one
per slice, parallel to values) accept anything a theme config value does — hex, #RRGGBBAA, a Tailwind
palette name, an opacity modifier (primary/40) — resolved via the same theme()-driven element-prop resolver
every other themed element uses. Omit color(s) entirely and each series falls back to a built-in six-color
palette, cycling by index.
rising-color and falling-color are candlestick-only, defaulting to green and red when not set; every other
chart type ignores them.
Scope
This element deliberately stays a shape-drawing surface, not a full charting system:
- No axis labels, tick marks, or gridlines with values (radar's grid rings are unlabeled reference lines only).
- No legend, tooltips, or point-selection callbacks.
- No locale-aware value formatting (currency, percent, number grouping) — pass already-formatted labels to
your own
native:textinstead. - Area charts support overlay fills only; stacked (cumulative) areas are not implemented.
- Every "reads
series[0]only" chart type (candlestick, pie, doughnut) ignores any further series rather than rejecting them.
If you need any of the above, either build it in Blade around this element (as the labels/legend examples above do) or reach for a more complete charting plugin.
iOS status
The Swift renderer (resources/ios/NativephpChartsChartRenderer.swift) mirrors the Android one point-for-point,
but SwiftUI's Path.addArc angle/clockwise convention is a well-known gotcha (its clockwise flag is inverted
by the view's flipped y-axis) — the pie/doughnut angle math has not been confirmed on a real device or
simulator. Radar's polygon math uses plain trigonometry rather than addArc, so it doesn't carry the same risk,
but none of the four newer chart types (area, scatter, candlestick, radar) have been visually verified on
either platform yet — only compiled successfully. Verify every chart type on a real device before relying on it
in production.
License
MIT