I built Graph Studio as a small, focused playground for making clean 3D charts with React and three.js. It lets me switch between Bar, Line, and Scatter visualizations, tweak colors and motion, paste or import data, and export a high-res PNG or a reusable JSON scene config. It’s intentionally minimal—fast to try ideas, clean enough to ship demos.
- 3D charts with R3F: Bars, polylines, and scatter points rendered in three.js via
@react-three/fiber. - Motion presets: Subtle entrance animations powered by
@react-spring/three(e.g.,fade,grow,bounce,sweep) with a duration slider. - Palettes: Curated color sets plus support for custom arrays.
- Axes & labels: Simple XY axes, optional grid, and label placement that stays readable.
- Orbit controls: Drag to orbit, wheel to zoom, pan enabled. I persist the camera/target in
localStorageso my view sticks across reloads. - PNG export: One click to capture a sharp PNG from the current frame.
- JSON import/export: Save a scene (template, palette, labels, data, animation) and restore it later.
- Human-friendly data entry: Paste numbers or point pairs; the app is forgiving about separators.
- Modern dark UI: Spacious layout with a “glassy” look that doesn’t get in the way of the chart.
- React + @react-three/fiber (R3F)
- @react-three/drei (helpers like
OrbitControls,Html,Text,Line) - @react-spring/three (animation)
- Plain CSS-in-JS style objects (no CSS framework)
- Node 18+ recommended.
npm install# If using Vite (typical)
npm run dev
# If this project is set up with CRA instead
npm startThen open the printed local URL in your browser.
npm run build
# (Optional) if using Vite:
npm run preview- Pick a template (3D Bar, 3D Line, 3D Scatter).
- Choose a palette or switch to your custom array.
- Select an animation preset and adjust Duration.
- Toggle grid and edit X/Y labels.
- Paste data (see formats below) and click Apply.
- Hit Play to retrigger entrance animations.
- Export PNG for a high-res snapshot.
- Export JSON to save everything, or Import JSON to restore a scene.
- Accepts numbers separated by commas, spaces, newlines, or semicolons.
Examples:
12, 18, 9, 24, 16
12
18
9
24
16
12; 18; 9; 24; 16
- One pair per row. Each row can be
"x y"or"x,y". - Rows can be split by newlines or semicolons.
- Suggested range is
0..1for both axes (the scene normalizes point positions to width/height).
Examples:
0.2,0.5
0.4,0.8
0.85,0.3
0.2 0.5; 0.4 0.8; 0.85 0.3
Click Export JSON to download a file (e.g., graph-config.json) you can re-import later.
I keep validation intentionally light and only apply known fields.
Schema (illustrative):
{
"template": "bar" | "line" | "scatter",
"paletteName": "Professional",
"palette": ["#2563eb", "#7c3aed", "..."], // used if paletteName is absent/invalid
"showGrid": true,
"xLabel": "Categories",
"yLabel": "Values",
"duration": 1.1,
"presetKey": "grow",
"data": [12, 18, 9, 24, 16] // or [{ "x":0.4, "y":0.7 }, ...] for scatter
}Notes:
- When importing, I temporarily disable template defaults so the incoming data isn’t clobbered.
- If
paletteNameis valid, it wins; otherwise a providedpalettearray is used.
- Orbit: drag with primary button / touch drag
- Zoom: mouse wheel / pinch
- Pan: middle/right drag
- View persistence: I save
camera.position,controls.target, andfovper template key (e.g.,graph_view_bar) inlocalStorage. - Interaction hint: A small floating hint appears while you interact and fades after inactivity.
- The export button uses an internal
Captureutility that:- Temporarily increases the renderer’s pixel ratio,
- Forces a render,
- Reads the canvas (
toDataURL) and downloads as PNG, - Restores the previous pixel ratio immediately.
- The R3F
<Canvas>setsgl={{ preserveDrawingBuffer: true }}to ensure capture works after rendering.
src/
components/
Axes.jsx
AxisLabels.jsx
Capture.jsx
InteractionHint.jsx
graphs/
Bars.jsx
Polyline.jsx
Scatter.jsx
scene/
GraphScene.jsx
styles/
ui.js
ui/
Section.jsx
Field.jsx
constants/
palettes.js
presets.js
defaultData.js
utils/
parseData.js
GraphStudio.jsx
- Add palettes: Edit
constants/palettes. - Tweak motion: Presets live behind
useSpringPreset/useSpringConfigForusage in the graph components. Add your own curve/tension or new preset keys and wire them into the dropdown. - New chart types: Follow the patterns in
graphs/—accept normalized inputs, keep animations small and opinionated, and expose minimal props.
- Keep series sizes reasonable (especially scatter point counts).
- Grid divisions should be modest to avoid overdraw.
- Avoid cranking
dprtoo high—PNG export already boosts resolution on demand. - Light counts and shadowing are purposely minimal; keep it that way for interactivity.
- Blank PNG / empty export: Ensure the browser allows canvas capture and that
preserveDrawingBufferis set (it is by default inGraphScene). - Data won’t apply: Check separators and number formats. For scatter, ensure each row has two valid numbers.
- View doesn’t persist: Confirm
localStorageisn’t disabled/private-mode-blocked. - Nothing renders: Verify WebGL support and that the dev server is running without errors.
- Tooltips / value labels
- Multi-series overlays
- Axis ticks/legends
- CSV import
- Theming switch / light mode
- Image export presets (transparent BG, sizes)
MIT. Use it freely. If you make something cool with it, I’d love to see it.
- The
@react-three/fiberand@react-three/dreicommunities for making three.js feel ergonomic in React. react-springfor delightful motion without extra weight.