lustre-charts
v0.4.0
Published
Gorgeous, configurable 3D charts for the web — metallic, glass, neon and holographic looks built on Three.js. Few chart types, obsessive attention to how they look.
Maintainers
Readme
Lustre Charts
Gorgeous, vibe coded, configurable 3D charts for the web.
In mineralogy, "lustre" names the ways light plays on a surface — metallic, vitreous, adamantine. This library brings those finishes to your data.
▶ Play with the live demo — edit chart data and colors, tune every material, effect and quality setting, then export the exact configuration.
Few chart types. Obsessive attention to how they look.
Lustre is a small 3D charting library built on three.js. It deliberately ships a few chart types with deep configurability and reference-grade visual quality, rather than fifty chart types that all look like homework:
- 🥧 Pie / Donut — revolved cross-section profiles (rounded, straight, pillow, tube… or your own points), pad angles, exploding slices, callout labels
- 🌀 Radial — independently scaled concentric percentage rings, progress-edge callouts, collision-free label lanes and optional recessed tracks
- 📊 Bar — single or grouped series, rounded bars, projected value axis, staggered entrances
Everything else is look and feel:
| | |
|---|---|
| 🎨 15 material presets | PBR, translucent, subsurface, harmonic color, spectral, fabric, toon, procedural print, and inset finishes — each with curated, exportable controls |
| 🌗 Themes | dark, light, fully custom objects, transparent backgrounds |
| 🌈 8 palettes | aurora, neon, metal, candy, ocean, sunset, violet, mono — or any color array, auto-extended for large datasets |
| ✨ Effects | bloom post-processing, neon grid floor, HUD rings, floating particles, soft contact shadow |
| 🎬 Motion | sweep / rise / scale / wave / grow entrances, tweened data updates, hover lift & glow, click-to-explode/lift |
| 🏷 Overlays | SVG callout labels (dot + elbow leader), frosted-glass tooltip, interactive legend with animated re-layout |
| ♻️ Engineering | render-on-demand loop, ResizeObserver responsive, PNG + configuration export, complete destroy(), zero dependencies beyond three |
Gallery
| Glossy · light | Metal · light | Metal · dark |
|---|---|---|
|
|
|
|
| Neon + grid + bloom | Neon bars | Glass · dark |
|---|---|---|
|
|
|
|
| Crystal · dark | Iridescent · dark | Inset face · dark |
|---|---|---|
|
|
|
|
| Toon · light | tube profile | Custom profile (your own points!) |
|---|---|---|
|
|
|
|
Quick start
npm
npm install lustre-charts threeTypeScript declarations ship with the package. Because three.js publishes its types separately, TypeScript projects should also install the matching type package:
npm install -D @types/threeimport { LustreChart } from 'lustre-charts';
const chart = new LustreChart('#app', {
type: 'donut',
data: [
{ label: 'Chrome', value: 64 },
{ label: 'Safari', value: 19 },
{ label: 'Edge', value: 9 },
{ label: 'Firefox', value: 8 },
],
options: {
theme: 'dark',
material: 'glossy',
palette: 'aurora',
},
});No bundler? No problem
Lustre is plain ES modules — an import map is all you need:
<div id="app" style="width:100%;height:480px"></div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/",
"lustre-charts": "https://cdn.jsdelivr.net/npm/lustre-charts/src/index.js"
}
}
</script>
<script type="module">
import { LustreChart } from 'lustre-charts';
new LustreChart('#app', { type: 'pie', data: [12, 19, 3, 5] });
</script>The fun parts
Materials in one line
options: { material: 'neon' } // bloom auto-enables, rim lines appear
options: { material: 'metal', palette: 'metal' } // brushed gold/silver/copper
options: { material: { preset: 'glass', roughness: 0.02, ior: 1.8 } } // override anything
options: { material: { preset: 'toon', outline: { color: '#fff', widthPx: 4 } } }
options: { material: { preset: 'subsurface', shader: { radius: 1.1, backscatter: 0.8 } } }
options: { material: { preset: 'tricolor', shader: { dominance: 0.2, flow: 1.25 } } }Every preset has a curated editor in the demo. It shows the active defaults, supports per-control and per-material resets, retains edits while you compare finishes, and includes the active material/shader settings in Copy config and View config.
Reference or balanced quality
options: { quality: { preset: 'ultra' } } // 512px PMREM, 256/16/64 geometry (default)
options: { quality: { preset: 'balanced' } } // 256px PMREM, 256/8/32 geometryBoth tiers keep physical transmission at full resolution. Expert overrides
include environmentSize (64–2048px PMREM faces), radialResolution
(24–512 angular segments), roundedSegments, tubeSegments, PMREM blur, and
transmission resolution scale. The demo intentionally starts at 1024px for
close inspection; 2048px is available as an experimental, high-memory option.
Cross-section profiles
The donut's cross-section is a first-class citizen. Use a preset…
options: { pie: { profile: 'tube' } } // torus-like, straight, rounded, pillow……or hand it your own 2D outline (x = radius, y = height) and Lustre revolves it, caps it
and lights it:
options: {
pie: {
profile: [
{ x: 3.1, y: -0.5 }, { x: 3.25, y: 0 }, { x: 3.1, y: 0.5 },
{ x: 2.0, y: 0.62 }, { x: 1.2, y: 0.45 }, { x: 1.0, y: -0.5 },
],
},
}Concentric radial percentages
Each item owns a complete scale rather than sharing a pie total. Data order is inner-to-outer and a full revolution is 100 by default:
new LustreChart('#app', {
type: 'radial',
data: [
{ label: 'Reach', value: 34 },
{ label: 'Growth', value: 52 },
{ label: 'Quality', value: 68 },
{ label: 'Velocity', value: 81 },
{ label: 'Target', value: 100 },
],
options: {
radial: { ringGap: 0.09, track: true },
},
});Live updates
chart.update({ data: newData }); // tweened re-layout
chart.setTheme('light'); // relights the scene
chart.applyOptions({ material: 'hologram' }); // hot-swap the look
chart.replay(); // run the entrance again
const png = chart.toDataURL(); // export what you see
chart.destroy(); // leaves zero tracesEvents
options: {
interaction: {
onHover: (item) => console.log(item), // { label, value, percent, color… }
onClick: (item) => {},
onSelect: (selectedItems) => {},
},
}Documentation
| | | |---|---| | Getting started | install, first chart, data formats | | Configuration reference | every option, annotated | | Materials & theming | presets, palettes, themes, effects, custom studio | | Pie & donut charts | profiles, explode, labels, sorting | | Radial charts | concentric percentages, tracks, labels, updates | | Bar charts | series, axis, entrances |
Demo playground
▶ mihaipanait.github.io/lustre-charts — hosted straight from this repo, nothing to install.
Or run it locally:
git clone https://github.com/mihaipanait/lustre-charts.git
cd lustre-charts
npm run dev # → http://localhost:5173/demo/The demo lets you flip through every chart type, material, palette, theme, profile and effect. Its Custom palette editor supports ordered color pickers and hex values, while the adaptive Data editor exposes pie slices, radial rings, or the complete bar series/category matrix. Material settings exposes the useful artistic controls for the selected finish, while Quality lab exposes raw PMREM, transmission and geometry settings. Copy config / View config includes the active data, custom colors, material, shader and quality overrides in the generated snippet.
Development
There is no build step. The library is modern ES modules in src/, loaded directly
by the demo via an import map. Edit, refresh, done. See CONTRIBUTING.md.
npm run check # unit/geometry, lint, types, packed consumer, publint
npm run test:browser # Chromium integration, accessibility, lifecycle, mobilesrc/
├── index.js public API (LustreChart factory + exports)
├── core/ BaseChart rig · tween engine · themes · palettes · utils
├── charts/ PieChart · RadialChart · BarChart
├── geometry/ profile revolve + cap builder, outline builder
├── materials/ the fifteen PBR and graphic presets
├── overlay/ SVG callout labels · tooltip · legend
└── fx/ studio environment · bloom · grid/rings/particles/shadowHeads-up: three.js prints a cosmetic
sigmaRadians … will clipwarning while pre-filtering the environment. It is harmless and comes from three's PMREM mip chain, not your code.
Browser support
Any evergreen browser with WebGL2. The supported and continuously tested
three.js range is >= 0.175.0 < 0.186.0. r175 is the minimum because it adds
the variable-size PMREMGenerator.fromScene() API used by Lustre's reflection
quality controls.
Vibe coded
Lustre Charts is a vibe coded project. It began with an original idea by Mihai Panait, then evolved through iterative collaboration with AI models including Gemini, Claude, Codex, DeepSeek and Kimi. These models helped refine, implement, review, test and polish the project, while its creative direction and final decisions remain human-led.
License
MIT © 2026 Mihai Panait
