stinger/visjs-bundle
stinger/visjs-bundle integrates the vis.js visualization library into PHP applications, bundling its assets and providing helpers to render interactive networks, timelines, and graphs. It handles asset loading and configuration so developers can embed dynamic, data-driven visualizations without writ...
Reality check first: this is a Symfony2 bundle from 2015 with zero stars and zero dependents — it is not a Laravel package and has no service provider, facade, or config file Laravel can read. Read the repo in this order before installing anything:
composer.json — check the symfony/* and twig/twig constraints. They will almost certainly conflict with any Laravel 9+/10+/11 app (symfony/symfony ~2.x and modern symfony/* components cannot coexist). Expect composer require to fail; that failure is the answer, not a puzzle to solve.Resources/views/ — this is the only part with transferable value. It shows the container <div> + inline <script> pattern the bundle generates.Twig/ or DependencyInjection/) — it is a thin wrapper that serializes a PHP array of options to JSON and emits a new vis.Network(...) call. That logic is ~30 lines and easy to port.Resources/public/ or bower.json — identifies which vis.js version the bundle targets (likely 3.x/4.x, pre-vis-network split).Minimal path for a Laravel app: don't install the bundle. Take the concept and use the modern successor package:
npm install vis-network vis-data
// resources/js/app.js
import { Network } from 'vis-network';
import { DataSet } from 'vis-data';
window.vis = { Network, DataSet };
Then convert the bundle's Twig template into a Blade component. First use case: a "network graph of related entities" admin page (users↔roles, tags↔posts) rendered as nodes/edges from an Eloquent query.
Blade component instead of Twig extension. Port the bundle's serializer into a component that owns the container + init script:
{{-- resources/views/components/vis-network.blade.php --}}
@props(['nodes' => [], 'edges' => [], 'options' => [], 'height' => '500px'])
<div id="{{ $id = 'vis-'.Str::random(8) }}" style="height:{{ $height }}"></div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', () => {
const nodes = new vis.DataSet(@json($nodes));
const edges = new vis.DataSet(@json($edges));
new vis.Network(document.getElementById(@json($id)), {nodes, edges}, @json($options));
});
</script>
@endpush
Server-side graph builder. If you liked the bundle's array-based API, write a small VisGraph class that returns ['nodes' => [...], 'edges' => [...], 'options' => [...]] from your models. This keeps controllers clean and makes the graph testable (assert on the array, not the canvas):
final class VisGraph
{
public function nodes(iterable $models, string $labelKey = 'name'): array { /* ... */ }
public function edge(int|string $from, int|string $to, array $extra = []): array { /* ... */ }
public function toArray(): array { /* ... */ }
}
Progressive loading for larger graphs. The 2015-era pattern of dumping every node in the initial payload does not scale. Return the initial subgraph from the controller and fetch expansions from a JSON endpoint, mutating the DataSet in place:
Route::get('/graph/{entity}/neighbors', fn (Entity $e) => response()->json([
'nodes' => ..., 'edges' => ...,
]));
const res = await fetch(url).then(r => r.json());
nodes.update(res.nodes); // merge, don't recreate the Network
edges.update(res.edges);
Options as a PHP preset. Ship named presets ('hierarchy', 'physics-off', 'timeline') in a config file and merge them, rather than repeating 40-line option arrays per page. This mirrors what the bundle's visjs: config keys were meant to do, but under config/vis.php where Laravel can cache it.
Alpine/Livewire integration. Wrap init in an Alpine x-init and always call network.destroy() on teardown — Livewire morphs will otherwise leak a canvas and a physics simulation per navigation.
vis monolith being split. vis-network@9 has no global vis object, DataSet lives in vis-data, and options like physics.stabilization behave differently. Any code copied from the bundle's templates will silently no-op or throw. Pin to vis-network + vis-data and rewrite, don't translate.Resources/public + Assetic workflow does not exist here; there is no php artisan vendor:publish for it. Use Vite and let npm manage the library. Never hand-copy vendored JS into public/.{{ vis_network(...) }} must become a component or directive; |raw means nothing; @json already escapes for HTML-context script tags, so don't double-encode with json_encode inside Blade.network.once('stabilizationIterationsDone', () => network.setOptions({physics:false}))) or the tab pegs a CPU core.network.body.data.nodes.length, not pixels.<script> with @push('scripts') will break under a strict script-src. Either allow a nonce on the pushed script or move init into a Vite module that reads a JSON blob from data-* attributes.grep -r "vis" vendor/stinger/ for the extension class, and check app/config/config.yml for the visjs: keys, since option merge behavior lives there, not in the bundle defaults.How can I help you explore Laravel packages today?