elqora / chart
Framework-neutral, renderer-agnostic chart data definitions for PHP.
Requires
- php: ^8.2
Requires (Dev)
- phpunit/phpunit: ^11.0
README
Elqora Chart is a framework-neutral PHP package for describing portable chart data. It provides immutable DTOs, backed enums, serialization, hydration, and structured validation for chart definitions that can be rendered by a host using any charting system.
It does not render charts, generate HTML, build JavaScript options, or depend on frontend or PHP frameworks.
System Architecture & React Registry
The Elqora Chart system consists of two complementary components:
- PHP Package (
elqora/chart): The protocol authority. Runs on your backend to construct, validate, and serialize portable chart data models into deterministic JSON. - Elqora Shadcn React Registry: The customizable host implementation. An editable React component registry built with Recharts, Tailwind CSS, and shadcn UI. It receives serialized JSON payloads directly from your PHP backend and renders them dynamically on the frontend.
Installing React Registry Components
You can install the React chart infrastructure directly into your React / Next.js / Vite application using the shadcn CLI with the organization/repo/component namespace:
npx shadcn@latest add elqora/chart/chart
Or directly from the registry manifest URL:
npx shadcn@latest add https://raw.githubusercontent.com/elqora/chart/main/registry.json
This installs editable components into your project's components/chart/ directory, including:
<Chart />/<ChartRenderer />: Top-level unified chart router.cartesian-renderer: Line, Area, Bar, Stacked Bar, Sparklines.categorical-renderer: Pie, Doughnut.coordinate-renderer: Scatter, Bubble.radial-renderer: Radar, Gauge.flow-renderer: Funnel.matrix-renderer: Heatmap.hierarchical-renderer: Treemap, Sunburst.statistical-renderer: Box Plot.financial-renderer: Candlestick.
Frontend React Usage
Once installed, your frontend simply fetches the JSON response from your PHP backend API and passes it to the unified <Chart /> component:
import React, { useEffect, useState } from 'react'; import { Chart, SerializedChart } from '@/components/chart'; export function DashboardWidget({ apiUrl }: { apiUrl: string }) { const [chartData, setChartData] = useState<SerializedChart | null>(null); useEffect(() => { fetch(apiUrl) .then((res) => res.json()) .then((data) => setChartData(data)); }, [apiUrl]); if (!chartData) return <div>Loading...</div>; // Render any server-generated chart dynamically return <Chart chart={chartData} height={350} className="shadow-lg" />; }
Installation
composer require elqora/chart
Convenience builders
The Charts facade creates the same canonical Chart objects as manual construction.
Minimal line chart
use Elqora\Chart\Charts\Charts; use Elqora\Chart\Enums\ValueType; use Elqora\Chart\Series\Series; $chart = Charts::line( key: 'delivery.throughput', title: 'Delivery throughput', category: 'time', rows: [ ['time' => '10:00', 'delivered' => 0, 'failed' => 0], ['time' => '10:30', 'delivered' => 500, 'failed' => 12], ], series: [ new Series('delivered', 'Delivered', 'delivered', ValueType::INTEGER), new Series('failed', 'Failed', 'failed', ValueType::INTEGER), ], );
Bar chart
use Elqora\Chart\Charts\Charts; use Elqora\Chart\Data\PresentationHints; use Elqora\Chart\Enums\Orientation; use Elqora\Chart\Series\Series; $chart = Charts::bar( key: 'revenue.by-region', title: 'Revenue by region', category: 'region', rows: [ ['region' => 'North', 'revenue' => 125000], ['region' => 'South', 'revenue' => 98000], ], series: [ new Series('revenue', 'Revenue', 'revenue'), ], presentation: new PresentationHints(orientation: Orientation::HORIZONTAL), );
Pie chart
use Elqora\Chart\Charts\Charts; $chart = Charts::pie( key: 'orders.status', title: 'Orders by status', category: 'status', value: 'count', rows: [ ['status' => 'completed', 'count' => 83], ['status' => 'failed', 'count' => 9], ['status' => 'canceled', 'count' => 8], ], );
Scatter chart
use Elqora\Chart\Charts\Charts; $chart = Charts::scatter( key: 'duration.by-quantity', title: 'Duration by quantity', x: 'quantity', y: 'duration', rows: [ ['quantity' => 100, 'duration' => 12], ['quantity' => 500, 'duration' => 42], ], );
Manual construction
The builders are only convenience methods. Manual construction remains the canonical chart protocol and is useful when you need direct access to payload DTOs.
use Elqora\Chart\Charts\Chart; use Elqora\Chart\Data\TabularData; use Elqora\Chart\Enums\ChartType; use Elqora\Chart\Enums\ValueType; use Elqora\Chart\Series\Series; $chart = new Chart( key: 'delivery.throughput', type: ChartType::LINE, title: 'Delivery throughput', data: new TabularData( categoryField: 'time', rows: [ ['time' => '10:00', 'delivered' => 0], ['time' => '10:30', 'delivered' => 500], ], series: [ new Series( key: 'delivered', label: 'Delivered', field: 'delivered', valueType: ValueType::INTEGER, ), ], ), );
Hierarchical chart
use Elqora\Chart\Charts\Chart; use Elqora\Chart\Data\HierarchyData; use Elqora\Chart\Enums\ChartType; use Elqora\Chart\Hierarchy\HierarchyNode; $chart = new Chart( key: 'service.mix', type: ChartType::TREEMAP, title: 'Service mix', data: new HierarchyData([ new HierarchyNode('root', 'All services', children: [ new HierarchyNode('email', 'Email', 120), new HierarchyNode('sms', 'SMS', 75), ]), ]), );
Financial chart
use Elqora\Chart\Charts\Chart; use Elqora\Chart\Data\CandlestickData; use Elqora\Chart\Data\CandlestickPoint; use Elqora\Chart\Enums\ChartType; $chart = new Chart( key: 'market.ohlc', type: ChartType::CANDLESTICK, title: 'Market OHLC', data: new CandlestickData([ new CandlestickPoint('2026-07-08', open: 100, high: 110, low: 95, close: 105, volume: 1000), ]), );
Serialization
All DTOs serialize to deterministic, JSON-compatible arrays. Nullable fields are omitted consistently.
$array = $chart->toArray(); $json = json_encode($chart, JSON_THROW_ON_ERROR); $hydrated = Chart::fromArray($array);
Validation
Validation collects structured issues and does not rely on exceptions for expected user-data errors.
$result = $chart->validate(); if (! $result->isValid()) { foreach ($result->issues as $issue) { echo $issue->code . ': ' . $issue->message . PHP_EOL; } }
Renderer neutrality
Elqora Chart describes chart meaning and data. Hosts decide how to map that model into a renderer, a report, a data table, or another output. The public API deliberately avoids renderer option objects, callbacks, CSS, framework service identifiers, DOM behavior, and component names.
Non-goals
- Rendering charts.
- Building renderer-specific options.
- Providing frontend components.
- Providing Laravel, Symfony, or other framework integrations.
- Acting as a dashboard, analytics, reporting, or database package.