uneca/plotly-chart-editor
Reactive Plotly.js chart builder for Laravel via Livewire. Sidebar-driven editor to configure traces and layout, multi-language UI (EN/FR/PT/ES), multiple sync modes and persistence options. Requires Plotly.js 3.x (peer dep), Alpine, PHP 8.4+.
Installation:
composer require uneca/plotly-chart-editor
npm install # Ensure Plotly.js is available (peer dependency)
Publish Config:
php artisan vendor:publish --provider="Uneca\PlotlyChartEditor\PlotlyChartEditorServiceProvider" --tag="config"
config/plotly-chart-editor.php to define your chart profiles (e.g., scatter, bar).First Use Case:
<livewire:plotly-editor
:chart-profiles="['scatter']"
wire:model="chartConfig"
sync-mode="hybrid"
/>
chartConfig) to persist state server-side.Define Profiles:
Extend config/plotly-chart-editor.php to add custom trace types (e.g., pie):
'profiles' => [
'pie' => [
'groups' => [
'data' => ['title', 'values', 'labels'],
'style' => ['hole', 'pull'],
],
],
],
Dynamic Field Binding:
Use x-model in Blade to sync Alpine state to Livewire:
<x-plotly-chart-editor::schema-field
type="number"
x-model="chartBuilder.config.traces[0].pull"
/>
Sync Strategies:
<button @click="chartBuilder.syncToBackend()">Save</button>
Data Integration: Fetch data via Livewire properties and pass to Alpine:
public $dataset = []; // Loaded via Livewire mount()
<script>
Alpine.store('chartBuilder').dataset = @this.dataset;
</script>
Custom Fields:
Extend resources/js/plotly-chart-editor.js to add field types (e.g., color-picker):
Alpine.data('colorPicker', () => ({
init() {
this.$watch('modelValue', (val) => {
this.$el.style.backgroundColor = val;
});
},
}));
Register in Blade:
<x-plotly-chart-editor::schema-field type="color-picker" />
Theme Overrides: Override Tailwind tokens in your app’s CSS:
@layer theme {
--plotly-editor-primary: #3b82f6;
}
Server-Side Rendering:
Use wire:ignore for the Plotly canvas to avoid Livewire re-renders:
<div wire:ignore id="plotly-canvas"></div>
State Mismatch:
chartBuilder.syncToBackend() or reset _lastStructuralSig in Alpine:
Alpine.store('chartBuilder')._lastStructuralSig = Date.now();
Plotly Initialization:
Plotly.newPlot() fails if the DOM element isn’t ready.x-init to defer initialization:
<div x-init="setTimeout(() => Plotly.newPlot('plotly-canvas', data), 100)"></div>
Deep Copy Quirks:
JSON.parse(JSON.stringify()) flattens Date objects.const deepCopy = (obj) => JSON.parse(JSON.stringify(obj, (_, v) =>
v instanceof Date ? v.toISOString() : v
));
Tailwind Conflicts:
!important sparingly and prefix classes (e.g., pe- for package-specific).<pre x-text="JSON.stringify(Alpine.store('chartBuilder'), null, 2)"></pre>
config/livewire.php:
'log' => env('APP_DEBUG', false),
Custom Sync Logic:
Override syncToBackend() in Alpine by extending the store:
Alpine.store('chartBuilder').syncToBackend = function() {
// Custom logic (e.g., validation)
this.$wire.call('customSync', this.config);
};
Plotly Events:
Listen to Plotly events (e.g., plotly_relayout) in Alpine:
document.addEventListener('plotly_relayout', (e) => {
Alpine.store('chartBuilder').config.layout = e.details.layout;
});
Server-Side Validation: Validate incoming data in Livewire:
protected function rules() {
return [
'chartConfig.traces.*.type' => 'required|in:scatter,bar,pie',
];
}
manual overrides auto, but hybrid merges both (auto + manual button).required in the schema will show validation errors if omitted.Alpine.store() namespace; use chartBuilder exclusively.
```markdown
## Performance Considerations
- **Large Datasets**: Use `Plotly.react()` for incremental updates instead of `newPlot()`.
- **Memory Leaks**: Clear Alpine stores on component unmount:
```js
window.addEventListener('livewire:init', () => {
Livewire.hook('component.updated', ({ component }) => {
if (component.$id === 'plotly-editor') {
component.$wire.on('chartUpdated', () => {
Alpine.store('chartBuilder').reset();
});
}
});
});
$this->mock(Alpine::class, function ($mock) {
$mock->shouldReceive('store')->andReturn(['chartBuilder' => ['config' => []]]);
});
$page->fill('#chart-title', 'Test Chart');
$page->click('text=Save');
$this->assertDatabaseHas('charts', ['title' => 'Test Chart']);
How can I help you explore Laravel packages today?